I have a modally presented View Controller called ChangeViewController that allows the user to change some information regarding his/her profile. When the user is done, the 'ACCEPT CHANGES' button is pressed and I use an unwind segue to exit out of the ChangeViewController and go back to the main screen.
Is it possible for me to refresh the data of the entire app before performing this unwind segue? As of now, the information on the home screen and other screens remains unchanged after user modifications. Only after an app reboot does the information update. Is there a way to programmatically 'reboot' so all View Controllers are up to date? More specifically, is there a way to call the viewDidLoad functions of all View Controllers so their data is updated?
Solutions in Swift preferred. Thank you!
I oftentimes updateUI after a user changes with a function like this. This is pretty simple, and you just call the function whenever your UI is updated.
func updateUI() {
// Redraw your labels, update your UIElements, do what you have to do
}
A way you can call this function from a modally presented ViewController without closing the app is with delegation, since modal presentation does not throw the old ViewController out of the stack and heap, Delegation works like this:
In your modal controller:
protocol ChangeViewControllerDelegat: class {
func updateUI(sender:UIButton)
}
class ChangeViewController: UIViewController {
weak var delegate: ChangeViewControllerDelegat?
func opChangingUserSettings() {
// Change settings with your code
// tell your ViewController to do it.
delegate?.updateUI()
}
in your mainVC
class MainViewController: UIViewController, ExtensionViewControllerDelegate {
func updateUI() {
// Redraw your labels, update your UIElements, do what you have to do
}
}
Hope that helps!
By the way, are you trying to change language by any chance on the fly? If so, I can show you how to do that. If not, and If I understand your question, this should work.
Well, you can use the local notification for your scenario.
As in, when the user is done with the changes and presses the button to accept the changes, there you can post a local notification to reload the data.
And you can listen to that notification in all of your viewControllers and reload the data there, like if it is a table view then you can simply call the reloadData method on tableview to achieve it.
Related
I want to detect if back button is pressed in the next viewController in a navigationController.
Let's say I have VC_A and VC_B viewControllers in a navigationController. I know how to detect if back button is pressed in a current view controller but I do not know how to detect it in a previous viewController.
Edit:
I go from VC_A to VC_B and when I press back button in VC_B then I want to call a function in VC_A.
You could use notification center. This link has a nice tutorial: https://learnappmaking.com/notification-center-how-to-swift/
I want to detect if back button is pressed in the next viewController in a navigationController.
I'm not sure I understand this exactly, but it really doesn't matter much: in essence, you're talking about some view controller (call it controllerA), whose views aren't currently visible, finding out about a change that affects some other view controller (controllerB). The usual reason for needing such a thing is so that controllerA can update some data that it manages.
A better way to handle that is to have both controllers share a common data model. Any application state that's affected by something like a view controller being dismissed is shared data that should be part of the data model. controllerA really shouldn't care about whether controllerB's back button was tapped or not... that event is only the business of controllerB (and arguably the navigation controller that manages it). What controllerA should care about is updating its own views according to whatever changes happened while it was off screen, and those changes should be recorded in the model by controllerB and any other view controllers that might have been active along the way.
I'm suggesting you to do that with Notification Center like AglaiaZ suggested you. But if you're not feeling comfortable with using Notification Center, then try this more basic solution with viewWillAppear delegate method in viewController from which you're tracking are you back from B VC. So, let's go.
Set this variable in your current view controller class where you want to trigger method when the back button is pressed on the specific view controller, let's call that specific view controller B VC.
let isFromBViewController = false
Then in code block where you're triggering the transition to B VC set this variable to true.
func goToBViewController() { // This method is triggering transition from A VC to B VC
isFromBViewController = true }
And then in viewWillAppear delegate method check did current VC from which we triggered the transition to B VC have appeard from B VC.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFromBViewController {
// code for doing something when you got back from B VC
isFromBViewController = false
}}
And that's it.
But, again I'm suggesting you to use the notification center as #AglaiaZ suggested, the tutorial is easy, and with that tutorial I've also learned how to use Notification Center and how to create and use custom notifications.
Good luck.
If I understood correctly, you want to do something when the back button in the navigation bar at the current view controller is pressed, and the user is going back from the current B view controller to A view controller.
Put this line of code in the view controller in which you want to track when the user has pressed the back button.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingToParent {
//your code when back button is pressed
}
}
Upon app start, i have a login screen, which is an instance of LoginViewController and on success, i.e. after my user is successfully logged in, the main content of my app is being shown using the performSegue(withIdentifier identifier: String, sender: Any?) method to an instance of a UITabBarController. Unfortunately i deinit() is not being called on my LoginViewController after the segue. This does not happen due to any reference cycles i might have in my LoginViewController, because i explicitely checked for those.
Since a login screen is only required once upon app start, i was wondering if there is a best practice to be able to make sure that my LoginViewController is being deallocated after the segue?
Maybe using a segue is just not the right way to achive this, because
from what i understand, the conclusion of a similar question Deinit not called on Show Detail segue was the fact, that using a showDetail-segue is the same as using a modal presentation, i.e. both controllers keep a reference to each other and deinit() will not be called in either of them. Is this really ecpected behavior, or am i missing something?
Update: The reason i care so much, is the fact that i am using RxSwift and my LoginViewController is subscribed on several Observables, so after my app leaves the LoginViewController, i want to make sure to set my collected DisposeBag to nil. Would it be finde to do this in the ViewDidDisappear() of my LoginViewController?
I would suggest you to create a custom segue class and override the perform function like this:
class CustomSegue: UIStoryboardSegue {
override func perform() {
source.view.window?.rootViewController = destination
}
}
For my iOS app, I am currently checking if a user is logged in with email and password via firebase in my initial view controller, using the recommended method from the firebase documentation. If a user is not logged in, I then present my login screen as shown below:
class InitialViewController: UIViewController {
var authHandle: FIRAuthStateDidChangeListenerHandle!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
hasUserSignedIn()
}
func hasUserSignedIn() {
authHandle = FIRAuth.auth()?.addStateDidChangeListener { [unowned self] auth, user in
if user == nil {
self.perform(#selector(self.presentLogInSignUp), with: nil, afterDelay: 0)
}
}
}
However, when a user is not signed in, they briefly see my initial view controller before the log in controller is presented, which isn't a great user experience. A way to solve this would be to add a check in the app delegate's didFinishLaunchingWithOptions.
Firstly, is the app delegate and didFinishLaunchingWithOptions an OK place to check if a user is logged in with Firebase (would be done after FIRAppConfigure())?. I assume it is, as from my understanding, a user's Firebase logged in state is persisted in the keychain (is that correct?)
Secondly, if the app delegate is an appropriate place for checking a user's logged in state, should I use the recommended way as I do in my initial view controller or the method below? The advantage of latter is that I don't need to worry about having to remove a listener but Firebase docs gives a warning with this method that the auth object may not have finished initialising.
if FIRAuth.auth()?.currentUser != nil {
// User is signed in.
// ...
} else {
// No user is signed in.
// ...
}
Current potential solution:
I use the recommended method in didFinishLaunchingWithOptions and I just remove the handler after using it within didFinishLaunchingWithOptions.
Firebase is awesome and look forward to implementing this correctly.
they briefly see my initial view controller
They are not seeing the initial view controller. They are seeing the view that the initial view controller is controlling. i.e. by the time the code gets to viewWillAppear the view has already been loaded and is ready to be shown which is why it's initially visible before changing to the log in view.
The logic to determine which view should be shown should occur before then.
One option is to determine that in the app delegate and instantiate the appropriate viewController in code in the didFinishLaunchingWithOptions function.
If you look at your Main.storyboard, click the view and then on the right in the attributes inspector, there's a checkbox for Is Initial View Controller. That's the first one to be displayed. If you uncheck that, then you can set it up in code.
See this question and answer
Programmatically set the initial view controller using Storyboards
You can do the check in didFinishLaunchingWithOptions, or viewDidLoad....
Or... alternatively, for anyone who want to keep it in viewWillAppear, because you may have to do that anyway. For example if the user gets their email verified and you come back to the same page, you may have to reaload the user and check isEmailVerified inside of viewWillAppear.
So one way of showing different controllers in general is to embed them in a customised UITabBarController, make sure to hide the actual bar of cause. You can then check the "Auth.auth().currentUser" in the viewWillAppear of the customised UITabBarController and select the page you want to show conditionally (with self.selectedIndex = whatever and make sure not to animate the transition).
I guess the advantage of doing it this way is the flow is clear and if you use the storyboard it's nice and tidy.
I have four viewControllers in my application. Now lets suppose user is currently on the second viewController and He terminates the application.
Now how can I save this state, so that when user reopen the application he should be presented with the second viewContoller, and should be able to navigate back to first viewContoller as well.
The solution I have in my mind is to simply save a variable of current screen in userDefaults and then make that viewController a rootViewController. But I know its not the proper solution and I will lose navigation as well.
Please guide me thanks.
If you haven't yet, create a class called BaseViewController that should be extended from all your UIViewControllers:
class BaseViewController: UIViewController {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// always remember last controller shown
NSUserDefaults.standardUserDefaults.setObject(classForCoder(), forKey: "lastController")
}
}
You can save controller class name, an int identifier or somenthing you want.
Then, in app delegate on application start you can read this value and make your custom logic to open the right controller.
if NSUserDefaults.standardUserDefaults().stringForKey("lastController") == MyCustomViewController.classForCoder() {
// open MyCustomViewController
}
Then when user goes back, redirect to the right UIViewController according your application flow.
I'm trying to figure out how to navigate around my app. But i'm a little lost.
I have a UIViewController that loads some data, then displays the data in a CollectionView. Then I have another UIViewController for the detailed view. I then trigger a segue to go to it, I pass the data etc.
self.performSegueWithIdentifier("detailViewSeque", sender: nil)
But the part i'm lost on is getting back to my main view, if I just trigger another segue then it loads all the data / view again. The data has already been loaded once, I really don't want to keep loading it.
I feel like I'm doing things wrong, that theres some super obvious way to handle this scenario.
Could someone point me in the right direction?
This is good situation to use an unwind segue (for more information: What are Unwind segues for and how do you use them?). Here's how to setup one up:
Firstly, create an #IBAction in the view controller you want to segue to, that takes a UIStoryboardSegue as its only argument. For example:
#IBAction func unwindToHere(segue: UIStoryboardSegue) {
// If you need you have access to the previous view controller
// through the segue object.
}
Secondly, you need to create the unwind segue in IB. To do this ctrl-drag from the view controller you want to segue from, to Exit and select the unwindToHere method:
Thirdly, you need to give your segue and identifier. To do this select your segue (see below - your segue will not be visible like normal segues); then use the Attribute Editor to give your segue an identifier.
Now you can use your segue. On the view controller you want to segue from, call:
self.performSegueWithIdentifier("YourID", sender: self)
To rephrase your needs "I have data that I need to keep around somewhere that isn't associated with a view controller".
You have a few options here. Your goal is basically to store it somewhere that isn't going to go out of memory.
The AppDelegate gets used for this purpose a lot but Singleton variable works as well.
I would personally create a singleton, say CatPictureRetriever with
private let _CatPictureRetriever SharedInstance = CatPictureRetriever()
class CatPictureRetriever {
static let sharedInstance = CatPictureRetriever()
var catPictures : NSArray?;
func gimmeCatPictures -> NSArray? {
return catPictures
}
}
Now you can get your pictures though your CatPictureRetriever anywhere
var pictures = CatPictureRetriever.sharedInstance.gimmeCatPictures()