How to synchronize multiple controls? - swift

I am looking for pattern in swift to synchronize the state of multiple controls. Say I have NSSegmentedControl in the view, NSMenuItems with checkmarks and NSSegmentedControl in touch bar. Each of them is sending change to model. But how to synchronize with other two? I can use delegates but it looks like the complexity will grow with number of synchronizable items. Is there a common pattern to approach this problem? I would think of something like subscription to change, so all 3 controls can subscribe to it and receive notification when a change occurs, then use it's own transformation of data to pass to the control. Wonder if there is some mechanism for that.

There is a mechanism for that, it's the notification model. I imagine you want the view controller to listen for changes and update its controls--not have each control listen for changes and update itself (which is also possible). The basic approach would be to add a notification observer to the view controller (or whatever object you want to be the observer)...
NotificationCenter.default.addObserver(self, selector: #selector(nHandler(_:)), name: NSNotification.Name(rawValue: "someControlDidChange"), object: segmentedControl)
...and a handler to take action on that notification...
#objc func nHandler(_ notification: Notification) {
guard let control = notification.object as? UISegmentedControl else {
return
}
// determine which control posted the notification
// read the control's new value
// update the other controls
}
Then as a control updates its values, it posts notifications...
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "someControlDidChange"), object: self)
The object here can be self if this notification is posted from within the control (i.e. it's subclassed) or the instance of the control itself (i.e. someSegmentedControl).
The notification observer and the notification poster allow you to include an optional object, as you see in my examples. You should be passing some data within these notifications to allow the observer to determine what object is posting the notification and what value that object has been updated to. Then in the handler of the notification observer, you unwrap that object to get that data.
I have yet to benchmark the costs of posting notifications; I really have no idea how expensive it is. If the delegate approach is too messy and you've written off property observers, the notification model is likely the way to go.

Related

Send data between controllers with Notification and observers or protocols

In my app, I don't use Storyboard, and I have a coordinate class to start with the first view controller. And also I have a web socket manager class that connected to the coordinate class. So for example if the fifth View controller in hierarchy want to change something in the web socket class, I use event handlers or protocols to send data through each view controllers (for example here the forth VC get data and send it to the third, and then second and .. ) all the way down to the coordinate and it send it to the web socket manager.
I can use another option to use Notification and observers to send data directly from the fifth view controller in hierarchy directly to websocket manager manager and when I get the call back, use Notification and observers again to send data from web socket to the fifth view controller in hierarchy.
I don't know which way is the most effective way in term of use less CPU and RAM and also most reliable way. Could you please tell me which way is better here?
Thanks
IMHO I don’t think it’s a good use case for Notifications, as you are just passing an event from one object to another. Notifications are more useful when you need to broadcast an event that multiple objects will observe.
On the other hand, if you inject the event handler to all view controllers down the hierarchy, that would be a good solution if all of them depend on the Websocket manager. But if it’s only used by the fifth view controller down, perhaps it is not a good idea either, as you are injecting an irrelevant dependency to several objects and that’s not scalable.
If that is the case, I think your best choice here would be to have your Websocket Manager as a singleton and inject it directly into the view controller that requires it. I’m not a big fan of singletons, to be honest, but they are handy in cases like this. You just need to be careful of how you use it if it holds any state that can be changed by multiple objects concurrently.
Here is an example of how you could use this approach:
class WebsocketManager {
static let shared = WebsocketManager()
private init() {
}
}
class ViewController: UIViewController {
let websocketManager: WebsocketManager
init(websocketManager: WebsocketManager = .shared) {
self.websocketManager = websocketManager
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Hope it helps.

How to update a value after it is loaded inViewDidLoad in another file (Swift)?

I have created all my views programatically and to the UIView I have created an extension. This extension is present in a different file called App+Extensions.swift
extension UIView {
func setupParentView() -> UIView {
//...
}
}
The setupParentView() gives me a view with my navbar and background colour. As the design is same everywhere I have used this function. This function is called everywhere in viewDidLoad. So, now in this function the navabar consists of points which need to updated every time the user has purchased/spent it.
So, now as this method is only called in viewDidLoad the function is not called again and the point value do not change in the navigation bar. So, how can I update the point value every time it is changed? I thought of using Combine, but this app will be available for iOS 12 users as well, and I am not using RxSwift, so any help?
Well ViewDidLoad won't get refreshed because your view is now in the stack hierarchy. Some override methods that do get called which have a relation to the view are viewWillAppear, viewDidDissapear, and viewDidAppear.
If your view is going back in forth and the point button needs to be updated everytime your view re-appears consider putting the "refresh" point number in one of the above methods.
Note if your sending info back and forth between two views its also best maybe implement a delegation pattern or an observer.
Use notifications.
In other words, have points be a property of a global object that lives off in "data space" as a singleton. Now just use a property observer so that whenever anyone changes that object's points property, it emits a notification through the NotificationCenter.
That way, any view or view controller that needs to update whenever the points property changes just has to register for that notification when it comes into existence.
[Basically, this is the mechanism, or one of the mechanisms, that you would be replacing or even using directly if you were using Combine or RxSwift.]
Create a custom view called NavBarView
This NavBarView has a property called point
var point = 0 { didSet { updateView() } }
You want to avoid singleton, single view object, so that not everything is coupled together.
You don't need RxSwift or notification to do this.

Where is the right place to remove notification observers in Swift 2?

I have this code, taken from this answer: https://stackoverflow.com/a/29099066/406322
extension NSNotificationCenter {
func setObserver(observer: AnyObject, selector: Selector, name: String?, object: AnyObject?) {
NSNotificationCenter.defaultCenter().removeObserver(observer, name: name, object: object)
NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: name, object: object)
}
}
Now, in my view controller, I am setting my observers in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
setObservers()
}
func setObservers() {
NSNotificationCenter.defaultCenter().setObserver(self, selector: #selector(BaseController.handleComment(_:)), name: "newComment", object: nil)
}
However, even with using this extension, where the observer is removed before getting added, each time I exit the view controller, and return to it, I get multiple notifications (one extra each time).
How is this possible?
If you need this setObserver extension, you are very likely doing something wrong. You should be able to balance your registration and removal easily. If you can't, your notification management is very likely too complicated or in the wrong place.
Typically the correct place to add observations is in viewWillAppear (or viewDidAppear, either is fine), and remove them in viewDidDisappear (or viewWillDisappear). This ensures that you do not receive notifications while you are offscreen, even if the view controller still exists (which is common).
If your view controller requires that it receive notifications while it is offscreen, then you have a design problem. View controllers should only manage onscreen views. If they're doing anything else, you have put too much of the model into the controller.
As #rmaddy notes, your specific problem is likely that you have two instances of this view controller. That's may be fine or it might be a mistake (it depends on how the view controller works). But if you balance adding and removing your registration when going on and offscreen, that part will be fine.

When to observe model data when using Storyboards

Let's assume I have an address book App.
So from the address list I now push to a detail view like so:
User taps on cell in the master list
Segue showDetail is invoked
In prepareForSegue: I set the model object contact of my ContactDetailViewController
viewWillAppear I add an observer for self.contact
So now when the contact object changes in the background, the detail view will automatically be updated.
In viewWillDisappear I remove this observer (I guess it's clean because it is symetrical).
However, viewWillAppear: is called after I set the contact. So should I setup the KVO differently or simply call my updateView function in viewWillAppear which is a bit stupid because I want to get rid of those calls with KVO in the first place.
When you call addObserver:... you want to include the option NSKeyValueObservingOptionInitial. This will cause KVO to send the equivalent of a didChangeValueForKey: notification (i.e. an "Initial" notification) in the same call that adds the observation. This should cause your view to update at that time.

iPhone Dev - Delegate or event?

In many situations, such as making the keyboard go away when the use clicks done, there are two options: set the text field's delegate to self, and adopt the UITextFieldDelegate protocol, and then use the method
- (BOOL)textFieldShouldReturn:(UITextField *)textField;
to resignFirstResponder and return YES. But you can also
addTarget:self
action:#selector(myMethod:)
forControlEvent:UIControlEventDidEndOnExit];
or something like that, using the did end on exit event, and then in the method, [sender resignFirstResponder]. So what is the best option in situations like these: the delegate, or the event?
The quick rule of thumb is that delegates are supposed to answer the question of "should I?" on behalf of the object they are a delegate for. Events, on the other hand, are broadcast afterward to let listeners know that something has happened.
In your case, while you could call [sender resignFirstResponder] in response to the event, you're mixing metaphors by doing this. Your delegate should have already made the decision to hide the keyboard (or not) and the event being broadcast is merely to let all the other components know that they keyboard hid.
If you are going to be paired with one other class, where the real type of that class may vary, then it makes a lot of sense to formalize that pairing into a protocol and a delegate arrangement.
If the information you want to send is targeted at a broader set of objects, then it starts to make more sense to use notifications - though now you have somewhat obscured what information is being passed by the notification as there's no central definition for what to expect.
Both are about an equal load to work with - with a delegate you have to set yourself and then remember to unset yourself before you are deallocated. You have to do the same thing with notifications, remember to start listening and then unsubscribe before you are deallocated.
Also, you should try as much as possible to make sure you send notifications out on the main thread as the notices get sent on the same thread they started from. Same goes for delegate methods, it's not very kind to call a delegate method from some other mystery thread!
The delegate makes your objects more reusable they are an adapter that lets any object interact with the defined behaviors of that object and use the object. I would say delegates should be adopted by the object responsible for keeping the state of and defining behavior to actions that will occur in the object that it is using. Events should be used for any other objects that are intersted in particular action that the object that has the protocol does (so objects not responsible for keeping the state of the object that defines the protocol).
For example: A view controller using a textfield will adopt its protocol to dismiss the keyboard and any other behaviors that can occur for a textfield, maybe another controller will do some animation when the keyboard is dismissed, so it will register to the textfield as an event in order to receieve the event of the keyboard being dismissed.