How to properly handle a nil UIApplication.sharedApplication().keyWindow - ios

When the viewDidLoad is called the view is supposed to be loaded.
But I always crash in UIApplication.sharedApplication().keyWindow being nil...
Where should I put my code to have it called once the view is loaded and not everytime the user comes back (I have excluded viewWillAppear for that reason) ?

Situation 1: - Manual UIWindow creation in App's delegate
You have probably somehow added a UIViewController to a UIWindow before setting it as key.
You should call window.makeKeyAndVisible() in your app's delegate after creating the window.
Situation 2: - Automatic storyboard instantiation
The system reads your storyboard, initializes the root view controller, prepares it by calling viewDidLoad and viewWillAppear, adds it to the window and shows the window.
What happens is the system cannot just set the window to the front and animate the view controller onto it, because it's the first view controller and you are not push/popping of a nav controller. And viewDidLoad can take some time... So the Default.png is showing in the meanwhile until the view controller is ready.
Then it calls viewDidAppear, when it has actually appeared. So that's where you should be able to access a keyWindow
Situation 3:
You are in a weird situation where you have windows but none of them is the "key" window currently, but you desperately need to access the window.
Call UIApplication.sharedApplication().windows, see if it's not empty and take the first element. UIApplication.sharedApplication().delegate?.window might have a value too, but it's usually only when the window is key already.

Try using the window property of the application delegate:
UIApplication.sharedApplication().delegate!.window

If you get nil from "UIApplication.shared.keyWindow" at "viewDidLoad", Try wrap your code inside of DispatchQueue.
ex)
override func viewDidLoad() {
DispatchQueue.main.async {
if var window = UIApplication.shared.keyWindow {
// do something
}
}
}

Related

TabBarController's second tab doesn't get instantiated -> IBOutlets are nil

I'm creating a simple app which has a tab bar controller where the summary is one tab and the history is another. In the summary tab, there is a button to add a new round. Whenever this round gets added it has to go to the history tab as well.
I'm trying to send the data through the tabBarController.
What I'm experiencing is whenever I don't open the history tab before adding a new round my program crashes because my IBOutlets are nil. But whenever I open the tab first and then go back to add a new round it works fine. I also don't have to reopen the tab after every round. It looks like the tab isn't getting instantiated before I open it up the first time.
Gif of failure (Error is that the Chart View is nil):
http://i.imgur.com/VPa0RmK.gifv
Gif of success:
http://i.imgur.com/LqxYBjV.gifv
Is there any way to do this manually?
I'm new to iOS programming so that is what I think the problem is. If there's happening something else that's crashing my code I'd like to know!
You are right. The problem is that the outlets for the second tab do not get set up until you visit that tab.
Solutions:
You can write the data to a property of the second viewController, and then move that data into the outlet in an override of viewWillAppear.
Another possibility is to check if the outlet is nil before you write to it. If it is, call loadView on the second viewController to tell it to set up its outlets, and then you'll be able to write to them. Note, if you call loadView manually, the method viewDidLoad will then not run, so if you're doing any additional setup in there, you'll also need to do that from loadView.
Perhaps even simpler than calling loadView manually is to trigger the viewDidLoad of the secondViewController by accessing the view property. This can be as simple as:
if let svc = self.tabBarController?.viewControllers?[1] as? SecondViewController {
// check if outlet is nil
if svc.myLabel == nil {
// trigger viewDidLoad to set up the outlets
_ = svc.view
}
svc.myLabel = "Success!"
}
That said, directly accessing another viewController's views (i.e. outlets) is poor design and a violation of the MVC (Model-View-Controller) paradigm. You should really put your data in a place that can be accessed by all tabs and then have each viewController update itself in viewWillAppear when the tab is selected. See this answer: Sharing data in a TabBarController for one way to do this.
This is how it's supposed to work. Each Tab is created only when it has to be displayed. This the same idea for TableViews and CollectionViews.
The easy one to fix this for you is to override the viewWillAppear (or viewDidAppear) method of your HistoryViewController and refresh the UI from there.
This way, you never assume that the History tab exists in the Summary tab, History refreshes its UI by itself.
Hope this helps.

viewDidLoad vs ViewWillAppear in IOS

Please help me with this. I have created a simple project with two views as shown. I have attached the images for my storyboard and swift files. So, I read that viewdidload will be executed only once while loading the view into the memory. But, when I make a transition from secondview to the firstview the viewdidload is executing again and so is the print statement in the viewdidload method.
Someone please explain me this.
viewDidLoad is not called once for the Application. It is get called once for that viewController when the view holds memory and loaded.
So as many number of of time you push to the viewController, that many times it will call the viewDidLoad
viewDidLoad() — Called when the view controller’s content view (the top
of its view hierarchy) is created and loaded
viewWillAppear() — Intended for any operations that you want always to
occur before the view becomes visible.
For more info about this look at the link : https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson4.html
So if the view is already in memory (Like your case), then no need to push again, only need to pop back by this code
self.navigationController?.popViewControllerAnimated(true)
You should not make transition from secondViewController to firstViewController for back. Pop the second view controller by this code to back:
self.navigationController?.popViewControllerAnimated(true)
When you make a transition it makes a new instance from your firstViewController but when you pop the second view controller it dismiss your secondViewController and shows your last viewed viewController again.
Or
in the case that you are not using navigationController you should use below code to dismiss your secondViewController
self.dismissViewControllerAnimated(true, completion: {});
The main point is that you should not use new transition for back.
The simplest way:
1.First embed your ViewController in NavigationController
2.Call to this (instead of create segue for backing)
navigationController?.popToRootViewController(animated: true)
viewDidLoad will be called only once

reloadData in AppDelegate Swift

I have a function in my AppDelegate which is similar to, DidReceiveRemoteNotification, and upon receiving a message appends a message to an array and then attempts to reload a tableview.
My app consists of just one Viewcontroller and one AppDelegate.
My code looks like this:
var myCustomViewController: ViewController = ViewController(nibName: nil, bundle: nil)
println("******didReceiveMessage*****")
myCustomViewController.messages.append(message.data.message as! String)
println(myCustomViewController.messages)
myCustomViewController.MessageTableView.reloadData()
whenever I try to call a reload function my app crashes however. Is there a way to reload my tableView from my appDelegate function?
Your view isn't loaded yet, so the table view doesn't exist yet, hence it is nil.
It isn't clear whether you're using the correct view controller, because you're creating a new one. Either call view on it to create the view (and subviews) or change the reference to the real existing view controller.
Most likely you should be accessing the root view controller of the app delegates window.

Code in viewDidLoad runs every time it is called

Hi all I am doing a course in Udemy, and the code calls for placing code in the viewDidLoad function as shown below:
override func viewDidLoad() {
super.viewDidLoad()
placesArray.append(["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"])
}
The array append should only run once, however, when I segue to another viewController and come back, it runs the code to append again. So I now have an array with 2 rows, both of which are Taj Mahal.
I thought that the viewDidLoad function only runs code once?
Is there a way around this?
Thanks.
Addendum:
I am using Swift, so I don't see any alloc and init while creating and launching the viewController. And weird as it sounds, the video tutorial has it working in the viewDidLoad and the trainer is using the storyboard to segue from the initial table view controller to a map view on a view controller and just has a back button on the map view that segue's back to the table view controller via the storyboard as well. - Could be because I have the latest version of the Swift language and the trainer was using an earlier version, (cause I noticed some slight differences in coding earlier) but you never know. Either way whenever he touches the back button it does not run the append code anymore.
I am trying to get in contact with the trainer as some of the suggestions here, though they are good don't seem to work.
I will put the solution in here once I get in contact with the trainer.
The viewDidLoad method is called when your view controller's view finishes loading. The view will load when a view controller's view property is nil and something attempts to access it.
UIViewController *myVC = [[UIViewController alloc] init];
UIView *aView = myVC.view; // this loads myVC's view; viewDidLoad is called when it completes loading.
If the view has unloaded (usually due to memory limitations), it will be called when the view property is next accessed.
Manipulation of data sets should generally not be done within view methods. Consider moving this to the init of the view controller (or to a different "datasource" class).
I suppose you are trying to do data initialisation in viewDidLoad. If there is no other operation on placesArray before viewDidLoad, then instead of append, what about setting the placesArray directly:
placesArray = ["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"]
Then even if your view is unloaded for some reasons. Taj Mahal will still be added once only.
viewDidLoad is called whenever the view controller's view property is set. When does this happen? It depends on how the view controller is contained:
UINavigationController
- View Controller views are loaded as they are added to the navigation stack and "unloaded" (although the viewDidUnload method is deprecated) as they are removed.
UITabBarController
- View Controller views are loaded as they are added to the tab bar regardless of whether they are on screen or not. They stay loaded as you change from tab to tab.
Depending on your needs and use case, you can create your own view controller container that does what you need. Checkout the Apple docs on the proper way to do this:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html

Can I initialize something in an iPhone app with a storyboard outside of viewDidLoad?

I have an iPhone application which consists of two View Controllers; a main one, and one with the help screen. Each one has a button that performs the segue from one to the other.
The problem I have is, when I segue back from the help screen to the main screen, the main view controller's viewDidLoad method gets called, so all of the initialization I did when the app was first started is repeated. Is there another method in the view controller that gets called just once, where I can do the initialization?
My first thought was, "Have a boolean variable that is initially set to false, then have viewDidLoad test it, and if it is false, do the initialization, then set it to true" - but how do I initialize it to false in the first place?
My guess is that you're doing a "Push" segue (which is the most standard kind of segue one can do in an iOS app), and if you are using a "Push" segue that means you have a navigation controller in your app.
The best thing to do here is not to push another "Main" view controller onto the stack of other view controllers (which is why you are seeing "viewDidLoad" called each time you push the main view), but instead when you click a "go to main" button in your help screen, pop the help screen off and return to the previous one. The call that would do this is UINavigationController's popViewControllerAnimated method.
Doing that means "viewDidLoad" on that view controller only gets called once, as the main view gets loaded once.
on the .m class file create a bool on #implementaition:
#implementation yourClass{
bool initialize = 0;
}
and then test it on view did load:
-(void)viewDidLoad{
if(initialize == 0){
//do everything you need to do
initialize = 1;
}
}
I think it will work...

Resources