Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Kugler committed Mar 1, 2014
0 parents commit f7ae8ca
Show file tree
Hide file tree
Showing 66 changed files with 15,821 additions and 0 deletions.
170 changes: 170 additions & 0 deletions 2013-06-07-containment-view-controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
layout: post
title: "View Controller Containment"
category: "1"
date: "2013-06-07 08:00:00"
author: "<a href=\"https://twitter.com/rickigregersen\">Ricki Gregersen</a>"
tags: article
---


Before iOS 5, view controller containers were a privilege for Apple only.
In fact, there was a passage in the view controller programming guide
stating that you should not use them.
The general advice from Apple on view controllers used to be that <q>a
view controller manages a screenful of content</q>.
This has since changed to <q>a view controller manages a self-contained
unit of content</q>.
Why didn't Apple want us to build our own tab bar controllers and
navigation controllers? Or put more precisely, what is the problem with:

[viewControllerA.view addSubView:viewControllerB.view]

<img src="{{site.images_path}}/issue-1/[email protected]" style="width:357px" alt="Inconsistent view hierarchy"/>

The UIWindow, which serves as an app's root view, is where rotation and
initial layout messages originate from. In the
illustration above, the child view controller, whose view was inserted
in the root view controller view hierarchy, is excluded from those events.
View event methods like `viewWillAppear:` will not get called.

The custom view controller containers built before iOS 5 would keep a
reference to the child view controllers and manually relay all the
view event methods called on the parent view controller, which is
pretty difficult to get right.

## An Example

When you were a kid playing in the sand, did your parents ever tell you that if you kept digging with your little shovel you would end up in China?
Mine did, and I made a small demo app called *Tunnel* to test this claim.
You can clone the [GitHub
repo](https://github.com/RickiG/view-controller-containment) and run the
app, which will make it easier to understand the example code.
*(Spoiler: digging through the earth from western Denmark lands you somewhere in the South Pacific Ocean.)*

<img src="{{site.images_path}}/issue-1/[email protected]" alt="Tunnel screenshot" style="width: 513px;" />

To find the antipode, as the opposite location is called, move the little guy with the shovel around and the map will tell you where your exit location is. Tap the radar button and the map will flip to reveal the name of the location.

There are two map view controllers on screen. Each of them has to deal with dragging, annotation, and updating the map.
Flipping these over reveals two new view controllers which reverse geocode the locations.
All the view controllers are contained inside a parent view controller, which holds their views, and ensures that layout and rotation behaves as expected.

The root view controller has two container views. These are added to
make it easier to layout and animate the views of child view
controllers, as we will see later on.

- (void)viewDidLoad
{
[super viewDidLoad];

//Setup controllers
_startMapViewController = [RGMapViewController new];
[_startMapViewController setAnnotationImagePath:@"man"];
[self addChildViewController:_startMapViewController]; // 1
[topContainer addSubview:_startMapViewController.view]; // 2
[_startMapViewController didMoveToParentViewController:self]; // 3
[_startMapViewController addObserver:self
forKeyPath:@"currentLocation"
options:NSKeyValueObservingOptionNew
context:NULL];

_startGeoViewController = [RGGeoInfoViewController new]; // 4
}

The `_startMapViewController`, which displays the starting position, is instantiated and set up with the annotation image.

1. The `_startMapViewcontroller` is added as a child of the root view
controller. This automatically calls
the method `willMoveToParentViewController:` on the child.
2. The child's view is added as a subview of the first container view.
3. The child is notified that it now has a parent view controller.
4. The child view controller that does the geocoding is instantiated,
but not inserted in any view or controller hierarchy yet.



## Layout

The root view controller defines the two container views,
which determine the size of the child view controllers.
The child view controllers do not know which container they will be
added to, and therefore have to be flexible in size:

- (void) loadView
{
mapView = [MKMapView new];
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
[mapView setDelegate:self];
[mapView setMapType:MKMapTypeHybrid];

self.view = mapView;
}

Now they will layout using the bounds of their super view. This increases the reusability of the child view controller; if we were to push it on the stack of a navigation controller, it would still layout correctly.

## Transitions

Apple has made the view controller containment API so fine-grained that
it is possible to construct and animate any containment scenario we can
think of.
Apple also provides a block-based convenience method for exchanging two controller views on the screen.
The method
`transitionFromViewController:toViewController:(...)`
takes care of a lot of the details for us:

- (void) flipFromViewController:(UIViewController*) fromController
toViewController:(UIViewController*) toController
withDirection:(UIViewAnimationOptions) direction
{
toController.view.frame = fromController.view.bounds; // 1
[self addChildViewController:toController]; //
[fromController willMoveToParentViewController:nil]; //
[self transitionFromViewController:fromController
toViewController:toController
duration:0.2
options:direction | UIViewAnimationOptionCurveEaseIn
animations:nil
completion:^(BOOL finished) {
[toController didMoveToParentViewController:self]; // 2
[fromController removeFromParentViewController]; // 3
}];
}

1. Before the animation we add the `toController` as a child and we
inform the `fromController` that it will be removed. If the fromController's view is part of the container's view hierarchy, this is where `viewWillDisapear:` is called.
2. `toController` is informed of its new parent, and appropriate view
event methods will be called.
3. The `fromController` is removed.

This convenience method for view controller transitions automatically swaps out the old view controller's view for the new one. However, if you implement your own transition and you wish to only display one view at a time, you have to call `removeFromSuperview` on the old view and `addSubview:` for the new view yourself. Getting the sequence of method calls wrong will most likely result in an `UIViewControllerHierarchyInconsistency` warning. For example, this will
happen if you call `didMoveToParentViewController:` before you added the view.

In order to be able to use the `UIViewAnimationOptionTransitionFlipFromTop` animation, we had to add the children's views to our view containers instead of to the root view controller's view. Otherwise the animation would result in the entire root view flipping over.


## Communication

View controllers should be reusable and self-contained entities. Child view controllers are no exception to this rule of thumb. In order to achieve this, the parent view controller should only be concerned with two tasks: laying out the child view controller's root view, and communicating with the child view controller through its exposed API. It should never modify the child's view tree or other internal state directly.

Child view controllers should contain the necessary logic to manage their view trees themselves -- don't treat them as dumb views. This will result in a clearer separation of concerns and better reusability.

In the Tunnel example app, the parent view controller observes a property called `currentLocation` on the map view controllers:

[_startMapViewController addObserver:self
forKeyPath:@"currentLocation"
options:NSKeyValueObservingOptionNew
context:NULL];

When this property changes in response to moving the little guy with the shovel around on the map, the parent view controller communicates the antipode of the new location to the other map:

[oppositeController updateAnnotationLocation:[newLocation antipode]];

Likewise, when you tap the radar button, the parent view controller sets the locations to be reverse geocoded on the new child view controllers:

[_startGeoViewController setLocation:_startMapViewController.currentLocation];
[_targetGeoViewController setLocation:_targetMapViewController.currentLocation];

Independent of the technique you choose to communicate from child to parent view controllers (KVO, notifications, or the delegate pattern), the goal always stays the same: the child view controllers should be independent and reusable. In our example we could push one of the child view controllers on a navigation stack, but the communication would still work through the same API.
6 changes: 6 additions & 0 deletions 2013-06-07-index.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
layout: toc
category: "1"
date: "2013-06-07 12:00"
tags: toc
---
21 changes: 21 additions & 0 deletions 2013-06-07-introduction.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
layout: post
title: "Introduction"
category: "1"
date: "2013-06-07 12:00"
tags: editorial
---

Welcome to the first edition of objc.io, a periodical about best practices and advanced techniques in Objective-C!

objc.io was founded in Berlin by [Chris Eidhof](https://twitter.com/chriseidhof), [Daniel Eggert](https://twitter.com/danielboedewadt), and [Florian Kugler](https://twitter.com/floriankugler). We started objc.io to create a regular platform for in-depth technical topics relevant to all iOS and OS X developers.

Each edition of objc.io has a focus on one particular subject, with multiple articles covering different aspects of that topic. The subject of this first edition is *Lighter View Controllers*, and it contains four articles – three from the founding team and one from [Ricki Gregersen](https://twitter.com/rickigregersen), whom we would like to welcome as our first guest writer!

It's a common problem in the code base of iOS apps that view controllers get out of hand, because they do too much. By factoring out reusable code, they are easier to understand, easier to maintain and easier to test. This issue will focus on best practices and techniques how you can keep view controllers clean.

We will look at how to make view controllers primarily coordinating objects by factoring out view and model code, as well as introducing other controller objects in addition to view controllers. Furthermore, we will look at splitting up view controllers using the view controller containment mechanism, and finally, discuss how to test clean view controllers.

In upcoming editions we will have more articles from great guest writers of the Objective-C community; [Loren Brichter](https://twitter.com/lorenb), [Peter Steinberger](https://twitter.com/steipete), [Brent Simmons](https://twitter.com/brentsimmons), and [Ole Begemann](https://twitter.com/olebegemann) have committed to writing in the future. [Contact us](mailto:[email protected]) if you have an idea for an interesting topic and you would like to contribute an article about it to objc.io.

Chris, Daniel, and Florian.
Loading

0 comments on commit f7ae8ca

Please sign in to comment.