I am creating an app with different views as you can see in this screenshot.
However when I tap on "Transitions" it just loads the view again with viewDidLoad it does that every time so it always resets upon opening. But when you have a tabbed application it just loads it with viewDidLoad then whenever you open it again it just loads viewDidAppear since it already loaded.
Does anyone know how I can keep the controller alive like in a tabbed application ?
*Image of tabbed application - http://s1.ibtimes.com/sites/www.ibtimes.com/files/styles/v2_article_large/public/2013/06/18/ios-7-notonthisphone-2.PNG
Here's how the view loads now.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *menuItem = self.menuItems[indexPath.row];
// This undoes the Zoom Transition's scale because it affects the other transitions.
// You normally wouldn't need to do anything like this, but we're changing transitions
// dynamically so everything needs to start in a consistent state.
self.slidingViewController.topViewController.view.layer.transform = CATransform3DMakeScale(1, 1, 1);
if ([menuItem isEqualToString:#"Transitions"]) {
self.slidingViewController.topViewController = self.transitionsNavigationController;
} else if ([menuItem isEqualToString:#"Settings"]) {
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MESettingsNavigationController"];
}
[self.slidingViewController resetTopViewAnimated:YES];
}
Decided to share my files with all of you if someone is an expert in this (clearly I'm not one of them) : http://www.mediafire.com/download/1q6n01514bf78jo/Sldiing_App.zip
Well, keep them.
Maintain some container (array or individual properties/ivars etc) and keep references to them. Then, when the user tapps on "transitions", do not re-create the views and their controllers but put them back into place.
You may need to maintain each view controllers sub-view-controllers call hierarchy and manipulate the navigation controllers navigation stack (see docs for the viewControllers property of UIViewController).
It is a bit of work though and does not come for free.
Alternatively, if you do not have a navigation controller involved or do not need to stack more view controllers on top of your bunch of view controllers that you want to keep alive, then you could just try to move them off the screen (To negative coordinates or beyond the windows' boundaries. But don't use constants for that. We all don't know what screen sizes future devices will have. Fetch the boundaries from the actual window on runtime.)
Whenever you're tapping on "transitions" I assume you're doing an alloc/init of your view controller and then pushing it to whatever view stack you have for the main view. Instead, alloc/init a global view controller when your side menu loads, and push that onto your stack when you tap on "transitions" instead of creating a new one every time (this assumes that your menu persists and isn't loaded every time it comes up as well)
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.
Right I have looked at a few SO questions on the subject and I am finding it difficult to come up with the correct solution here.
Requirements
I have a UITabBar based application. One of the tabs has a UINavigation controller with UISegmentedControl at the top allowing the user to switch between three different views.
Each view will have a UITableView which will allow the user to navigate to another view. These views should be pushed onto to the navigation controller.
Problem
Now all the SO questions and Answers on the subject show how to switch between views. However I need the view I switch to, to allow pushing of another view onto the navigation stack. I am not sure this is even possible. I thought about UIViewController containment - however that would show a view being pushed onto the stack in a smaller window that the screen's bounds. Not what I am looking for.
Any ideas how I can solve this with storyboards and UIViewControllers?
UPDATE
Here is what I am trying to do: In the screenshot the container area is where I need to load other view controllers into. The UISegment control cannot go into the navigation bar as that space is used for something else. So that's why I think UIViewController containment might be better here?
So even though this isn't using separate TableViewControllers, you can use different custom UIViews that are hidden by default and become visible when you select it's corresponding button. This will unfortunately make it so you have all three view's logic in the same VC.
To get around this, you can try setting up some delegates and mimicking the TableViewController logic separation by sending out the didSelectTableAtIndexPath, UIGesture touches, etc into classes outside the ViewController to help keep your code cleaner.
User UITabBarController and hide the tab bar.
- (void)viewDidLoad
{
self.tabBar.hidden = YES;
}
Binding the segment control with method valueChanged
- (void)valueChanged:(UISegmentedControl *)seg
{
if ([seg.selectedSegmentIndex == 0]) {
self.selectedIndex = 0;
} else if ([seg.selectedSegmentIndex == 1] {
self.selectedIndex = 1;
}
}
I achieve this by this way, I hope this will help.
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 have a method that is called when a Settings button is tapped in my root view, that subclasses two UIViewControllers, attaches them to a UITabBarController and pushes the UITabBarController onto a navigation stack:
-(IBAction)onSettings:(id)sender {
// Create the Settings Views
SettingsViewController *vcSettings1 = [[Settings1ViewController alloc] initWithNibName:#"Settings1ViewController" bundle:nil];
Settings2ViewController *vcSettings2 = [[Settings2ViewController alloc] initWithNibName:#"Settings2ViewController" bundle:nil];
// Create the Tab View
UITabBarController *tabController= [[UITabBarController alloc] init];
tabController.viewControllers = #[vcSettings1,vcSettings2];
// Pass the Index of the database on to the views so they can pull the record from the database
vcSettings.recordIndex = recordIndex;
vcSettings2.recordIndex = recordIndex;
// Add the tab bar controller to the navigation stack
[self.navigationController pushViewController:tabController animated:YES];
}
In each of the Settings views, I override the viewWillAppear method to load a row of data from an sqlite database at primary key recordIndex. (Both views pull the same record and display different data from the record, except one field is the same on both.)
I also override viewWillDisappear in each view to save the controls data back to the database.
I can verify that every time I switch views using the tab bar, the viewWillDisappear method is called on one closing view and the viewWillAppear is called on the opening view.
The problem is that when I change data on the first view and switch to the second view, the data is not changed on the second view unless I return to the first view and then back to the second. As best I can tell, here is what seems to be happening:
View 1 is open. I change the data in the field.
I tap on the tab for View 2
viewWillAppear is called for View 2, populating the field in View 2 with the old data in the database.
viewWillDisappear is then called for View 1, saving the changed data to the database.
It seems that the opening view is calling viewWillAppear BEFORE the closing view is calling viewWillDisappear.
I have tried this other ways, such as using a singleton, and simply trying to modify the recordIndex from both views and in all cases it seems that the data is loaded from the opening view before it is saved from the closing view.
Is this a bug in the way UITabBarController works, or am I abusing viewWillAppear and Disappear in a way that I'm not supposed to? Has anyone else run across this behavior?
This is not a good design. There is no guarantee that viewWillAppear of new view should be called after viewWillDisappear from the previous one. Even if that were to work, there would be no guarantee that this would keep working in future iOS versions.
If performance is not impacted, a quick fix could be to save the changes to the database as they occur, you would always have an up to date database that could be accessed from any view in any circumstance.
Best design is to have some model classes, with your model objects accessible through a singleton for example. Those model objects are updated real time as you interact with the UI, they are the ones being accessed by the different views, and they are periodically saved using the method of your choice.
as a cheap solution you could load data in viewDidAppear but in general I agree with JP's answer
I have a navigation-based app that allows the user to drill down through hierarchies. Some of the child hierarchies have only one element in them, e.g.,
TopLevel1----->Level2a
TopLevel2 |->Level2b----->Level3a----->Level4a
|->Level2c
Instead of making the user tap 'Level3a', I just want to jump from Level2b to Level4a, but keep the Level3a view in the stack so when the user backtracks, it is visible.
I found some code here to simulate a row tap:
Simulate a Detail Disclosure Button press
When each level is loaded, I check to see if there is only one element in it. If so, I simulate the row tap. This all works initially, and the final view is loaded. But when I start backtracking through the view hierarchy, I get problems (it appears that the skipped views aren't loaded).
I think what I'm trying to accomplish is fairly simple, so I'm hoping someone on here can point me in the right direction.
You should be able to place a [self.navigationController pushViewController:level4 animated:NO] call in the viewWillAppear method for your level3 view controller. This will automatically push level4 on top of level3.
If it only happens some of the time, level3 can have a property to indicate when this behavior takes place.
I'm not 100% sure that would work, but that's what I would do.
You could directly [self.navigationController pushViewController:level4a animated:NO] and when that's done, set a new array of viewControllers, the navigationController propriety (an array that includes Level3a).
Here is a code sample, in you didSelectRowAtIndexPath:
[self.navigationController pushViewController:level4a animated:NO]; //Push the level 4 first
NSMutableArray* mutableViewControllers = [self.navigationController.viewControllers mutableCopy];
[mutableViewController addObject:level3a atIndex:3]; //Add the level 3 manually
self.navigationController.viewControllers = mutableViewControllers;
[mutableViewControllers release];