Why does my ViewController still reload every time I segue to it? - ios

The header of my HomeViewController looks like this:
private let sharedGameBoard = HomeViewController()
class HomeViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
class var sharedInstance: HomeViewController {
return self.sharedInstance
}
I have my HomeViewController embedded in a UINavigationController. To navigate to a MultipleChoiceViewController I call:
var multipleChoiceVC: MultipleChoiceViewController = self.storyboard?.instantiateViewControllerWithIdentifier("multipleChoiceViewController") as! MultipleChoiceViewController
self.navigationController?.pushViewController(multipleChoiceVC, animated: true)
To return from MultipleChoiceViewController I call a show segue that is hooked up to a swipe gesture in my main.storyboard file.
Every time I navigate from MultipleChoiceViewController to HomeViewController, HomeViewController completely reloads. How do I keep this from happening. Like is seen in so many applications, I would like to be able to navigate from detail views back to the home controller without the home controller loading each time.
My load functions are called in viewDidLoad but I have a global bool variable that indicates whether the load functions should be called or not.
I've spent a good deal of time trying to understand singletons, push/show segues, and the navigation stack, to try and alleviate what is probably a very simple thing to accomplish. What is a way to implement this?

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
}

Showing a view controller across the app

I am having one view controller which should be hide and show from everywhere in the app without initializing it again. So I just want to know that how can I achieve this. Like by adding that view controller as childView or by presenting it to navigation controller or anything else.
The idea is that the view controller can be shown or hide from any screen of the app.
You can make a view controller as a cocoa touch class... and you can add a xib to it.. once you design the interface for the view controller..
You can make a singleton class and keep the shared instance like this:
class YourViewController: UIViewController {
static let sharedInstance = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "yourStoryBoardId")
}
To show this just do :
func someFunc() {
show(YourViewController.sharedInstance, sender: self)
}
I once did something like this in one of my apps.. i think its a standard approach.
You could also see this for more info and source

Detect that from which Page I come to the current Page

I want to know how to do this:
I have 3 view controllers and the first and second view controller are connected to the third one !
I want to know how can I write a code that detect from which one I came to this view controller
I have searched here for my answer But all of the similar questions asked about navigation !!!
The Important thing is that I don't have navigation in my app!!
I don't know if my answer will help you in your specific case, but here is the implementation I see from what you are asking. Maybe it will inspire you.
So imagine your are in your homePage or whatever viewController and you want to navigate throw other, but you want to know from which viewController you came from.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:segue_VC1]) {
CustomViewController1* destinationVC = segue.destinationViewController;
destinationVC.fromSegue = #"I AM VC 1";
}
if ([segue.identifier isEqualToString:segue_VC2]) {
CustomViewController2* destinationVC = segue.destinationViewController;
destinationVC.fromSegue = #"I AM VC 2";
}
}
The more important thing you have to know is that you can access attribute from the destination view controller you will access with your segue.
I know this is in Obj C, but the implementation is still the same.
So that when you navigate from a ViewController to an other one, you can set the attribute of the destinationViewController.
Then when you are in the view controller you wanted to navigate you can check :
if ([_fromSegue isEqualToString: "I AM VC 1"])
// do specific stuff when you come from VC 1
else if ([_fromSegue isEqualToString: "I AM VC 2"])
// do specific stuff when you come from VC 2
else
// other case
There are many ways to do that like simply passing a view controller as a property to the new instance. But in your case it might make more sense to create a static variable which holds the stack of the view controllers the same way the navigation controller does that.
If you are doing this only between the UIViewController subclasses I suggest you to create another subclass of it form which all other view controllers inherit. Let us call it TrackedViewController.
class TrackedViewController : UIViewController {
static var currentViewController: TrackedViewController?
static var previousViewController: TrackedViewController?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
TrackedViewController.previousViewController = TrackedViewController.currentViewController
TrackedViewController.currentViewController = self
}
}
Now you need to change all the view controllers you want to track so that they all inherit from TrackedViewController as class MyViewController : TrackedViewController {. And that is pretty much it. Now at any point anywhere in your project you can find your current view controller via TrackedViewController.currentViewController and the previous view controller via TrackedViewController.previousViewController. So you can say something like:
if let myController = TrackedViewController.previousViewController as? MyViewController {
// Code here if the screen was reached from MyViewController instance
}
Now the way I did it was through the instance of the view controller which may have some side effects.
The biggest problem you may have is that the previous controller is being retained along with the current view controller. That means you may have 2 controllers in memory you do not need.
If you go from controller A to B to C and back to B then the previous view controller is C, not A. This might be desired result or not.
The system will ignore all other view controllers. So if you use one that is not a subclass of TrackedViewController the call will be ignored: A to B to UITableViewController to C will report that the C was presented by B even though there was another screen in between. Again this might be expected result.
So if the point 2 and 3 are good to you then you should only decide weather to fix the point 1. You may use weak to remove the retaining on the two properties but then you lose the information of the previous view controller if the controller is deallocated. So another choice is to use some identifiers:
class TrackedViewController : UIViewController {
static var currentViewControllerID: String?
static var previousViewControllerID: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
TrackedViewController.previousViewControllerID = TrackedViewController.currentViewControllerID
TrackedViewController.currentViewControllerID = self.screenIdentifier
}
var screenIdentifier: String {
return "Default Screen" // TODO: every view controller must override this method and have unique identifier
}
}
Also you may replace strings with some enumeration or something. Combining them with some associated values could then create quite a powerful tool.

Pass data between three viewController, all in navigationController, popToRootView

The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.

Dismiss performed UIViewController within controller that performed segue

I have UINavigationController with root UICalendarViewController. At some time while app running, inside UICalendarViewController I perform segue with UICalendarEventViewController. I keep reference to the 'UICalendarViewController' using
var calendarViewController: UICalendarViewController!
Later at some time I have an access to the UICalendarViewController, and then I need to dismiss UICalendarEventViewController. How to do this?
Inside UICalendarViewController I created
var calendarEventViewController: UICalendarEventViewController?
But when I try to dismiss it using:
calendarViewController.calendarEventViewController.dismissViewController(true, animated: true)
it doesn't work.
Sometimes for some reason we do not have the same instance of controller what we expect. In my case I didn't get the same instance of UICalendarViewController. But there is a way to do this if as me you use UITabBarController as a main navigation:
if let navigationController = viewControllersByIdentifier[PLContainerTabBarItemCalendar] as? UINavigationController {
navigationController.popToRootViewControllerAnimated(true)
}
Then I have a current controller, and I can manage them with visible results.

Resources