What's the difference between currentContext and overCurrentContext? - swift

The docs for both currentContext and overCurrentContext have an identical description.
The differences I can spot are:
since when they are available:
#available(iOS 3.2, *)
case currentContext
#available(iOS 8.0, *)
case overCurrentContext
With currentContext, after dismissing the presented view controller, viewWillAppear of the covered view controller gets called (which doesn't have to be the view controller that provides the context). With overCurrentContext it does not get called.
Is there any other difference?

It's exactly the same as the difference between fullScreen and overFullScreen. The first removes the view from the area it covers; the second doesn't, it merely covers it.
(In both cases, this difference is easy to see visually if your presented view controller's main view's background color has some transparency. But the differences for behavior are even more important; some views become unhappy when they are abruptly ripped out of the view hierarchy, so it could be better to leave them in place and use the .over variant.)
With currentContext, after dismissing the presented view controller, viewWillAppear of the covered view controller gets called (which doesn't have to be the view controller that provides the context). With overCurrentContext it does not get called.
That's just a consequence of what I just said. With over, viewWillAppear is not called on dismissal because the view never disappeared in the first place; it just sat there behind the presented view. Again, that's completely parallel to fullScreen and overFullScreen.

Related

Transition between view controllers and rotation in iOS

Consider a container view controller with two child view controllers (A and B), both added with addChildViewController:. Then:
A.view is added to the container view
B is displayed by doing transitionFromViewController from A to B. B receives viewWillLayoutSubviews and all is good with the world.
The device rotates while displaying B. Only B receives the rotation calls (willRotateToInterfaceOrientation: et all).
A is displayed by doing transitionFromViewController from B to A. A doesn't receive viewWillLayoutSubviews and thus the layout is broken.
Is this the expected behavior? If not, what might I be doing wrong? If yes, what should I do to notify A of the rotation change while displaying B?
As soon as you call addChildViewController: you are now a View Controller Container Implementer. This means you do have to do a little more work than a standard presentation call like presentViewController... This includes dealing with the frames of the views of the controllers you add as children, as your question suggests you might have expected.
For example, to implement a super basic example container, that just shows each child full screen, you could do something like this.
-(void)swapChildVCFrom:(UIViewController *)from to:(UIViewController *)to{
[self addChildViewController:to];
[from willMoveToParentViewController:nil];
// Adjust the new child view controller's view's frame
// For example here just set it to fill the parent view
to.view.frame = self.view.bounds;
[self transitionFromViewController:from
toViewController:to
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:nil
completion:^(BOOL b){
[to didMoveToParentViewController:self];
[from.view removeFromSuperview];
[from removeFromParentViewController];
}];
}
After A has been loaded and displayed, viewWillLayoutSubviews will not be called again unless A.view's bounds change. If you need your implementation to run each time the view comes on screen, you can try moving that code to viewWillAppear.
There are three solutions that I can think of:
Add all of your container's view controller's up front, and add their views, but hide all but the first one. Then in your method that transitions between the view controllers, you don't have to worry about the myriad containment related methods anymore (e.g. you don't need willMoveToParentViewController, addChildViewController, removeFromParentViewController, or didMoveToParentViewController), but rather just use the UIViewAnimationOptionShowHideTransitionViews option which will show or hide the appropriate views. This way, all the controllers get all the rotation notifications.
The second logical alternative would be to create only the first view controller up front. Then, when you want to transition to the second view controller, only then would you create it (and it's view). And make sure you don't keep a strong reference to the original controller (e.g. not only your own properties, but make sure to removeFromParentViewController, etc.). That will let it, and it's views, be deallocated. Thus, when you transition back, it will be recreated.
If you're creating your view controllers up front, but letting transitionFromViewController to add and remove their respective views to your container, then I think you'll probably have to invoke the appropriate child rotation events, like discussed in the Customizing Appearance and Rotation Callback Behavior in the View Controller Programming Guide.

How could a UIVIew know its child has committed suicide

I have a quick question:
View Controller X has added a sub-view: View Controller Y.
Y has a "quit" button. When the button is tapped, it removes its view from X's view hierarchy.
But I found Y's 'viewDidUnload' method is not called and its memory can't be released. How could Y release itself properly, or how could X know that Y is gone?
I believe I can make a protocol and let Y call a method from X, but I hope there is a easier way.
So you want the Y view controller to get cleaned when its view is closed?
Prior to iOS5 there's nothing fancy to support more than one UIViewController on screen at once, so you basically how to make the calls yourself.
You could notify X when Y is closed (you could use NSNotificationCenter, or a delegate), and X would set Y's view to nil which would cause the viewDidUnload method to be called in it.
Additionally you could even release the Y view controller, which would dealloc every instance it has (along with the view itself) if you're not going to show it again.
As you have added View Controller Y in View Controller X by coding...
[viewX.view addSubView:viewY.view];
So, the viewDidLoad method wont going to execute. And, as far as memory management is concerns, you can code for that in viewX.m, i.e. as the "quit" has been tapped...
[viewY removeFromSuperView];
[viewY released];
If you only need to know when a subview has been removed from its parent view, implement didAddSubview: and/or willRemoveSubview:. (See here in the documentation)
If you want to release the viewController when removing the view, you need to... release the viewController when you remove the view: [viewController.view removeFromSuperview]; [viewController release]; viewController = nil;
The viewDidUnload method is only called when there is a memoryWarning being issued by iOS, and that the ViewController then automatically unload its view (if it is not onscreen, e.g. the ViewController is not the topmost of a NavCtrl's stack so its view is not visible) to release some memory (and will then re-load the view from your XIB and recall viewDidLoad when it needs to reload the view to display it again).
You should not have 2 UIViewControllers on screen at the same time. There is a whole range of problems associated with that and it is not how they are intended to be used.
A single UIViewController should manage all subviews associated with a single screen. If you want a "child" view subclass UIView and add it as a subview.
To allow to switch between view controllers easily, add a UINavigationController as the root view controller, and push/pop view controllers via that. You can then change screens very easily. Remember - you can turn of the navbar and/or animation you get no visual indication that your app is using a navigation controller, but full benefit of it's ability to manage a stack of views and ensure all the correct notifications are sent to each view controller as it loads/appears/disappears/unloads.
If you get in the habit of building every app with a UINavigationController as the root view controller things become very simple.

Presenting a ViewController

Does presenting a ViewController cause the presented controller to run its viewDidLoad method?
If a view1 is loaded and another is presented. Then something triggers to present view1. Will it run through its viewDidLoad method?
IF not how should this be done? ViewDidAppear?
Building on what Jesse relayed, the viewDidLoad is called when the view is loaded into memory (usually the first time the view controller is ever about to be presented since app launch - simplified, but this will suffice for now).
When you display other view controllers and then come back some how to this original "view1" view controller, unless there was a memory event that jettisoned it from memory, it will NOT call viewDidLoad again.
Instead, it will call the following, in order:
viewWillAppear:
viewDidAppear:
In viewWillAppear:, you have a place to do things "off screen" before your view controller is displayed.
In viewDidAppear:, you can do additional operations that are appropriate for when the view controller's view is already visible. For example, you want to run some little animation that the user will see once the view controller is fully visible.
In both of these methods, make sure you call super's implementation before you do anything. Also, to learn about this lifecycle, set a breakpoint or NSLog() statement in each of these methods (viewDidLoad, viewWillAppear, viewDidAppear) to see when they are called.
There's a concept piece in the Apple docs on View Controllers that is worth the 20 minute read - it'll clear up a lot of this key life cycle information about View Controllers, and these are central to iOS development. See the section "Understanding the View Management LifeCycle" at:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html%23//apple_ref/doc/uid/TP40007457-CH101-SW1
viewDidLoad is only called when the view is loaded into memory. Usually the first time it appears (could be more often if there are memory dumps and etc).
viewDidAppear: is called every time the viewController's view becomes the 'active' view in the window.

Can you cache UIViewControllers?

I have an application with 8 UIViewControllers presented by navigating from left/right/up/down. They are created on start of the app and kept in an NSArray. Only 1 of them is added to the view tree (addSubview:) at any time - the rest just sit in the cache until they are needed.
Now the problem I am having is when rotating to Landscape. Only the currently visible view changes the bounds.size to Landscape. When navigating to the next view, that view still thinks it is in Portrait (but the containing views will all look in Landscape).
By the way, the view hierarchy of the app is the following: UIWindow -> Main UIViewController -> 1 of the 8 cached UIViewControllers.
For example:
* Change orientation:
- Main UIViewController.view.bounds.size = 480x300 (OK)
- One of the cached UIViewControllers view.bounds.size = 480x300 (OK)
* Go to next view:
- Main UIViewController.view.bounds.size = 480x300 (OK),
- Another of the cached UIViewControllers view.bounds.size = 320x460 (??)
Not sure whats going on. Do I have to tell the cached UIViewControllers somehow that the orientation/size changed or something else?
Thanks a lot
Yes you can. Optimally you should purge the view that each view controller manages when it goes off screen.
Simply assign nil to the view property of the view controller that is being replaced. This will free all resourced used by the view that is not visible anyway. As an added bonus the view is then recreated with proper frame whenever you decide to brin a particular viw controller in front again.
I am also assuming that what you are implementing is a subclass of UIViewController that acts as a container for other view controllers. Like a sibling to UITabController named CWGridController or similar. Noe that it is the responsibility of the the parent view controller (your subclass) to size the frame of it's child view controllers views as needed.
Creating a container view controller is not a small task, and the support for doing it is not complete from Apple. There are a few things you must do including but not limited to:
Forward all orientation change calls to all child view controllers.
Properly call all appear/disappear on child view controller as needed.
The last part to make it work is to break some rules. View controllers works very badly unless they know about their parent view controller, layout will be wrong and modal view controller behaves strange. Unfortunately the parentViewController is readonly, or so you would think. Setting it anyway using KVC will solve most problems, and Apple does not seem to object when you submit to App Store:
[childViewController setValue:self forKey:#"parentViewController"];
In viewWillAppear you will have to check the interface orientation and set the frames accordingly.

How to tell if view has appeared via popping or not?

Using a UINavigationViewController, how do I find out how a view has appeared?
The view has either appeared in a straightforward manner, as the first view in the UINavigationController stack. Or it has appeared because a second view has been popped and the first view has revealed itself again. How do you find out which of these happened?
The only reliable way to do this, as far as I'm aware, is to subclass UINavigationController and override the UINavigationBarDelegate methods:
– navigationBar:shouldPushItem:
– navigationBar:didPushItem:
– navigationBar:shouldPopItem:
– navigationBar:didPopItem:
Don't forget to call super, of course.
Simple approach is to add a property to your RootViewController to track whether or not it has pushed another view onto the navigationController.
-(BOOL)hasPushedSecondView;
Initialize to NO in your init method.
Before pushing secondViewControllers view onto the stack, update the property to YES.
In viewWillAppear, check the value and update your view accordingly. Depending on how you want the application to behave you may need to reset the hasPushedsecondview property back to NO.
you could take a look at the leftBarButtonItem or backBarButtonItem, based on how your application is written and determine how the view appeared. If it is on top, unless you have a custom leftBarButtonItem, there would be no object there.
You can determine this directly via a couple of methods on your UIViewController subclass.
From Apple's documentation:
Occasionally, it can be useful to know why a view is appearing or
disappearing. For example, you might want to know whether a view
appeared because it was just added to a container or whether it
appeared because some other content that obscured it was removed. This
particular example often appears when using navigation controllers;
your content controller’s view may appear because the view controller
was just pushed onto the navigation stack or it might appear because
controllers previously above it were popped from the stack.
The UIViewController class provides methods your view controller can
call to determine why the appearance change occurred.
isMovingFromParentViewController: view was hidden because view controller was removed from container
isMovingToParentViewController: view is shown because it's being added to a container
isBeingPresented: view is being shown because it was presented by another view controller
isBeingDismissed: view is being hidden because it was just dimissed