how can i update other controllers' UI in swift? - ios

I have several controllers in my app. When my app call one function in one controller, I want to update other controllers' UI. How can I achieve that?
class FirstViewController: UIViewController {
func updateUI {...}
}
class SecondViewController: UIViewController {
func updateUI {...}
}
class ThirdViewController: UIViewController{
func updateAllUI {...} # I want call FirstViewController().updateUI() and SecondViewController().updateUI() here
}
But FirstViewController() means I create a new FirstViewController which is I don't want, and FirstViewController has already been created. So how can I call all other controllers' updateUI() in updateAllUI()
Please help, Thank you!

It's usually a pretty bad practice to have view controllers communicate directly. I would use a NSNotification to communicate between view controllers. It's convention to have the name of your notification start with a capital letter and end with the word "Notification".
class FirstViewController: UIViewController {
func updateUI {...}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI", name:"TimeToUpdateTheUINotificaiton", object: nil)
}
override deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
class SecondViewController: UIViewController {
func updateUI {...}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateUI", name:"TimeToUpdateTheUINotificaiton", object: nil)
}
override deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
class ThirdViewController: UIViewController{
func updateAllUI {
NSNotificationCenter.defaultCenter().postNotificationName("TimeToUpdateTheUINotificaiton", object: nil)
}
}

Eliminate the parentheses. You don't use them when calling a class function.
FirstViewController.updateUI()
That said.. what you're trying to do is very weird to say the least. You shouldn't use class functions to modify properties of instances of a class. If you have both View controllers on screen at the same time, you should be using a parent controller to command both of them to update their UI when you need to.
If they're not both on screen at the same time, you don't really need to update both UIs.

If you want all your view controllers to react[in this case - updating ui] to an action in one of your view controller, you try posting a Notification.. That is how you broadcast a message in iOS
You post notification from the view controller when your desired action is complete.
All other View controllers would subscribe to that notification and react to it by updating their UI when it is posted.
The below post quickly shows an example to post/observe notifications..
https://stackoverflow.com/a/2677015/4236572
http://www.idev101.com/code/Cocoa/Notifications.html might also be helpful

Related

Applying same logic on all the ViewControllers

The following implementation works on a single ViewController. However, I want to apply same logic all the other ViewControllers as well.
Rather than repeating(copy-paste) the same code again and again in each of the ViewController, what would be a good approach?
ViewControllerA
override func viewDidLoad() {
super.viewDidLoad()
//common
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func willEnterForeground() {
if (expired()){
navigationController?.popToRootViewController(animated: true)
}
}
Subclasses. That's what subclasses are for. This absolutely should not be applied to all UIViewControllers, since many of those are provided by Apple, and it would be very bad if you modified their viewDidLoad this way. But for every one of your view controllers, you just need to add this behavior as a superclass.
To create a subclass like this, you'd make an intermediate view controller type:
class ForegroundPoppingViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func willEnterForeground() {
if (expired()){
navigationController?.popToRootViewController(animated: true)
}
}
}
And then for all the view controller you want to have this behavior, you would subclass:
class MyViewController: ForegroundPoppingViewController { ... }
If MyViewController has its own viewDidLoad, you'd chain that to its superclass, just like in any other subclass:
class MyViewController: ForegroundPoppingViewController {
override func viewDidLoad() {
super.viewDidLoad()
// ... any other behaviors ...
}
}
This would not apply to Apple view controllers, such as UIDocumentBrowserViewController, but it shouldn't. That may not give valid behavior. You would need to decide on the proper behavior depending on what view controller you're presenting.
Also, as a general rule, you should observe notifications in viewDidAppear (or willAppear) and remove notification observations in viewWillDisappear (or didDisappear). You usually do not want notifications firing on view controllers that are not currently onscreen.
That said, for this particular problem, I probably would recommend moving this logic to a "presenter" type coordinator, or even a UINavigationController subclass. As written, this may call popToRootViewController many times (since many view controllers may exist at the same time), which may lead to animation glitches.
But for the general question of how to add functionality to view controllers, this is how you would do it.

Get a notification with viewWillAppear - in another VC?

Say you have
var someVC: UIViewController
is it possible to essentially do the following, somehow?
get a notification when {
someVC has a viewWillAppear
self.#selector(wow)
}
#objc func wow() {
print("we spied on that view controller, and it just willAppeared"
}
Is that possible ?
(Or maybe on didLayoutSubviews ?)
(I realize, obviously, you can do this by adding a line of code to the UIViewController in question. That's obvious. I'm asking if we can "add on" to it from elsewhere.)
If I understand your question correctly, you want ViewController B to receive a notification when viewWillAppear is called in ViewController A? You could do this through the Notifications framework. Keep in mind that both VC's have to be loaded for one to receive a notification.
Alternatively, if the two VC's are on the screen at the same time, then I'd recommend a delegate pattern - have VC A tell an overarcing controller class that it's viewWillAppear has been called, and this overarcing controller will then inform ViewController B.
To do this using Notifications:
(This is from memory, so please excuse typos)
class TestClassA: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// To improve this code, you'd pull out the Notification name and perhaps put it into an extension, instead of hardcoding it here and elsewhere.
NotificationCenter.default.post(Notification.init(name: Notification.Name.init(rawValue: "viewControllerAppeared")))
}
}
class TestClassB: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(viewControllerAppeared(notification:)), name: Notification.Name.init(rawValue: "viewControllerAppeared"), object: nil)
}
#objc func viewControllerAppeared(notification: NSNotification) {
print("other viewcontroller appeared")
}
}
Documentation

UIViewController base class - is it good or not

I have to apply single logic for gesture recongnizer to all UIViewController elements in my app.
I thought of creating a base class. However, as far as I know we should add extra layer to inheritance chain only in purpose of strong necessity.
What is the most efficient way to add functionality I mentioned above to my project, without copy-pasting code to each of mine controllers?
You can use extension of UIViewController
extension UIViewController
{
// write code that has to be used in all view controllers
func applyTheRequiredPropertiesToGestureRecognizer(gr:UIGestureRecognizer)
{
// do whatever you want to do
}
}
Later while using
class SomeViewController:UIViewController{
let gestureRecog = UIGestureRecognizer()
//call the method
func someMethod()
{
self.applyTheRequiredPropertiesToGestureRecognizer(gestureRecog)
}
}
Make a baseviewController and do all the functions which are generic or used at most of the view controllers like an alert.
class BaseViewController: UIViewController {
func yourGestureRecogniser() {
// do the process
}
}
Then inherit the base view to other view controllers like this,
class ViewController1: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
yourGestureRecogniser()
}
}
class ViewController2: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
yourGestureRecogniser()
}
}
customize the gesture function as per your needs.
Using base controller is certainly a common approach.
A common base controller often becomes a dumping ground for functionality that is used in multiple-but-not-all controllers, so it become overkill.
I also followed this pattern in my project but later it becomes complicated for me when i start using other controllers like UITableViewController, UICollectionViewController etc.
Agreed with Sulthan's comment: using a category and turn it on explicitly on every controller would definitely be a better option.

swift 3 call function from parent ViewController

I have a ViewController, this view container has a class which creates 2 container views, and adds a table to the first container and a HashtagPicker for the second.
The hashTagPicker has a function which is called whenever a change to the selected hashTags happens.
question: i want to call a update table function whenever a tag is changed. How can i call a function from the hashtagclass which is defined in the class that contains the containers?
I personally like the delegate approach over notifications - the latter solution almost always leads to confusing architecture. Sadly, the example for the delegate approach, which is also the accepted answer, is even worse - it basically opens an opportunity for memory leaks. I'll explain. In the accepted solution, ParentView is holding a strong reference to HashtagPicker and, in turn, HastagPicker is holding a strong reference to ParentView, this creates a retain cycle and means neither of the controllers will be picked up by ARC and be deinitialized. So, if you are, for example, presenting ParentView from some other view and you keep going to ParentView and back, you will keep spawning new instances of ParentView (and HashtagPicker) with old ones still occupying memory.
Now, how this should have been done. I'll use exactly the same names as in the accepted answer.
The protocol should be defined like so:
// note the ": class" part
protocol HashTagPickerDelegate: class {
func picked(hashtag: String)
}
If we specify class, it means the protocol can only be used on classes. This will allow use to create weak reference, which otherwise would have been impossible.
class HashtagPicker: UIViewController {
// if HashTagPickerDelegate wouldn't be limited to class,
// we couldn't have made a weak reference here!
weak var delegate: HashTagPickerDelegate?
// at some point, you call the delegate, it can be anywhere, this is just an example
#IBAction func tappedHashtag(_ sender: Any) {
delegate?.picked(hashtag: "bla")
}
}
Now we have a weak reference to delegate, so there is not retain cycle and ARC can clean up everything nicely!
I'll throw in the rest of the code to have this as a complete answer:
class ParentView: UIViewController {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// we are presenting the nested controller
if segue.identifier == "SegueHastagPickerContainer",
let destinationController = segue.destination as? HashtagPicker {
destinationController.delegate = self
}
}
}
extension ParentView: HashTagPickerDelegate {
func picked(hashtag: String) {
// we just got info from the child controller, do something with it!
}
}
You can use delegates as mentioned in above answer. Or you can use notifications. So here is a solution using notifications.
First of all register a notification in your parent viewController's viewDidLoad like this
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ParentViewController.someActionToBePerformed), name: "myNotification", object: nil)
Create a function in your parent viewController named same as above so it will be like
func someActionToBePerformed () {
// this will be called when hashTag is changed
// do something when hashTag is changed
}
Now you can simply post notification from your Hashtag viewController. When you want like this.
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "myNotification"), object: nil)
You can use this (no notification, no delegate)
func exitButtonTapped() {
if let pdfVC : YourParnetViewController = self.parent as? YourParnetViewController {
pdfVC.removeBlurEffect()
self.removeFromParentViewController()
self.view.removeFromSuperview()
}
}

Swift delegates isn't working

I'm trying to use delegates between two controllers but it doesn't work as it should be
protocol saveDelegate: class {
func saveSite()
}
class AuditSiteViewController: UIViewController {
weak var delegate: saveDelegate?
#IBAction func saveButton(sender: UIBarButtonItem) {
print("Saved")
delegate?.saveSite()
}
}
class AuditDetailsViewController: UIViewController, saveDelegate {
var mainView: AuditSiteViewController?
override func viewDidLoad() {
super.viewDidLoad()
mainView?.delegate = self
}
func saveSite() {
print("delegated")
}
}
it should print delegated but it only prints "saved"?
You can use delegate, but have you debug and check that mainView is the correct instance?
My suggestion in this case would be to use NSNotification instead. You can add a observer in your viewDidLoad and post a notification on the saveButton()
class AuditDetailsViewController: UIViewController {
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(AuditDetailsViewController.saveSite), name: "SaveSite", object: nil)
}
}
class AuditSiteViewController: UIViewController {
#IBAction func saveButton(sender: UIBarButtonItem) {
NSNotificationCenter.defaultCenter().postNotificationName("SaveSite", object: nil)
}
}
In my opinion there are only two reasons possible:
First:
In the moment of calling mainView?.delegate = self mainView is nil. Then the delegate isn't assigned. Set a breakpoint there and you will see it.
Second:
In the moment of calling delegate?.saveSite() the delegate is nil. That may be because your instance of AuditDetailsViewController was deinit by you or system. System removes the instance if noone holds a strong reference to it anymore. Implement the deinit method and set a breakpoint in it to see when it happens.
Looks like the mainView is nil when you set the delegate. Try to set the reference when you instantiate the detail view controller.
Anyway, maybe what you want is to delegate the saving action from the detailViewController to the AuditSiteViewController and handle in this last VC the savings.

Resources