I have an app that I am developing using Swift 4.0. I have a View Controller on which I am showing some useful information and user can interact with them. Lets call this as BaseViewController.
What I am doing:
The BaseViewController is starting different other ViewControllers, and than user can dismiss those viewControllers and come back to BaseViewController.
What I want:
Now I want that whenever user comes back to BaseViewController it gets itself updated. I know it can be done using Protocols, but I just want a simple way. Like in Android there is onResume method to perform updates whenever Activity comes into active state.
I think there is no need to share code as starting other viewController from one viewcontroller is pretty simple. I just wanted to know the better approach. Thanks in advance
UPDATE: THIS IS HOW I AM CALLING NEXT CONTROLLER OVER BASE CONTROLLER
let dialogRegisterForEventVC = UIStoryboard(name: "Main",bundle: nil).instantiateViewController(withIdentifier: "idDialogRegisterForEventVC") as! DialogRegisterForEventVC
dialogRegisterForEventVC.modalPresentationStyle = .overCurrentContext
dialogRegisterForEventVC.modalTransitionStyle = .crossDissolve
dialogRegisterForEventVC.isUserLogin = isLogin
self.present(dialogRegisterForEventVC, animated: true) {
}
You can always go with viewWillAppear Method
override func viewWillAppear(_ animated: Bool) {
print("This is called when coming back to Base View Controller")
}
Use your refresh code here. This is called when you pop your viewController
There different cases and you need to handle them for example
If you presenting another viewController on top of this you should use
override func viewWillAppear(_ animated: Bool)
If you need to refresh the view if the app comes in foreground state you need such code :
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func willResignActive(_ notification: Notification) {
refreshMyView()
}
Also, you can create delegate that your viewController can call to update this view controller. And one more option is to make your view controller observer of custom notification in Notification center and to push this notification once when you need to refresh the view controller.
If you using the notification center just be sure that your UI changes are on the main thread.
Related
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
I want to refresh the whole page controller on back press.
I am navigating the viewcontroller using code.
My Code
let GTC = self.storyboard?.instantiateViewController(withIdentifier: "GoToCart")as! GoToCart
self.navigationController?.pushViewController(GTC, animated: true)
Using viewWillAppear to reload your UI. As you use navigationController?.pushViewController, the view will be retained and stored in stack.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reload the UI
}
viewWillAppear(_:)
viewWillAppear is called the first time the view is displayed as well as when the view is displayed again, so it can be called multiple times during the life of the view controller object. It’s called when the view is about to appear as a result of the user tapping the back button, when the view controller’s tab is selected in a tab bar controller etc. Make sure to call super.viewWillAppear() at some point in the implementation. You can refresh your UI in this method
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Reload the UI
}
A better approach is to use protocol
Create protocol from where you want to pop back(GoToCart)
Create delegate variable in GoToCart
Extend GoToCart protocol in MainViewController
Give reference to GoToCart of MainViewController when
navigate
Define delegate Method in MainViewController
Then you can call delegate method from GoToCart
Example
In GoToCart: Write code below..
protocol GoCartControllerDelegate
{
func childViewControllerResponse(parameter)
}
class GoToCart:UIViewController
{
var delegate: ChildViewControllerDelegate?
....
}
Then in mainViewController implement the protocol function end extend to the protocol
class MainViewController:UIViewController,GoCartControllerDelegate
{
// Define Delegate Method
func childViewControllerResponse(parameter)
{
//...here update what you want to update according to the situation
}
}
2 Important thing
when navigating to the gocart controller code like this
let GTC = self.storyboard?.instantiateViewController(withIdentifier: "GoToCart")as! GoToCart
GTC.delegate = self
self.navigationController?.pushViewController(GTC, animated: true)
and when popping from gocartViewController
code like this
self.navigationController?.popViewController(animated:true)
self.delegate?.childViewControllerResponse(parameter)
In my App, I've created a new storyboard that serves as a very basic tutorial for how to use certain features. (Instructions.storyboard). This storyboard has it's own class - InstructionsVC.swift
I want to present InstructionsVC when MainVC loads within viewDidAppear.
It works great. Fires up on App load just like it's supposed to. The problem occurs when I press the [Close] button on the Instructions interface. It closes the VC, fades to the main screen, and then immediately fires the Instructions VC back up.
How can I prevent the Instructions VC from loading back up once it's closed?
func openInstructions() {
let storyboard = UIStoryboard(name: "Instructions", bundle: nil)
let instructionsView = storyboard.instantiateViewController(withIdentifier: "instructionsStoryboardID")
instructionsView.modalPresentationStyle = .fullScreen
instructionsView.modalTransitionStyle = .crossDissolve
self.present(instructionsView, animated: true, completion:nil)
}
override func viewDidAppear(_ animated: Bool) {
openInstructions()
}
And within my instructions class, I have the following action on the close button:
#IBAction func closeButtonPressed(_ sender: UIButton) {
let presentingViewController: UIViewController! = self.presentingViewController
presentingViewController.dismiss(animated: true, completion: nil)
}
Note - I'd rather not use UserDefaults to resolve this, because I'm going to be incorporating something similar in other parts of the App and don't want to resort to UserDefaults to achieve the desirable behavior.
Thanks in advance buddies!
viewWillAppear and viewDidAppear are called every time a view controller's content view becomes visible. That includes the first time it's rendered and when it's shown again after being covered by a modal or by another view controller being pushed on top of it in a navigation stack.
viewDidLoad is only called once when a view controller's content view has been loaded, but before it is displayed. Thus when viewDidLoad is called it may be too soon to invoke your second view controller.
You might want to add an instance variable hasBeenDisplayed to your view controller. In viewDidAppear, check hasBeenDisplayed. If it's false, display your second view controller and set hasBeenDisplayed to true.
I have some code I call that changes the language in the viewWillAppear section of a viewcontroller inside a navigation controller.
When I hit the back button the language change doesn't take place even though I have code for it to in the viewWillAppear. The only time it switches is when I hit back all the way to the original screen and then start moving forward it changes. Is there any way to have the function in the viewWillAppear work?
Here is my code, I'm using a language changing pod:
//MARK: Language change
//used to change language text for imediate screens
func setText(){
locationsLabel.text = "Locations".localized()
languageLabel.text = "Languages".localized()
termsOfUseLabel.text = "Terms of Use".localized()
privacyPolicyLabel.text = "Privacy Policy".localized()
pushNotificationsLabel.text = "Push Notifications".localized()
contactUsLabel.text = "Contact Us".localized()
}
// Changes text to current language
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setText", name: LCLLanguageChangeNotification, object: nil)
}
// Remove the LCLLanguageChangeNotification on viewWillDisappear
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
The viewWillAppear method is only adding a notification observer. The observer is removed in viewWillDisappear. This means that setText will only be called if the LCLLanguageChangeNotification notification is sent while the view is visible.
The update stops as soon as the view goes off-screen due to the navigation behaviour.
To ensure that the text is updated, you also need to call setText inside viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setText()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setText", name: LCLLanguageChangeNotification, object: nil)
}
Implement navigationcontroller delegate methods
navigationController:willShowViewController:animated:
navigationController:didShowViewController:animated:
I have created this little class that solves this problem.
Just set it as a delegate of your navigation controller, and implement simple one or two methods in your view controller - that will get called when the view is about to be shown or has been shown via NavigationController
Here's the GIST showing the code
I am in the 3rd UIViewController of a UINavigationController and would like to dismiss all UIViewControllers before switching tabs on the UITabBarController that contains my UINavigationController.
self.navigationController?.popToRootViewControllerAnimated(true)
self.tabBarController?.selectedIndex = 1
The code above does not switch tab bar indexes. It seems that code after popToRootViewControllerAnimated never runs.
What are my options?
AFAIK, there is no API to provide a completion block to popToRootViewControllerAnimated and I'm guessing that once you pop the VC, it's gone and no more code executes. It's like you want to provide a completion block to be performed once the animation completes.
The UINavigationController API itself doesn't offer any options for this.
However by using a combination of CoreAnimation framework and NSNotifications it's possible to add a completion block that posts a notification, which the root view controller can listen for.
You might even be able to get away without the CoreAnimation trickery, and just post the notification after you popToRootViewControllerAnimated but I haven't tried that yet.
This would be the code for the bottom view controller in the stack:
class DetailViewController: UIViewController {
#IBAction func popAndSwitchTabs(sender: AnyObject) {
CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("switchTabsNotification", object: nil)
}
self.navigationController?.popToRootViewControllerAnimated(true)
CATransaction.commit()
}
}
And then use code like this in the top view controller in the stack:
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "switchTabs", name: "switchTabsNotification", object: nil)
}
func switchTabs() {
self.tabBarController?.selectedIndex = 1
}
}
Here's a quick example project I threw together that you can try out on Github:
https://github.com/obuseme/PopAndSwitchExample