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.
Related
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.
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.
So, I followed this tutorial: http://enroyed.com/ios/how-to-pass-data-between-ios-tab-bars-using-delegate/
And the most important part of the tutorial:
- (void)viewDidLoad
{
[super viewDidLoad];
SecondViewController *svc = [self.tabBarController.viewControllers objectAtIndex:1];
svc.delegate = self; //important !! otherwise delegation will not work !
}
The problem is that even if I put it in "viewWillLoad", it still forces me to click on my tab before it initializes. How can I specify this before I click on the tab?
Edit
I have a three tab project. I used that tutorial in the link pass data from tab 1 to tab 2. The data passed is a url from a webview on tab 1 to a url on tab 2. The url gets pass when I click a link on the 1st tab.
The data does get passed, but only if I physically click on the 2nd tab first and then click back to the 1st and click on the link.
So, it appears to me that my code above only runs if I physically click on the 2nd tab.
Your problem is that - until you actually go to tab item 2, secondViewController is not fully initialised, and so there is no data to transfer from vc2 to vc1. In particular, secondViewController's view has not yet loaded, so there is no value to be had from it's slider yet, and no slider, so also no IBAction method to call to trigger the delegate method. Indeed, as the data transfer is only triggered on moving the slider in VC2, it should be fairly obvious that until you go to vc2 and move the slider, nothing is going to happen.
The example you link to uses the delegation pattern, which seems a fairly poor way to deal with your problem. The delegation pattern most comfortably fits with the scenario where there is a hierarchical relationship between delegator (owned) and delegatee (owner) ... not always, but most commonly. In a tab bar controller, the relationships are more like kindred child relationships to the tab bar controller itself.
You haven't offered enough detail in your question as to what you want to achieve, but you need to consider this:
When a tabBarController loads, all of it's child viewControllers are initialised but their views are not loaded.
This means that these methods do get called:
//if loading from a xib or in code
- (id) initWithNibName:bundle
//if loading from a storyboard
- (id) initWithCoder:
- (void) awakeFromNib
But the view loading methods (viewDidLoad, viewWillAppear etc) do not get called as the view does not get loaded unless you actually open the relevant tab.
You could solve this by putting an initialised variable into your viewController2 (in one of the init methods) and accessing that variable via property syntax from vc1. But then, you might as well just put an initialised value directly into vc1. You need to think closely about how each vc is dependent on the other, how you can decouple that dependency, and perhaps how to set up an independent data source that both vcs can access as needed. This could be a model class, or NSUserDefaults, or a property in your appDelegate... just a few of the many possible solutions.
which method will be called when i switch between tabs in tabBarController
i know at first time it will call viewDidLoad method ,i want to know is there any method that come in action when i switch to a particular tab (second time or third time ) .
regards
You can use the UITabBarControllerDelegate method tabBarController:didSelectViewController::
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
//do something
}
The method viewWillDisappear: is triggered each time you are about to leave the current view controller (and hence the current tab) and viewWillAppear: is triggered each time a view is about to be displayed.
A full reference for these methods can be found in the UIViewController docs.
This is pretty old, but it does come up on Google and is linked to from another answer. So, I thought I'd update it.
If your UITableBarController is displaying a UIViewController (i.e. its view) then you have to check the ViewController methods that fire when a view disappears and appears. You could use viewWillDisappear to find out if your view is about to be switched away from, and viewWillAppear to test if your view just got switched back to. Notice, the TabBarController typically keeps the ViewControllers loaded, just their views are moved out and in. The problem with using the TabBarDelegate method is that you need to know the name of your viewController, which makes that a dependency. Change the name and it will probably break with xcode's poor ability to rename Class String representations. Avoid it. Besides you don't want a bunch of conditional junk checking to see if your tabbar just loaded a particular tab unless you cannot avoid it. The other thing to notice is that if a particular tab presents a TableViewController you may have to resort to other techniques if you need data in the cells to change in response to being switched away from. I'm using willMoveToWindow:(UIWindow *)newWindow to get notified in the UITableViewCell case when the view goes away. There's probably a better way.