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.
Related
What I'm trying to achieve is to design my "shared" part of UI in interface builder and use child view controllers to show content to the user. This may sound as trying to reinvent UINavigationController, but it is not. In fact, the whole thing is embedded in one.
It looks like this :
Now, what I'm trying to do is change child view controllers of this BaseViewController and indicate this change in the navigation bar, so that all of its functionality remains.
I tried adding such a method :
+ (UIViewController *)baseViewControllerWithChild:(UIViewController *)child {
BaseViewController *base = [BaseViewController new];
[base addChildViewController:child];
child.view.frame = base.childViewControllerContainer.frame;
[base.view addSubview:child.view];
[child didMoveToParentViewController:base];
return base;
}
and then using it like this :
- (void)didTouch:(UIButton *)sender {
[self.parentViewController.navigationController pushViewController:[BaseViewController baseViewControllerWithChild:[DummyViewController new]] animated:YES];
}
(Note : DummyViewController is exactly that - a dummy vc, made just for testing, it only has background color set in viewDidLoad)
This method is a handler of a button in first child view controller. So far so good. Unfortunately, the result is not as expected - the pushed view controller is black. At firs I thought this was because BaseViewController was designed in storyboard and initially set as rootViewController of navigation controller. Moving it to a xib file and setting from code didn't quite work for me, as you cannot add a Container View in a xib.
To summarise, I would like to have a base design governed by BaseViewController class and content would be added as a childViewController of it. Pushing a new view controller would be a result of an action on these childViewController and should update the navigation stack accordingly.
Also, the whole thing needs to work with iOS 7.
Any help as to how to try to achieve this is greatly appreciated!
The issue was casued by base.childViewControllerContainer being nil - this was caused byt he fact that view property of view controllers is loaded lazily. Adding [base view] before accesing base.childViewControllerContainer solved the issue, though I'm not sure if this is the one, only and best way to do this.
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 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)
Just to clarify things, I don't want to use UITabBarController. I need to do some custom changes to the UITabBar that can't be done using UITabBarController. (like making it scroll etc')
This is how I've created my UITabBar
From the Interface Builder I've dragged a UITabBar and located it inside a ViewControllers.
Connected the delegate and outlet.
Added UITabbarItem tags and segue identifier.
and used this code:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if (item.tag==0) {
[self performSegueWithIdentifier:#"Favorite" sender:nil];
}
else if (item.tag==1)
{
[self performSegueWithIdentifier:#"Item" sender:nil];
}
}
My problem is that when I push a new ViewController the UITabBar disappears.
My question is, what's the right proper way to keep the UITabBar on the pushed ViewController and other ViewControllers ?
I've tried passing it to the next view controller using PrepareForSegue and it works but when I go back to my previous controller I need to reset the UITabBar frame etc'. I guess I can keep it as a global object inside my Singleton and keep adding it to new ViewControllers but that sounds like an over kill
Is there a better way to do it without using a UITabBarController ?
Even if you don't want to use a tab bar controller, you should still follow the same design pattern. Your ScrollableTabBarController should be a container view controller, and when different tab items are selected, it should add the new item as a child view controller. Read the view controller containment documentation for more details.
At the moment it sounds like you're pushing view controllers on top of your container, which suggests that your storyboard is based on a navigation controller. This is the wrong way to do it.
I'm not sure how straightforward it is to do custom container controllers in the storyboard, (I'd do it in code). You may have to make the connections manually rather than via segues.
What I want is to have two UITabBarItems in my UITabBar (thats not really the problem...).
So normally the first item has his own an UIViewcontroller and the second item has his own UIViewcontroller.
But I want that each TabBarItem shows the same UIViewcontroller instance.
(the functionality is nearly the same, only one label is different between those two viewcontrollers)
So I think I have to push the last viewController on top of the tabBarItemStack right after the user pushed the second tabBarItem, right ?
At the moment I'm using a StoaryBoard with two UIViewcontroller, so I really don't know how I can access the TabBarItemStack and where do I get the notification that the next tab is pushed by the user ?
Or can I alter the stack after loading the first view and push the current view on the second index of the stack ?
Hope I was able to explain my problem so anybody would understand ;-)
Thanks and Regards,
I wouldn't use the UITabBar at all in this case. I would make some GUI-object in my viewcontroller that looks like two tabs, but is acually just 2 buttons.
When you click them you switch their look so that it looks like you have switched tabs, by changing the images of the buttons. But you are always staying in the same viewcontroller all the time. And you just change the content in it.
Then you can keep track class which "tab" the user has selected by using member variables and that way you know which content to show.
The UITabBar is most useful when you have an unknown amount of tabs and you don't know exactly what they will contain. There are many times it is a lot easier to not use the UITabBar and just images/buttons with "tab-looking" layout, even when you have more than one viewcontroller.
Could you have two UIViewControllers (one for each UITabBarItem), but they are both inherited (descended) from a third (Which contains all the logic). That way you are not duplicating the code, and not faffing with the hierarchy, such that you might introduce bugs?
Load same UIViewController in both TabBar. I assume u need to hide label in first tabBar and show in second tabBar
Now in viewWillAppear Method add this code:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(self.tabBarController.selectedIndex == 0)
{
yourLabel.hidden = YES;
}
else if(self.tabBarController.selectedIndex == 1)
{
yourLabel.hidden = NO;
}
}