GKTurnBasedEventListener method not called when using custom interface - swift

According to the documentation, I should implement the GKTurnBasedEventListener protocol's player(_:receivedTurnEventFor:didBecomeActive:) method to receive turn-based event information.
Although it's documented as GKTurnBasedEventListener, Apple now recommends that we implement the GKLocalPlayerListener protocol instead of several separate protocols including GKTurnBasedEventListener.
From the docs:
Adopt the GKLocalPlayerListener protocol to handle a variety of Game Center events instead of the individual GKChallengeListener, GKInviteEventListener, GKSavedGameListener, and GKTurnBasedEventListener protocols.
Then implement the player(_:receivedTurnEventFor:didBecomeActive:) and other GKTurnBasedEventListener protocol methods to handle turn-based events that occur throughout a match.
So, I've implemented GKLocalPlayerListener.
At first, I was using the default Game Center interface -- and player(_:receivedTurnEventFor:didBecomeActive:) was called as expected (when the user started a new match, for example).
However, now that I'm using my own custom interface, the method is no longer called, ever. It's not called on the Simulator, and it's not called on device.
So, here's what I'm doing:
Set up the GKLocalPlayerListener protocol:
class GameCenterMatches: UIViewController, GKLocalPlayerListener {
...
}
Add the player(_:receivedTurnEventFor:didBecomeActive:) method to the class:
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
...
}
Immediately after authenticating with Game Center, register the class with the local player (GameCenterAuth.player is GKLocalPlayer.local, and SceneDelegate.gameCenterMatches is GameCenterMatches()):
GameCenterAuth.player.register(SceneDelegate.gameCenterMatches)
As an example of something I believe should trigger player(_:receivedTurnEventFor:didBecomeActive:), here's how I'm creating new matches:
let request = GKMatchRequest()
//Configure the request
GKTurnBasedMatch.find(for: request) { match, error in
...
}
Question: Why isn't player(_:receivedTurnEventFor:didBecomeActive:) being called for my custom interface events, and how can I make it work?

Related

How do you update your UI when using a state machine

I'm using swifts stateMachine from gamePlayKit. It works great, but there is one thing I don't understand: The stateMachineseems to be its own isolated island; from what I can tell there is no way to pass in arguments or get callbacks. And this raises questions like, how do I update the UI?
Lets say I enter combat state:
stateMachine.enter(CombatState.self)
There's no way to pass in an argument. Ok, fine. But then CombatState does its thing and when it's done it goes into another state. BUT, before that I need a callback to the view so that I can remove the units from the board lost in combat like so:
self.removeChildren(in: nodesOutOfAction)
But there's no way to send the nodes out of the stateMachine because there's no possible way to add a completion handler. So how do you work around this problem? To me the stateMacine is pointless as it can´t update the UI.
Its possible to access properties of the state like so:
stateMachine.enter(CombatState.self)
self.removeChildren(in: combatState.nodesToRemove)
But that can't be safe, right? Still, as this is the only option that I know of, this is how I solve it.
I totally recommend you check the DemoBots project from Apple.
You should pass the entity reference on the State Initializer
For example
class CombatState: GKState {
unowned var gameScene: GameScene
required init(gameScene: GameScene) {
self.gameScene = gameScene
}
override func didEnter(from previousState: GKState?) {
gameScene.removeChildren(in: ...)
}
}
With this logic you can decouple a lot of logic inside to the states.

How to store value, received in delegate class, inside external one?

There is a BLEManager class, that is responsible for scanning, connecting, and receiving data from Bluetooth Low Energy (BLE) devices. Looks like that:
class BLEManager: ObservableObject, OtherProtocols {
private var myCentral: CBCentralManager!
#Published var data = 0.0
override init() {
super.init()
myCentral = CBCentralManager(delegate: self, queue: nil)
myCentral.delegate = self
}
// ...some functions that scan, establish connection, etc.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
// here I receive raw value, handle it and get a data to work with
data = 10.0 // imagine it's the data received from BLE device
}
}
Right now the "data to work with" is stored inside this class. I'd like to move this data in such a way, so the current class (BLEManager) is responsible for the BLE logic only, and data is stored together with other user data.
Is it possible in Swift?
P.s. I'm pretty new to Swift. Have experience in JS.
EDITED
In the current case, BLEmanager receives data from one specific peripheral. The data represents a human weight, to be clear. Other than that, there is a struct with human biometric data (age, height, gender). At the end of the day, biometric data + data from the device (weight) are closely related and are used in the same calculations.
RESULT
I was able to implement the Cristik’s approach. The only difference that in my case the subsription happens in a View’s .onAppear() modifier and not on class init, as he described. Had troubles with passing a publisher to the class.
I'd like to move this data in such a way, so the current class (BLEManager) is responsible for the BLE logic only, and data is stored together with other user data
This is a good mindset, as currently your BLEManager breaks the Single Responsibility Principle, i.e. has multiple responsibilities. The ObservedObject part is a SwiftUI specific thing, so it makes sense to be extracted out of that class.
Now, implementation-wise, one first step that you could make, is to transform the data property to a publisher. This will allow clients to connect to the data stream, and allows you to circulate the publisher instead of the BLEManager class, in the rest of your app.
import Combine
class BLEManager: OtherProtocols {
// you can use `Error` instead of `Never` if you also want to
// report errors which make the stream come to an end
var dataPublisher: AnyPublisher<Int, Never> { _dataPublisher.eraseToAnyPublisher() }
private var myCentral: CBCentralManager!
// hiding/encapsulating the true nature of the publisher
private var _dataPublisher = PassthroughSubject<Int, Never>()
// ...
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
_dataPublisher.send(10.0) // imagine it's the data received from BLE device
}
This way anyone who's interested in receiving BLE data simply subscribes to that publisher.
Now, on the receiving side, assuming that you also need an ObservableObject for your SwiftUI view, you can write something along of the following:
class ViewModel: ObservableObject {
#Published var data: Int = 0
init(dataPublisher: AnyPublisher<Int, Never>) {
// automatically updates `data` when new values arrive
dataPublisher.assign(to: &$data)
}
}
If you don't use SwiftUI (I assumed you do, due to the ObservableObject conformance), then you can sink to the same publisher in order to receive the data.
Either SwiftUI, or UIKit, once you have a BLEManager instantiated somewhere, you can hide it from the rest of the app, and still provide the means to subscribe to the BLE data, by circulating the publisher. This also helps with the separation of concerns in the rest of the app.
If you plan to connect to a single device, I would keep this very simple, and let BLEManager pass this data to a delegate as it comes in. The delegate would then be responsible for deciding what to do with the data. Swift by Sundell has a nice introduction to delegates if you're not familiar with the pattern.
You'd make a separate object, a "DataManager" or whatever you'd like to call it, and it would be the delegate to the BLEManager (and would own the BLEManager). Whenever new data comes in, call methods like bleManager(_:didReceiveWeight:) or bleManager(_:didReceiveBiometricData:).
This will split up the process of getting data from the device from managing that data and performing computations, which I think is a worthy goal.
If this is a "real" project, and you're just starting, I highly recommend this pattern. It's well understood, there are dozens of blog posts about it going back many years. It's easy to understand and implement. The only pattern that's even easier to implement is to post Notifications, from BLEManager.
On the other hand, if this is more of an exploration, and you want to jump into the deep-end and the future of Swift (and limit yourself to iOS 15), you could also look at AsyncStream to emit new values as they occur into an AsyncSequence. That eventually will likely be the "Swifty" way to do this. But it's a much steeper learning curve, and the tools are not all fully developed yet, and no one really knows how to use it yet (not even Apple; it's just too new). If getting in on the ground floor of technology excites you, it's an area that everyone is exploring right now.
But if you just want this thing to work, or you want to focus on learning Swift right now rather than learning "the future of Swift," I'd use a delegate.

swift - Activate segue on a class from model file outside of a class? Instance member cannot be used on type

My app has a model file where I check my API. If the network check fails with a specific code (token inactive), I want the app to segue back to the log in page.
My challenge is that the API call is stored in my model file, not in a view controller class.
Is there a way to activate the segue from the model file outside of the actual class?
I tried to make a logout function on my TabBarController class and use this in my model to activate the segue:
class InitialTabBarController: UITabBarController {
...
func logoutFresh(){
performSegue(withIdentifier: "logoutFresh", sender: self)
}
}
Then in my model I added:
func pullAPIData(){
...
if httpResponse == 403 {
InitialTabBarController.logoutFresh()
}
}
I call the pullAPIData() function in another view controller class (one of the viewControllers in the tabBarController
However, this gives an error that:
Instance member 'logoutFresh' cannot be used on type 'InitialTabBarController'; did you mean to use a value of this type instead?
Is there a way check if the InitialTabBarController is active and then have it initiate the segue from the model file? Or a better way to handle this?
Basically, iOS uses the delegation pattern or the completion handler patters to solve this type of scenarios;
In delegation pattern, you set a weak variable in your model class that points to your viewcontroller. So that once your network call is completed, you can use this delegate to call a function in the view controller
In completion handler patter, you send a code block that should be executed after your api call is completed as follows:
func pullAPIData(completion: () -> Void){
//do your stuff
completion()
}
Your can refer the following posts to get a better understanding of delegates and completion handlers:
https://medium.com/#jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef
https://medium.com/#nimjea/completion-handler-in-swift-4-2-671f12d33178

What design pattern do I need for a single object which can receive data from different sources?

I'm trying to create some code in Swift that will enable the following functionality. There is a single class (call it the Datastore) that is the main interface into the code - this is what most users of the API will deal with most of the time.
This Datastore class is flexible. You could use it with data from a text file, or from Bluetooth data, or from data coming in over WiFi, etc. I call these providers of data. So, for example, you could have a BTProvider class which will take bluetooth data and send it to the Datastore when it arrives. One Datastore will never need to get data from multiple providers of data.
What design patterns or language tools help me to achieve this?
I thought about using protocols, but it feels backwards - a protocol defines which methods an object can respond to - but in this case, that will be the Datastore object - of which there is only one. In my head I feel like I want a reverse-protocol - something where I can guarantee "this object /calls/ these methods on another object". Then all the providers could implement this, and the Datastore could have a method "setProvider: ProviderOfData" where provider of data is the name of the reverse-protocol.
This would be a lot easier if I could poll the providers from the Datastore (then they could implement a protocol that defines methods like 'getMostRecentData', but due to the nature of it (asynchronous data receiving from WiFi, Bluetooth, etc.) this isn't possible and doesn't feel elegant - though if you have ideas I'm open to them!
This doesn't seem like this is the first time this would've been done, so I'm interested in how it's commonly done so I don't have to reinvent the wheel.
something where I can guarantee "this object /calls/ these methods on another object".
Seems like what you need is the Delegate-Pattern
You can have a DataStore (Swift is camel case) and this class can implement several delegate protocols. Example:
class DataStore {
// logic of the DataStore
}
You said that your app is mostly one class (the DataStore), so I am guessing you someone initialise your providers from it. I would suggest:
// Provider Factory
extension DataStore {
func makeBluetoothProvider() {
let btProvider = BTProvider()
btProvider.delegate = self
}
// creation of other providers, or you can create them all at once.
}
Not the important part, the DataStore is the delegate of your providers, that way, when they retrieve data, they can call the DataStore. I would have a protocol like this:
protocol ProviderDelegate: class {
func provider(_ provider: Provider, didFinishReceiving data: Data)
}
extension DataStore: ProviderDelegate {
func provider(_ provider: Provider, didFinishReceiving data: Data) {
// read data and do something with it...display it, save it, etc.
}
}
Provider would be a general class for all the providers, probably with network requests or similar basic data. One example would be:
class Provider {
var delegate: ProviderDelegate
var data: Data
}
class BTProvider: Provider {
// logic only for bluetooth provider
}
Depending on how differently your providers behave, you can have a delegate protocol for each and an extension of DataStore implementing each of this protocols. That only if the behaviours are too different from each other, which I don't think.
UPDATE ADDRESSING COMMENT: Protocol can provide code
A protocol can provide code, let me show you an example:
protocol Provider {
weak var delegate: ProviderDelegate { get set }
func fetchData(with url: URL)
func processData(data: Data)
}
extension Provider {
func processData(data: Data) {
// do some processing that all providers have to do equally
// probably also call delegate to tell DataStore it is ready
}
}
Your provider class would implement the method and can choose to implement a new processData or just use the default one. If it implements it, there is no need to call for override, you would just no longer have access to the protocol method. You provider can look like this:
class BTProvider: Provider {
weak var delegate: Provider?
func fetchData(with url: URL) {
// do some logic to fetch data for this provider
processData(data: whateverWasFetched)
}
}

How to expose different functionality of a single type to different modules?

I'm working on a small 2-player card game app for iOS. For now, I've separated this app into 3 modules.
UI
Game logic
Networking
I have two pubic protocols in the networking module:
public protocol ConnectionManager {
init(name: String)
var peers: Property<[String]> { get }
func invitePeer(at index: Int)
func respond(accept: Bool)
var invitation: Signal<String, NoError> { get }
var response: Signal<Bool, NoError> { get }
}
public protocol MessageTransmission {
func send(_ data: Data)
var data: Signal<Data, NoError> { get }
}
and there is actually only one concrete implementation which conforms to both two protocols. Lets' say:
class Foo: ConnectionManager, MessageTransmission {
// ... codes omitted
}
The UI module receives a name from the user and uses it to initialize a Foo. Then it displays the nearby players according to the peers property. When a user commits an invitation to start a new game, UI module forwards this request to the ConnectionManager and the ConnectionManager handles those dirty works.
For the game logic module, it only cares about message transmission, but the transmission depends on the previous "invite-respond" step because we need a target to exchange message with. (the target related concepts are encapsulated, so the game logic only knows there is a thing that it can send message to and receive message from.)
My thought is: once a session is established, i.e., once an invitation is responded with true, the UI module initializes a game logic thing (maybe an instance of a type Game) and passes the ConnectionManager (although UI module initializes an instance of type Foo, it stores that instance as of type ConnectionManager) to it. But the problem is, the ConnectionManager has nothing to do with MessageTransmission when looked from the outside even if they're implemented by a single type internally.
I can do a force case at either side, but it looks tricky.
I can combine those two protocols, but then the UI module and the game logic module interacts with a surplus interface (interface that has more features than needed).
Which path should I take? or is there any better way to do this?
ps: The Foo can only be initialized by UI module because it's the only one who knows about the name. And it's also necessary to pass the same instance to the game logic module because there are some internal states (the target related thing) which is modified in the previous interaction with UI module and effects the later interaction with game logic module. I can't think of another way to do this.
As I understood from your explanation you need to pass MessageTransmition implementation to game logic but do not want to pass ConnectionManager.
As soon as instance of MessageTransmission depends on result of invitation (or accepting invite) made by ConnectionManager you need to build object conformed to MessageTransmition from within ConnectionManager and pass to game logic. Yes, that means that you couple these two entities but they will have clean responsibilities in this case.
Consider this kind of protocol:
protocol ConnectionManager {
func respond(accept: Bool) -> Signal<MessageTransmition, Error>
}
respond() method accepts invitation and builds MessageTransmission conforming object on success or returns error if accepting failed or if invitation declined. This object you can pass to game logic.