So my app decides at runtime which TabBarItems are available.
Problem is, [self.tabBarController viewControllers] is empty in the actual TabBarController class (in viewDidLoad and viewWillAppear that is). It is filled after the first ViewController (tab) did load, but that is too late as i dont even know if i want to load this particular one.
I know this can be solved by dumping storyboard, is it possible relying on it too?
Best solution that comes to mind so far is creating a dummy ViewController that gets swapped out immediately after loading is done...
It's empty because the tab bar controller doesn't have a tabBarController property. It should just be self.viewControllers.
Related
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.
As someone who usually used separate xibs in the past I thought I'd give storyboard a go as it seemed a lot simpler to use and much easier to develop with. I've been writing an application where the essential set up is this:
At the top of all this is a UINavigationController (first level). Then I have Multiple UIViewControllers (second level) with buttons in them which you can tap to switch between the second level UIViewControllers.
However a problem occurs when I start switching between the second level UIViewControllers. I first thought this was an initialisation problem with the NSMutableArrays because in my code I have a NSTimer set to loop periodically and found when I set a breakpoint during it, when I went forward to the next timer tick event there appeared to be different instances of the same NSMutableArrays and it seemed a gamble to try and insert new values into these array with it sometimes working, sometimes not (as it may or may not insert into the correct instance).
Then, looking at the memory usage under Debug Navigator I found the issue. Each time I "switched" between the UIViewControllers a new UIViewController was being initiated, along with all new variables.
The code I am using to switch between them is
-(void) perform {
[[[self sourceViewController] navigationController] pushViewController:[self destinationViewController] animated:NO];
}
Or essentially a push segue transition. This also explains why when I tried to switch back to my view, the data on that view was lost as it is a complete new view.
Does anyone know how to switch between multiple ones of these UIViewControllers (say 5) essentially like a UITabViewController would except without the tab bar being present?
First option you can do this: You can use a tabbarcontroller for switching viewcontroller and hidden the tabbar. Then on buttonclick setthe tabbar index.
Second option you can do this: Create one more view controller and in this viewcontroller subview the all switching viewController and when you want to switch viewcontroller just bring that viewcontroller view to front by delegate.
Do you need the navigation bar and other features provided by your top level navigation controller?
If not, you could use a UIPageViewController instead.
You set up all your second level view controllers and then just have to tell the page view controller which one to display.
If you implement the associated delegate methods, it will automatically provide swipe gestures to switch between them and nice animations to get them on and off screen.
You can also get it to put a UIPageControl at the bottom showing a dot for each VC with the dot for the current VC highlighted.
I have two ViewControllers. The first has UITableview that I push data from to the second ViewController. Whenever I go back, the first ViewController loses its properties - Its navigationbar background disappears and for example the sidebar menu does not work either. Is there any way I could reload it while pushing the back button?
Thank you
Sounds like you are probably doing your setup in viewWillAppear instead of viewWillLoad. This means that when the view will appear on return, the setup is happening again and perhaps leaving you in an unexpected state. Put breakpoints in your view controller delegate methods and see what order things are being called in and why.
I changed navigation in my application from using UITabBarController to u UINavigationController. I.e. former solution (1st version) was based only on the TabBarController - 4 ViewControllers (one simple TableView, one simple custom view and one MapView with many overlays). The second version is based only on the UINavigationController.
In case of TabBarController it was clear and simple, everything worked fine, especially MapView. I mean: the MapView was loaded once (with a significant number of overlays) and when I went to another view and back to the MapView the MapView was still there with its overlays already loaded and displayed (simple check: MapView`s viewDidLoad was called just once per app run, I had some debug messages there).
Now I changed navigation logic to the UINavigationController. Everything works fine for the first look - but: the viewDidLoad (for each view) is called everytime I navigate to the view. It is annoying especially in the case of the MapView - the loading of overlays is performed everytime, it takes some time and it causes app crash in some cases.
OK, my questions:
Is it some kind of "common" behavior of NavigationController?
Can I change this behavior so viewDidLoad will be called just once?
And more - How can I influence the "display sequence" of some view?
I understand the logic is probably more complicated but I appreciate any answer or hint ;)
Some related circumstances:
TabBar and Navigation controllers are not combined.
I use storyboards, segues are designed in the UIB, no manual calling like perfomSegue or prepareForSegue in my code. One button triggers segue to MapView.
I use push segues.
I also tried to use modal segues but without any change of that behavior.
any of viewDidUnload is never called during segues among the views.
No memory warning received.
No memory leaks measured both on simulator and iPhone 4.
I tried to build a very simple temporary project / app that is concerned just about the Nav. Controller and other views without ANY coding, just storyboard. It was the same behavior.
There was an issue that causes app crash when I fast and periodically tapped to navigation button and back button between one view and the MapView. In most cases the app crashed when I tapped the back button on the MapView before it was fully displayed (i.e. its overlays). It was fixed when I added a 1 sec. delay method call in the viewDidDisappeared in the MapView. It is not a fair fix, I know ;)
A UITabBarController and UINavigationController are based on fundamentally different paradigms.
The UITabBarController is intended for the UIViewController on each tab to exist independently of each other and for the user to choose which they want to view. viewDidLoad only gets called once for each UIViewController because it is intended that each tab still exists in memory even as the user switches to a different tab.
The UINavigationController is a stack of UIViewControllers where each is related to the one above and beneath itself. The top UIViewController in the stack is always the one that is visible to the user. When a UIViewController is pushed to the stack, its viewDidLoad gets called because it is being loaded into memory. When the top UIViewControllergets poped off the stack, it is unloaded from memory, and viewDidUnload gets called on the way out (viewDidUnload is deprecated in iOS6 and won't get called, but the controller will still get dumped from memory). This is why viewDidLoad gets called every time that the user pushes a particular UIViewController onto the UINavigationController stack.
I'm new working with Storyboards, so I'd appreciate any help!
I have a Settings view controller which should only appear if no previous settings exist. If those have been set, a different vc (the main one) is loaded.
I tried using the method in the attached image, but I'm not sure that's correct...
In the RootViewController I'm testing in viewDidLoad if settings were previously set or not, and am triggering the Segue with either ShowMain or ShowSettings as identifiers.
[self performSegueWithIdentifier:#"ShowMain" sender:self];
Of course RootViewController shows up in the NavigationController hierarchy (with the back button showing), which I don't want.
How should I be going about this?
Tia!
S.
If, as it seems, the RootViewController only exists to decide which real controller to show, I'd suggest you get rid of it. Your main controller can be set as the first controller and have the settings logic in its viewDidLoad. Then, make your settings controller be presented using a modal segue. Once you're finished with the settings (if necessary), dismiss it and you're back to the main one.