Swap views in a container view - ios

I'd like to swap two custom UIViewControllers inside a ContainerView. To do this I created a ViewController that handles the logic of the swapping. This swap should occur when pressing a button.
What I want to do is similar to this. I tried the current accepted answer but the problem is that the layout doesn't show up. I don't get any compile errors or warnings.
I have this code in the ContainerViewController, executed when the button is pressed.
if([view isEqualToString:#"view1"]){
NSString *userId = [self getUserId];
ViewController1 *vc = [[ViewController1 alloc]init];
[vc someFunction:userId];
UIViewController *fromViewController = [self.childViewControllers firstObject];
[self swapViewController: fromViewController toViewController:vc];
}else{
NSString *userId = [self getUserId];
ViewController2 *vc = [[ViewController2 alloc]init];
[vc otherFunction:userId];
UIViewController *fromViewController = [self.childViewControllers firstObject];
[self swapViewController: fromViewController toViewController:vc];
}
The swapViewController function looks like this:
- (void)swapViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
toViewController.view.frame = fromViewController.view.frame;
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
}
completion:^(BOOL finished) {
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
}];
}
This is what I have in the storyboard:
If I just simply load the segue it works fine, but I need to show some data received over the internet, so I think it's not an option.
Could anyone please, give me directions on how to achieve that?

Related

How to present View Controller modally on app launch?

I've created a screen that ONLY appears the first time that my app is launched; it works great. However, I'd like to present this screen modally on top of my root view controller (EditorsNoteViewController being the screen I want to present modally on first launch). Does anyone know how I would do this? Here is what I have so far:
Appdelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"])
{
NSLog(#"not first launch");
self.viewController = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"articlesNav"];
self.window.rootViewController = self.viewController;
}
else
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"HasLaunchedOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
self.viewController = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"articlesNav"];
self.window.rootViewController = self.viewController;
EditorsNoteViewController *vc = [[EditorsNoteViewController alloc]init];
[self.viewController.navigationController presentViewController:vc animated:YES completion:nil];
}
[self.window makeKeyAndVisible];
return YES;
}
I figured out that you'll have to call
[self.window makeKeyAndVisible];
before presenting the view controller
I have read about and encountered this issue many times. A way to completely get around this issue (without any flickering or warnings) is to consider a custom container view controller that will contain your root view controller and your modally presented view controller, as child view controllers. The container view controller can then transition between the two with custom animations, with a very similar effect as present/dismiss.
Let's consider the following container view controller :
#implementation ContainerViewController {
NSMutableArray* stack;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
stack = [NSMutableArray new];
}
return self;
}
- (void) pushViewController:(UIViewController*) viewController {
UIViewController* currentViewController = [stack lastObject];
[stack addObject:viewController];
if (self.isViewLoaded) {
[self addChildViewController:viewController];
[currentViewController willMoveToParentViewController:nil];
viewController.view.frame = CGRectOffset(self.view.bounds, 0, self.view.bounds.size.height);
[self transitionFromViewController:currentViewController toViewController:viewController duration:0.3 options:kNilOptions animations:^{
viewController.view.frame = self.view.bounds;
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[currentViewController removeFromParentViewController];
}];
}
}
- (void) popViewController {
UIViewController* currentViewController = [stack lastObject];
[stack removeLastObject];
if (self.isViewLoaded) {
UIViewController* viewController = [stack lastObject];
[self addChildViewController:viewController];
[currentViewController willMoveToParentViewController:nil];
[self transitionFromViewController:currentViewController toViewController:viewController duration:0.3 options:kNilOptions animations:^{
[self.view sendSubviewToBack:viewController.view];
currentViewController.view.frame = CGRectOffset(self.view.bounds, 0, self.view.bounds.size.height);
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[currentViewController removeFromParentViewController];
}];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController* viewController = [stack lastObject];
[self addChildViewController:viewController];
[self.view addSubview:viewController.view];
[viewController didMoveToParentViewController:self];
}
#end
In your AppDelegate, you can then have something along those lines :
ContainerViewController* viewController = [ContainerViewController new];
[viewController pushViewController: [RootViewController new];
if ([self shouldPresentModalViewController]) {
[viewController pushViewController: [ModalViewController new];
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
Somewhere in the modal view controller, you can use the following code to dismiss it:
[((ContainerViewController*) self.parentViewController) popViewController];
Assuming that you have created your EditorsNoteViewController in a storyboard, you should initialise it like this:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
EditorsNoteViewController *vc = (EditorsNoteViewController*)[storyboard instantiateViewControllerWithIdentifier:#"EditorsNoteOrWhateverIdentifierYouChose"];
Try to check your NSUserDefaults in your RootViewController -viewDidLoad, and if it is the first time, present the new UIViewController there (So that you don't need to mess around with your AppDelegate).
If you want to present it animated, you can do it in -viewDidAppear instead of -viewDidLoad.

How to use animateWithDuration: when presenting view controller on iOS 7?

I'm currently using the following code in order to present my view controller.
CATransition* transition = [CATransition animation];
transition.duration = 1;
transition.type = kCATransitionFade;
transition.subtype = kCATransitionFromBottom;
[self.view.window.layer addAnimation:transition forKey:kCATransition];
[self presentViewController:viewController animated:NO completion:nil];
I need to use more complex animation utilizing UIView animateWithDuration method. It is just modal presentation and background view controller should stay there. How can I animate presenting view controller's view?
Update: next version of code :)
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:NULL];
RootViewControllerEx *viewController = (RootViewControllerEx *)[sb instantiateViewControllerWithIdentifier:#"SB_RDVC"];
viewController.transitioningDelegate = self;
viewController.modalPresentationStyle = UIModalPresentationCustom;
[self presentViewController:viewController animated:YES completion:nil];
...
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
...
}
VC in storyboard doesn't have any segue specified, it stays lonely in storyboard :)
You need to use UIModalPresentationCustom as your UIViewController's modalPresentationStyle.
You then need to create a class that conforms to the UIViewControllerTransitioningDelegate and set that your on presentedViewController.
Example
YourViewController *viewController = [[YourViewController alloc] init];
viewController.delegate = self;
viewController.transitioningDelegate = self;
viewController.modalPresentationStyle = UIModalPresentationCustom;
[self.navigationController presentViewController:viewController animated:YES completion:nil];
You must then implement the following method:
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
Which returns an object that conforms to a UIViewControllerAnimatedTransitioning protocol, that implements the following method:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *inView = [transitionContext containerView];
UIView *toView = [[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey] view];
toView.frame = //CGRectMake(0,0,0,0);
[inView addSubview:toView];
// Use whatever animateWithDuration you need
[UIView animateWithDuration:0.6f delay:0.0f usingSpringWithDamping:0.7f initialSpringVelocity:0.5f options:UIViewAnimationOptionCurveEaseIn animations:^{
// Custom animation here
} completion:^(BOOL finished) {
// IMPORTANT
[transitionContext completeTransition:YES];
}];
}
Always remember to tell the context when a transition is complete, otherwise you'll enter an unstable state.

Shift to Parent View Controller from child view controller - iOS

I have a main Actions View Controller, and on that there is button "Review". Review Button's functionality is :
- (IBAction)btnReview:(id)sender
{
ReviewViewController *vc = [[ReviewViewController alloc]initWithNibName:#"ReviewViewController" bundle:nil];
[self addChildViewController:vc];
[vc didMoveToParentViewController:self];
[self.view addSubview:vc.view];
}
Now on Review Page, I have a button. And on that button's action, I want to switch back to Parent View Controller. Its view should be displayed. I have tried following code, it either pauses or crashes application.
[self didMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
I even tried:
[self dismissModalViewControllerAnimated:YES]
I have read other posts related to this question as well but could not find a satisfactory answer. Please Help!
I'm assuming that this is a custom viewcontroller container you're trying to build, "self" is the Main Actions viewController you're talking about.
Try this:
- (IBAction)btnReview:(id)sender
{
ReviewViewController *vc = [[ReviewViewController alloc]initWithNibName:#"ReviewViewController" bundle:nil];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
vc = nil;
}
For the "Back" button, I'm assuming this is the Button to return to Main Actions viewController from the Review viewController, try this:
- (IBAction)btnReviewHide:(id)sender
{
[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
Hope this helps.
You should use delegation. When you click the button in your ReviewViewController, you call a method like: [self.delegate hideReviewController:self];
And this method would look something like:
- (void)hideReviewController:(ReviewViewController *)controller {
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
controller.view.alpha = 0;
} completion:^(BOOL finished) {
[self.view removeSubview:controller.view];
// If you want the Review Controller to be deallocated:
[self removeChildViewController:controller];
}];
}
EDIT:
In your ReviewDelegate.h file add:
#class ReviewViewController;
#protocol ReviewDelegate <NSObject>
- (void)hideReviewController:(ReviewViewController *)controller;
#end
Then add this property:
#property (nonatomic, weak) id <ReviewDelegate> delegate;
In the parent class:
- (IBAction)btnReview:(id)sender
{
ReviewViewController *vc = [[ReviewViewController alloc]initWithNibName:#"ReviewViewController" bundle:nil];
vc.delegate = self;
[self addChildViewController:vc];
[vc didMoveToParentViewController:self];
[self.view addSubview:vc.view];
}
- (void)hideReviewController:(ReviewViewController *)controller {
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
controller.view.alpha = 0;
} completion:^(BOOL finished) {
[self.view removeSubview:controller.view];
// If you want the Review Controller to be deallocated:
[self removeChildViewController:controller];
}];
}
I also suggest that you read about delegation
Try this
self.view=nil;
[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
You don't need to make it complex... Just use UINavigationController.
To navigate from ParentVC to ChildVC :
ChildViewController *ChildVC = [[ChildViewController alloc]initWithNibName:#"ChildViewController" bundle:nil];
[self.navigationController pushViewController:ChildVC animated:YES];
And for navigating back from ChildVC to ParentVC :
ParentViewController *ParentVC = [[ParentViewController alloc]initWithNibName:#"ParentViewController" bundle:nil];
[self.navigationController popViewControllerAnimated:YES];
Since you are adding the Child View as a Subview on to Parent you need to manually it from its superview (in your case parentview). Rather then adding it as a subview you can make use of presentViewController method as follow:
- (IBAction)btnReview:(id)sender
{
ReviewViewController *vc = [[ReviewViewController alloc] initWithNibName:#"ReviewViewController" bundle:nil];
[self presentViewController:vc
animated:YES
completion:nil];
}
and in the child class code for back button will be :
-(IBAction)backButtonClicked:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Sounds like what you're looking for could be taken care of with a UINavigationController. Just use pushViewController: to push the new view onto the screen. Then, a back button will appear in the navigation bar atop the view, which can be used to return to the "parent" view controller.
See the documentation.
writing only
- (IBAction)backbutton:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
worked in my case

iOS UIViewAnimation not working

I have a master detail application that when the detail view appears and a string is empty, I want it to present a new view through a UIViewAnimationFlip. The animation is working, but it keeps flipping to itself, not the view controller I initiated. Any help would be great!
- (void)viewDidAppear:(BOOL)animated {
if (masterView.parserURL == nil) {
LoginViewController *login = [[LoginViewController alloc] init];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.8];
[UIView setAnimationTransition:UIModalTransitionStyleFlipHorizontal
forView:self.view
cache:YES];
[self.navigationController presentViewController:login
animated:YES
completion:nil];
[UIView commitAnimations];
}
}
I agree that you should be doing this modally, instead of just adding a subview. In your example code you are animating twice, because the presentviewcontroller method is animating itself already. Try removing the other animation code as follows:
LoginViewController *login = [[LoginViewController alloc] init];
login.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:login
animated:YES
completion:nil];
I'm suggesting simply:
- (void)viewDidAppear:(BOOL)animated {
if (masterView.parserURL == nil) {
LoginViewController *login = [[LoginViewController alloc] init];
login.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:login
animated:YES
completion:nil];
}
}
Note, there's an interesting question of how the login screen is supposed to update that parserURL field in masterView. You might add a property to your login controller that is a pointer to masterView, so that it has a mechanism to update the parserURL. Thus it might be like:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (masterView.parserURL == nil)
{
LoginViewController *login = [[LoginViewController alloc] init];
login.masterView = masterView;
login.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:login
animated:YES
completion:nil];
}
}
Then your login controller can now update the parserURL via:
self.masterView.parserURL = ... // set it as appropriate

Is it right to switch in between UIViewController that way?

I want to use my own controller for navigating in between uiviewcontrollers. So like that I can have custom transitions in between my views.
I did something which is working but I'm not sure if it is the right way? Could there be problems related to memory later?...I'm not sure everything is deallocated the right way.
Thanks for your help!
Here is my code:
In my AppDelegate, when it starts:
self.rootVC = [[[MenuView alloc] initWithNibName:#"MenuView" bundle:nil] autorelease];
[self.window addSubview:self.rootVC.view];
In my AppDelegate, the method for switching in between UIViewController:
-(void) changeRootController: (NSString*) ctrlName{
UIViewController *oldVC = self.curVC;
UIViewController *newVC;
if(ctrlName == #"Studio"){
newVC = [[StudioView alloc] initWithNibName:#"StudioView" bundle:nil];
}
else if(ctrlName == #"Menu"){
newVC = [[MenuView alloc] initWithNibName:#"MenuView" bundle:nil];
}
self.curVC = newVC;
[UIView transitionWithView:self.window
duration:1.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
newVC.view.transform = CGAffineTransformConcat(newVC.view.transform, CGAffineTransformMakeTranslation(-1024, 0));
[self.rootVC.view addSubview:newVC.view];
newVC.view.transform = CGAffineTransformConcat(newVC.view.transform, CGAffineTransformMakeTranslation(1024, 0));
oldVC.view.transform = CGAffineTransformConcat(oldVC.view.transform, CGAffineTransformMakeTranslation(1024, 0));
}
completion:^(BOOL finished){
if(finished){
[oldVC release];
}
}
];
}
And I switch views in my UIViewController as follow:
AppDelegate *ad = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[ad changeRootController:#"Studio"];
newVC isn't released.
The block should retain the newVc so after your transitionWithView
you should call. (last line of function)
[newVC autorelease];
Thats assuming that the self.curVC property is strong OR retained

Resources