I have a UIViewController that has UITabBar in it. I am trying to imitate a UITabBarController.
My question is how do I set or a UIViewController whenever a TabBarItem is selected?
I am confused as to how to put a UIViewController inside my UIViewController that is trying to imitate a UITabBarController.
Please don't ask me to use a UITabBarController
You can use child view controllers to embed view controllers in other view controllers, just call this from your view controller:
YourViewController *childViewController = [[YourViewController alloc] init];
UIView *containerView = //some view in your view hierarchy
childViewController.view.frame = containerView.bounds;
[self addChildViewController: childViewController];
[containerView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
If you want to page between child view controllers, you can use a UIPageViewController as the root child view controller or alternatively borrow this code from the apple documentation:
- (void) cycleFromViewController: (UIViewController*) oldC
toViewController: (UIViewController*) newC {
[oldC willMoveToParentViewController:nil]; // 1
[self addChildViewController:newC];
newC.view.frame = [self newViewStartFrame]; // 2
CGRect endFrame = [self oldViewEndFrame];
[self transitionFromViewController: oldC toViewController: newC // 3
duration: 0.25 options:0
animations:^{
newC.view.frame = oldC.view.frame; // 4
oldC.view.frame = endFrame;
}
completion:^(BOOL finished) {
[oldC removeFromParentViewController]; // 5
[newC didMoveToParentViewController:self];
}];
}
(1) To learn it in detail, I would suggest you to go through:
Creating Custom Container View Controllers - https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html#//apple_ref/doc/uid/TP40007457-CH18-SW6
And
WWDC 2011 Session Video - Session 102 - Implementing UIViewController Containment.
(2) For quick learning, go through: Using Multiple ViewControllers on a Single Screen in iOS
Demo project at git: multiple-viewcontrollers
Related
When I am creating a custom segue from one view controller to another, there's a problem with showing top bar properly.
This is the code for custom segue:
#implementation CustomSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
[sourceViewController.view addSubview:destinationViewController.view];
destinationViewController.view.alpha = 0.0;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
destinationViewController.view.alpha = 1.0;
}
completion:^(BOOL finished){
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL];
[destinationViewController.view removeFromSuperview];
}];
}
#end
I am using storyboard to set this up and sourceViewCotoller is embeded in NavigationViewController.
After performing a segue, there is no top bar in th destinationViewController.
I found an answer that I should add a Navigation Item to the destinationViewController - doesn't help.
Another way of dealing with this that I found is embedding the destinationViewController in NavigationViewController.
After doing this, the top bar is visible, but also during the segue.
It's probably because I am adding the desitnationViewController into sourceViewController, so at the beginning of segue I have 2 top bars visible one below another.
How to deal with this ?
I'm writing a custom UIStoryboardSegue to animate the transitions between views. When a segue is triggered, each controller (source and destination) is asked to animate accordingly. Here's how:
- (void)perform
{
UIViewController * sourceViewController = [self sourceViewController];
UIViewController * destinationViewController = [self destinationViewController];
UIViewController * containerViewController = [self containerViewController];
UIView * sourceView = sourceViewController.view;
UIView * destinationView = destinationViewController.view;
UIView * containerView = containerViewController.view;
// The container view is as large as the union of the two other views
CGRect sourceFrame = sourceView.frame;
CGRect destinationFrame = destinationView.frame;
CGRect containerFrame = CGRectUnion(sourceFrame, destinationFrame);
[containerView setFrame:containerFrame];
// The two views are presented in a container view; this way no one fights about who gets what
[sourceView removeFromSuperview];
[containerView addSubview:sourceView];
[containerView addSubview:destinationView];
// [sourceViewController presentViewController:containerViewController animated:NO completion:^{
// NSLog(#"done presenting");
// }];
// NSLog(#"done calling presenting");
id <HyTransition> presentingTransition = [self presentingTransitionForViewController:destinationViewController];
id <HyTransition> dismissingTransition = [self dismissingTransitionForViewController:sourceViewController];
// Perform the transitions
[presentingTransition performWithTransitionContext:self.presentingContext];
[dismissingTransition performWithTransitionContext:self.dismissingContext];
}
The problem is when presenting the containing view. It works well if the source view controller is a standard view controller, but not if it's UINavigationController or others of the kind, which don't add their views to the view hierarchy. When doing so, I get this:
Warning: Attempt to present <HyPresentationViewController: 0x7fdac2d34b60> on <HyRootController: 0x7fdac2e28590> whose view is not in the window hierarchy!
How can I present the containing view?
Edit: notice that I do not require a containing view controller - a containing view is enough.
I am building an application using UISplitViewController as my root view controller (as prescribed by Apple). However, I needed a custom view for login / management to be displayed prior to the UISplitViewController, so I created a custom UIStoryboardSegue that calls some custom animations. I am attempting to recreate the push / pop segues through a modal segue, without actually pushing an popping view controllers. I've implemented everything correctly, however, at the end of my animation I have a flicker. Here is a gif of it:
Here is my custom Segue's code:
- (void)perform {
UIViewController *srcViewController = (UIViewController *) self.sourceViewController;
UIViewController *destViewController = self.destinationViewController;
UIView *prevView = srcViewController.view;
UIView *destView = destViewController.view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
[window insertSubview:destView aboveSubview:prevView];
[destView enterRight:0.1 then:^{
[destView removeFromSuperview];
[srcViewController.presentingViewController dismissViewControllerAnimated: NO completion:nil];
}];
}
And here is my custom animation (Implemented as a category on UIView):
-(void)enterRight:(float)delay then:(void(^)(void))after
{
CGPoint moveTo = self.center;
CGPoint moveFrom = self.center;
// Grab a point from off the screen
CGFloat simpleOffscreen = [UIScreen mainScreen].bounds.size.width;
// come from off the right side (+)
moveFrom.x = moveFrom.x + simpleOffscreen;
self.center = moveFrom;
self.hidden = NO;
[UIView animateWithDuration:0.5
delay:delay
usingSpringWithDamping:1
initialSpringVelocity:0.1
options:UIViewAnimationOptionCurveEaseIn
animations:^
{
self.center = moveTo;
}
completion:^(BOOL finished)
{
if (after) after();
}
];
}
As you can see in the Segue, I am animating the view into the current view controller, then without animation presenting the actual destination view controller. I think this is where the flicker is introduced, yet I am unsure about how to go about preventing this.
My Storyboard for this custom segue is
Anyone know how to implement this?
I'm using a custom rootViewController, and am using the code below to access it from any UIViewController
#implementation UIViewController(CustomRootVC)
- (CustomRootVC*)customViewController
{
UIViewController *parent = self;
Class customClass = [CustomRootVC class];
while ( nil != (parent = [parent parentViewController]) && ![parent customClass] )
{
}
return (id)parent;
}
#end
If I call self.customViewController on viewDidLoad I get nil. If I call it on willAppear I get the reference I expect.
I'm guessing this is something to do with the order I add the view controllers to my view controller container (i.e. viewDidLoad is called before the view controller has been added to customViewController and so it isn't a parent), but I can't spot anything obvious. I add the view controllers as follows:
- (void)addViewController:(UIViewController*)controller toWrapper:(PLSliderView*)wrapper{
[self addChildViewController:controller];
[self addView:controller.view ToWrapper:wrapper];
[self.viewControllers addObject:controller];
[controller didMoveToParentViewController:self];
}
In particular, the issue seems to be with adding a new view controller and view as follows:
- (void)replaceTopOfStackWithViewController:(UIViewController *)newController animated: (BOOL)animated {
UIViewController *oldController = self.currentController;
[self addChildViewController:newController];
[self transitionFromViewController:oldController
toViewController:newController
duration:1.0
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:^(BOOL finished) {
[self.rightViewController didMoveToParentViewController:self];
[self removeViewController:oldController];
[self queryDimensions:#"REPLACE"];
[self.view setUserInteractionEnabled:YES];
self.currentController = newController
}];
}
The view controller hasn't been added to the view controller hierarchy at the time viewDidLoad is called, hence it has no parent view controller, hence your function returns nil as expected.
this piece of code (parent = [parent parentViewController]) sets parent equal to self.parentViewController. If self does not have a parent view controller at the time this is called, it would return nil as expected.
more broadly, I'm not sure what you're trying to accomplish with this code, or why you need to use a category. it seems like that sort of behavior would make more sense in a UIViewController subclass.
I'm implementing a custom segue using controller containment API, e.g.
#implementation CustomSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
[sourceViewController addChildViewController:destinationViewController];
destinationViewController.view.alpha = 0.0;
[sourceViewController.view addSubview:destinationViewController.view];
[UIView animateWithDuration:0.3 animations:^{
destinationViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[destinationViewController didMoveToParentViewController:sourceViewController];
}];
}
#end
View controller hierarchy is trivial: sourceViewController → destinationViewController
When unwinding from the destinationViewController to the sourceViewController, app crashes in [UIStoryboardUnwindSegueTemplate _perform:] with exception Could not find a view controller to execute unwinding for <…>
I did not implement custom -[UIViewController viewControllerForUnwindSegueAction:fromViewController:withSender: or
-[UIViewController canPerformUnwindSegueAction:fromViewController:withSender: which means framework returns correct values (although I implemented it once to check).
When replacing my custom addChildViewController:… code with presentViewController:… in the segue, it works fine: unwinding performs like expected.
The question: is it possible to have a custom segue that creates a custom view controller hierarchy?
Test case project: https://bitbucket.org/zats/unwind/
I think that George Green's comment is relevant, and the way you have your controllers set up is the cause of the crash. I think to do what you want, you should have ZTSFirstViewController added as a child to a custom container controller which will do the unwinding, and the (forward) segue will exchange the children of that custom container controller (switch from ZTSFirstViewController to ZTSSecondViewController). The viewControllerForUnwindSegueAction:fromViewController:withSender: method needs to be implemented in the custom container controller (ViewController in my example). I tested this by adding a container view in IB to ViewController, and changed the class of the embedded controller to ZTSFirstViewController. I added a segue from a button in ZTSFirstViewController to ZTSSecondViewController, and connected the unwind segue from a button in that controller. The unwind: method is in ZTSFirstViewController. The code in the custom segue was this,
- (void)perform {
UIViewController *destinationViewController = self.destinationViewController;
UIViewController *sourceViewController = self.sourceViewController;
if (!self.unwind) {
[sourceViewController.parentViewController addChildViewController:destinationViewController];
destinationViewController.view.alpha = 0.0;
[sourceViewController.parentViewController.view addSubview:destinationViewController.view];
[UIView animateWithDuration:0.3 animations:^{
destinationViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[destinationViewController didMoveToParentViewController:sourceViewController.parentViewController];
}];
} else {
[self.sourceViewController willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3 animations:^{
sourceViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[sourceViewController.view removeFromSuperview];
[sourceViewController removeFromParentViewController];
}];
}
}
I kept this segue close to your implementation -- it doesn't actually switch the controllers, it just adds the second one as a second child and hides the view of the first.