Is it right to switch in between UIViewController that way? - ios

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

Related

UINavigationBar with UISegmentedControl partially covers childViews

I have read many other threads on this and the Apple docs, but haven't found a solution yet for my particular problem.
My app uses a UITabBarController as the rootViewController, and in one of the tabs I have a UISegmentedControl in the navigationBar to switch between three child UITableViewControllers.
(In the real app two of the childVCs are a custom UIViewController, I'm just using three UITableViewControllers for the sample app).
The segmentedControl setup and the switching all works fine. The thing that goes wrong is that only the first UITableViewController is shown correctly. For the second and third one, part of the first cell is hidden under the navigationBar. When I click through all three, the first one is still ok.
I have made a little sample app to show what's going on, using very bright colors for demonstration purposes: https://www.dropbox.com/s/7pfutvn5jba6rva/SegmentedControlVC.zip?dl=0
Here is also some code (I'm not using storyboards):
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
FirstViewController *fvc = [[FirstViewController alloc] init];
UINavigationController *firstNavigationController = [[UINavigationController alloc] initWithRootViewController: fvc];
SecondViewController *svc = [[SecondViewController alloc] init];
UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController: svc];
// Initialize tab bar controller, add tabs controllers
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.viewControllers = #[firstNavigationController, secondNavigationController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
// FirstViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = #"One";
self.view.backgroundColor = [UIColor orangeColor];
UITableViewController *vc1 = [[UITableViewController alloc] init];
UITableViewController *vc2 = [[UITableViewController alloc] init];
UITableViewController *vc3 = [[UITableViewController alloc] init];
vc1.view.backgroundColor = [UIColor redColor];
vc2.view.backgroundColor = [UIColor blueColor];
vc3.view.backgroundColor = [UIColor greenColor];
self.viewControllers = #[vc1, vc2, vc3];
self.segmentTitles = #[#"Red", #"Blue", #"Green"];
self.segmentedControl = [[UISegmentedControl alloc] initWithItems: self.segmentTitles];
[self.segmentedControl addTarget: self
action: #selector(segmentClicked:)
forControlEvents: UIControlEventValueChanged];
self.navigationItem.titleView = self.segmentedControl;
self.segmentedControl.selectedSegmentIndex = 0;
// set the first child vc:
UIViewController *vc = self.viewControllers[0];
[self addChildViewController: vc];
vc.view.frame = self.view.bounds;
[self.view addSubview: vc.view];
self.currentVC = vc;
}
- (void)segmentClicked:(id)sender
{
if (sender == self.segmentedControl)
{
NSUInteger index = self.segmentedControl.selectedSegmentIndex;
[self loadViewController: self.viewControllers[index]];
}
}
- (void)loadViewController:(UIViewController *)vc
{
[self addChildViewController: vc];
[self transitionFromViewController: self.currentVC
toViewController: vc
duration: 1.0
options: UIViewAnimationOptionTransitionFlipFromBottom
animations: ^{
[self.currentVC.view removeFromSuperview];
vc.view.frame = self.view.bounds;
[self.view addSubview: vc.view];
} completion: ^(BOOL finished) {
[vc didMoveToParentViewController: self];
[self.currentVC removeFromParentViewController];
self.currentVC = vc;
}
];
}
So obviously my question is, why does this happen, and what can I do to fix it?
Edit: adding screenshots.
EDIT: Based on the answer below I changed the code in the animation block to:
[self.currentVC.view removeFromSuperview];
if ([vc.view isKindOfClass: [UIScrollView class]])
{
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0);
[UIView performWithoutAnimation: ^{
vc.view.frame = self.view.bounds;
((UIScrollView *)vc.view).contentInset = edgeInsets;
((UIScrollView *)vc.view).scrollIndicatorInsets = edgeInsets;
}];
}
else
{
vc.view.frame = self.view.bounds;
}
[self.view addSubview: vc.view];
Now it works. I'm going to try this with a custom UIViewController as well.
The issue is that you do not set the correct content inset to each table view. The system attempts to do it for you, but I guess your setup is too complex for it, and it only does it for the first tableview that is loaded in viewDidLoad. In your loadViewController: method, when replacing the currently displayed view, make sure to set both the contentInset and scrollIndicatorInsets to the values of the previous view. I think the system will manage to set the correct insets later, in case you rotate to landscape. Try it. If it doesn't, you will need to do it on your own in viewDidLayoutSubviews.

Swap views in a container view

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?

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.

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

push uiviewcontroller from presentModalViewController

I Show a view using presentModalViewController. and from this UIView I want to push a UIView using UINavigationController.
I tried below code for this
[self.parentViewController.navigationController
pushViewController:objViewFullScreen
animated:YES];
But it did not works for me. so please can any one suggest how I push a view from ModelViewController.
Thanks
First you have to present your modal view controller inside a navigation controller:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyNib" bundle:nil];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentModalViewController:nc animated:YES];
[vc release];
[nc release];
Then inside MyViewController you can do:
OtherViewController *vc = [[OtherViewController alloc] initWithNibName:#"MyOtherNib" bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
-(void)pushViewControllerWithCustomAnimation:(UIViewController *)newViewController {
newViewController.view.alpha = 0.0f;
[self.view addSubview:newViewController.view];
[UIView animateWithDuration:1
animations:^{
newViewController.view.alpha = 1;
}
completion:^(BOOL fin){
if (fin) {
// finally display the new viewcontroller for real
[self.navigationController pushViewController:newViewController animated:NO];
}
}];
}

Resources