Should I use a delegate method or UINotificationCenter - ios

So right now I have a rootViewController which has a UIPageViewController as its only subview, rootViewController is basically a wrapper class for UIPageViewController. Now I want to disable scroll for the UIPageViewController from ViewControllerA (which is owned by rootViewController).
Right now I have implemented a delegate method (created by ViewControllerA) for the rootViewController which tells the UIPageViewController it has to stop scrolling depending on information given by ViewControllerA. But for the ViewControllerA to be able to call the delegate method implemented by rootViewController would require the rootViewController to be an instance variable of ViewControllerA.
Would this be the best practice? Would using NSNotificationCenter be a better choice? Or are none of these methods the best choice?

The best practice would be to use a delegate or a callback property:
final class ViewControllerA: UIViewController
{
var callback: (ViewControllerA -> ())?
func somethingHappened()
{
callback?(self)
}
}
If you're using this style, be sure to avoid a reference cycle:
let viewControllerA = ViewControllerA()
viewControllerA.callback = { [weak self] _ in ... }
If you're using a delegate, avoid a reference cycle by using weak var:
weak var delegate: ViewControllerADelegate?
These styles are preferable to NSNotificationCenter because they're more type-safe, readable, and flexible.

There are many ways to solve your problem. If you can set add a rootViewController variable to your ViewControllerA and able to set it during creation of ViewControllerA then that would be the quickest way to call it. I normally use the NSNotificationCenter when there are multiple threads involved. So if I have an async task running , like HTTP calls, and it would require the application to react accordingly depending on the outcome the using a NSNotificationCenter is a good choice as it would easily send the message app-wide and you just need to add an observer depending on the message.

Related

How to prevent timer reset using pushViewController method?

I'm trying to keep a timer running even if I switch view controllers. I played around with the Singleton architecture, but I don't quite get it. Pushing a new view controller seems a little easier, but when I call the below method, the view controller that is pushed is blank (doesn't look like the view controller that I created in Storyboards). The timer view controller that I'm trying to push is also the second view controller, if that changes anything.
#objc func timerPressed() {
let timerVC = TimerViewController()
navigationController?.pushViewController(timerVC, animated: true)
}
You need to load it from storyboard
let vc = self.storyboard!.instantiateViewController(withIdentifier: "VCName") as! TimerViewController
self.navigationController?.pushViewController(timerVC, animated: true)
Not sure if your problem is that your controller is blank or that the timer resets. Anyway, in case that you want to keep the time in the memory and not deallocate upon navigating somewhere else I recommend you this.
Create some kind of Constants class which will have a shared param inside.
It could look like this:
class AppConstants {
static let shared = AppConstants()
var timer: Timer?
}
And do whatever you were doing with the timer here accessing it via the shared param.
AppConstants.shared.timer ...
There are different parts to your question. Sh_Khan told you what was wrong with the way you were loading your view controller (simply invoking a view controller’s init method does not load it’s view hierarchy. Typically you will define your view controller’s views in a storyboard, so you need to instantiate it from that storyboard.)
That doesn’t answer the question of how to manage a timer however. A singleton is a good way to go if you want your timer to be global instead of being tied to a particular view controller.
Post the code that you used to create your singleton and we can help you with that.
Edit: Updated to give the TimeManager a delegate:
The idea is pretty simple. Something like this:
protocol TimeManagerDelegate {
func timerDidFire()
}
class TimerManager {
static let sharedTimerManager = TimerManager()
weak var delegate: TimeManagerDelegate?
//methods/vars to manage a shared timer.
func handleTimer(timer: Timer) {
//Put your housekeeping code to manage the timer here
//Now tell our delegate (if any) that the timer has updated.
//Note the "optional chaining" syntax with the `?`. That means that
//If `delegate` == nil, it doesn't do anything.
delegate?.timerDidFire() //Send a message to the delegate, if there is one.
}
}
And then in your view controller:
//Declare that the view controller conforms to the TimeManagerDelegate protocol
class SomeViewController: UIViewController, TimeManagerDelegate {
//This is the function that gets called on the current delegate
func timerDidFire() {
//Update my clock label (or whatever I need to do in response to a timer update.)
}
override func viewWillAppear() {
super.viewWillAppear()
//Since this view controller is appearing, make it the TimeManager's delegate.
sharedTimerManager.delegate = self
}

Call method on all the existing instances of UIViewController

I want to be able to allow the user to change some properties of the graphical user interface immediately through all the app. To achieve that, I thought about creating a protocol like
protocol MyProtocol {
func changeProperties()
}
so each UIViewController can change those properties in its own way and then call this method in all the current instantiated controllers.
However, I don't know if this is possible. My first idea was to access the most root controller of the app, and then iterate through all the child recursively calling the method. Something like
func updatePropertiesFrom(_ vc: UIViewController) {
for child in vc.childViewControllers {
if let target = child as? MyProtocol {
target.changeProperties()
}
updatePropertiesFrom(child)
}
}
let appRootController = ...
updatePropertiesFrom(appRootController)
I don't know how to get that appRootController and I would like to know if there's any more elegant way for doing this. Thanks.
You can use NotificationCenter. E.g. define a notification name:
extension Notification.Name {
static let changeViewProperties = Notification.Name(Bundle.main.bundleIdentifier! + ".changeViewProperties")
}
And your various view controllers can then register to be notified when this notification is posted:
NotificationCenter.default.addObserver(forName: .changeViewProperties, object: nil, queue: .main) { _ in
// do something here
}
(If you're supporting iOS versions prior to iOS 9, make sure to remove your observer in deinit.)
And to post the notification when you want to initiate the change:
NotificationCenter.default.post(name: .changeViewProperties, object: nil)
You can access the app's root controller with:
UIApplication.shared.keyWindow.rootViewController
Of course this assumes the current key window is the one with your app's root controller.
You could also access the window property of your app delegate and get the rootViewController from that window.
Your general approach is valid. There is no other "more elegant" way to walk your app's current view controllers. Though you should also traverse presented view controllers as well as the child view controllers.
However, a simpler solution might be to post a notification using NotificationCenter and have all of your view controllers respond accordingly.
You could do this with a central dispatcher and having each UIViewController register with it.
Example:
class MyDispatcher{
static let sharedInstance = MyDispatcher()
var listeners: [MyProtocol]()
private init() {}
public func dispatch(){
for listener in listeners{
listener.changeProperties()
}
}
}
From each UIViewController you want to update the properties, you call MyDispatcher.sharedInstance.listeners.append(self). When you want to update the properties, you call MyDispatcher.sharedInstance.dispatch().

What's the advantage of using delegate to transfer values between viewControllers?

I have two viewControllers, vcA and vcB.
When I want to transfer values to vcA from vcB, I use delegate like this:
//vcB.swift
protocol TransferDelegate {
func transferValue(msg: String)
}
var delegate: TransferDelegate!
//vcA.swift
class vcA: UIViewController {
}
extension vcA: TransferDelegate {
func transferValue(msg: String){
//xxxxxxx
}
}
When vcA push vcB,then vcb.delegate = self
When vcB want to transfer values, then delegate.transferValue(msg)
My question is what if I transfer vcA's instance self to vcB instead of set delegate like this:
//vcB.swift
class vcB: UIViewController {
var vca: vcA!
func transferValues(msg: String) {
vca.msg = msg
}
}
//vcA.swift
vcb.vca = self
I have tried both two methods, both of them can work.
So why we use the first method rather than the second?
What's the difference between then or what's the advantage of the delegate?
The advantage of using a delegate is that vcB doesn't have (or need) any knowledge of the specific object type acting as the delegate. And this in turn makes it much simpler for vcB to be used by more than just vcA. It makes your classes less coupled and much more flexible.
Imagine of a UITableView only worked with a specific type of data source and delegate instead of allowing for anything that conforms to the proper protocols to act as a data source and delegate. This is no different.
protocol TransferDelegate {
func transferValue(msg: String)
}
The good thing is you can reuse this delegate method in other vcs.
Now you have vcA and vcB to do things. Maybe in the future you need something similar in other vcs.
See when you are using delegate you can only access that particular method of that particular viewController. But if you save the whole instance in the vc2 you can access or change any of the properties or functions of that class. Might be which you don't want to allow the vc2 to access.

How can I create an instance of a custom model with multiple viewcontrollers in Xcode with Swift 4?

I'm new to Swift and I'm sure this question is pretty basic and has been asked and answered before.
I am not using storyBoard. My main viewController is created from AppDelegate via code.
I have:
a custom class defined in a model.swift file
a main viewController (from AppDelegate) that I am using as a container
3 additional viewcontrollers as subviews of the main (not each other)
all 3 subviews are displayed simultaneously each covering 1/3 of the screen (no segues)
each viewcontroller is in a separate .swift file
I want to create an instance of my custom class in the main viewController and have all 3 of the subviews be able to reference that instance.
Each of the subview view controllers need to be able to get/set instance variables and the other subviews need to be made aware of those changes.
I think I will need to use notifications to communicate the changes to the multiple subviews - but I haven't even begun to try and figure that out yet.
If this has been asked and answered before - could someone please either provide a link - or provide me with the right search terms so that I'm able to find the answer? The only found answers I've found that come close are to use segues to pass the data back and forth.
You can use delegate pattern. Below code is assuming that you are using MVVM pattern. (It is very similar for VIPER/ReSwift patterns also)
protocol DataChangedDelegate {
func refreshData()
}
// ViewModel for FirstViewController
class FirstViewModel {
var delegate: DataChangedDelegate?
var data: Any {
didSet {
delegate?.refreshData()
}
}
//rest of the things
}
//similarly other two view models will have a delegate and on data change will call the refresh method
And your view controllers should adopt this protocol
class FirstViewController: UIViewController, DataChangedDelegate {
//view controller code
//delegate code
func refreshDate() {
//tableView.reloadDate()
//collectionView.reloadDate()
//lableView.text = viewModel.data()
}
}
And where ever you create a viewControllers and add as subView, you have to set the delegate of viewModel.
let firstViewController: FirstViewController = createFirstViewController()
let firstViewModel = FirstViewModel()
firstViewModel.delegate = firstViewController
firstViewController.viewModel = firstViewModel
mainViewController.addSubView(firstViewController.view)
Similarly for all other view controllers.
Here's how I would do it:
Create a singleton class.
Configure the singleton's properties in the the main ViewController.
Use didSet to post a Notification.
Add a listener for that Notification in your additional ViewControllers.

Swift, how to tell a controller that another controller is its delegate

I'm learning Swift and I'm studying the delegation pattern.
I think I understand exactly what is delegation and how it works, but I have a question.
I have a situation where Controller A is the delegate for Controller B.
In controller B I define a delegate protocol.
In controller B I set a variable delegate (optional)
In controller B I send message when something happens to the delegate
Controller A must adopt method of my protocol to become a delegate
I cannot understand if every delegate controller (in this case A) listens for messages sent by controller B or If I have to tell to controller B that A is now his delegate.
I notice that someone use this code (in controller A)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Example" {
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddItemViewController
controller.delegate = self
}
}
Is this the only way to tell a delegator who is his delegate?
I believe, you need to tell a deligator who is its delegate upon creation of that it. Now, the delegator can be created programatically or through storyboard. So, based on that you have two options, you can tell it who is its delegator programatically like you showed in the code or from IB.
The key here is upon creation. Let's me explain myself. Take the case of a UIView. Say, you want a Custom UIView object(CustomView). So, you drag and drop a UIView in your View Controller and in the identity inspector, you assign its class as of your CustomView's class. So, basically, as soon as the controller is created, your custom view will also be created. Now, you can either say it that the View Controller in which it is created is its delegate or You can go to the IB and connect the view's delegate to the View Controller.
Now, let's assume that you wanted the custom view to be created in your ViewController programatically. In that case, you would probably call the -initWithFrame: method to create the view and upon creation you tell that delegator that who is its delegate like-
myCustomView.delegate = self;
same goes with a View Controller.
controller.delegate = self;
So, basically to tell a delegator who is its delegate, you first need that delegator to be created. At least, that's what I think.
I think one of the best example of delegation is UITableView.
Whenever you want the control of various properties of a tableView e.g. rowHeight etc, you set your controller to be the delegate of your tableview. To set the delegate of your tableView you need to have tableView created obviously as pointed out by #natasha.
So in your case, you can set delegate of your delegator when you create it or when you find a need for the controller to be delegate of your delegator but you definitely need your delegator to be present to set its property.
You can set your controller as delegate at any time when you require control.
I'm sure you want your UIViewController to act like described, but here is a simpler example how to use the delegation pattern with custom classes:
protocol ControllerBDelegate: class {
func somethingHappendInControllerB(value: String)
/* not optional here and passes a value from B to A*/
/* forces you to implement the function */
}
class ControllerB {
var delegate: ControllerBDelegate?
private func someFunctionThatDoSomethingWhenThisControllerIsAlive() {
/* did some magic here and now I want to tell it to my delegate */
self.delegate?.somethingHappendInControllerB(value: "hey there, I'm a magician")
}
func doSomething() {
/* do something here */
self.someFunctionThatDoSomethingWhenThisControllerIsAlive()
/* call the function so the magic can really happen in this example */
}
}
class ControllerA: ControllerBDelegate {
let controllerB = ControllerB()
init() {
self.controllerB.delegate = self /* lets say we add here our delegate*/
self.controllerB.doSomething() /* tell your controller B to do something */
}
func somethingHappendInControllerB(value: String) {
print(value) /* should print "hey there, I'm a magician" */
}
}
I wrote the code from my mind and not testet it yet, but you should get the idea how to use such a pattern.

Resources