Skip to content

Commit

Permalink
Adding 3-Controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
clayallsopp committed Jul 6, 2012
1 parent c9a4f3c commit a7bbbba
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 9 deletions.
90 changes: 81 additions & 9 deletions 3-controllers/3-controllers.md
@@ -1,20 +1,92 @@
# Controllers

We've done a lot of work with views, but that's only part of the story. Views are one leg of the "Model-View-Controller" paradigm that the iOS SDK uses. That sounds really fancy, but it's actually simple.
We've done some work with views, but that's only part of the story. Views are one leg of the "Model-View-Controller" paradigm that the iOS SDK uses. That sounds really fancy, but it's actually simple.

In your code, you should have three types of classes: views (which you've already seen), models (which are normal objects that exist to represent or handle data), and controllers.
In your code, you should have three types of classes: views (which yup, you've already seen), models (which are normal objects that exist to represent or handle data), and....controllers.

So...what are controllers? They're supposed to act as a layer between models and views, intrepreting events generated by the user for the models and updating the views in reponse. In a perfectly coded world, when you tap a button the controller intercepts that event, updates a property of the data, and changes the view to reflect the new property.
So, what are controllers? They're objects which act as a "layer" between models and views, intrepreting events generated by the user for the models and updating the views in response. In a perfectly coded world, when you tap a button the controller intercepts that event, updates a property of the data, and changes the view to reflect the new data.

You don't technically *need* controllers, but there are some really great reasons.
That sounds kind of "big picture", but there are some really practical reasons for controllers:

- View re-use. Let's say we have a PostView which displays all the information about a Post (it's content, author, "Likes", etc). We want to use this PostView on a couple of different screens, such as a main feed and a user's profile feed. To stay reuseable, the PostView shouldn't deal with how it gets its data; instead, its controller should take care of that.
- Presentation management. Sometimes we want a view to take up the entire screen, other times we want it to appear in a modal box. It doesn't make sense to write two identical views that differ only in presentation, so we use the controllers to resize and animate their views accordingly.
- View re-use. Let's say we have a PostView which displays all the information about a Post (it's content, author, "Likes", etc). We want to use this PostView on a couple of different screens, such as a main feed and a user's profile feed. To stay reuseable, the PostView shouldn't deal with *how* it gets the information; instead, its controller should take care of that and then pass the processed data to the view.
- Presentation management. Sometimes we want a view to take up the entire screen, other times we want it to appear in a modal box (think iPad vs iPhone). It doesn't make sense to write two identical views that differ only in presentation style, so we use the controllers to resize and animate views accordingly.

There's nothing technically stopping you from doing those things inside models and views, but it makes your code much more robust and easier to manage if you embrace MVC.

There's nothing technically stopping you from doing those things inside views, but it makes your code more robust and easier to manage.
In iOS-land, controllers are `UIViewController`s. They come with one `view` property and a handful of methods for dealing with things like the view "lifecycle" and handling orientation changes. Don't fret, we'll get to the lifecycle business soon enough.

So now that we know what a controller should do, what *shouldn't* it do?
So now that we know what a controller is and what it should do, what *shouldn't* it do?

- Directly query or save data. It's tempting to send a bunch of HTTP requests in a controller, but those are best left to your models.
- Complex view layouts. If you're adding directly adding subviews more then one level deep to your controller's `view`, you should rewrite your views to flatten that hierarchy. As a good rule of thumb, the only `addSubview` you should see is `self.view.addSubview`.
- Complex view layouts. If you're adding directly adding subviews more then one level deep to your controller's `view`, you should rewrite your views to flatten that hierarchy. As a good rule of thumb, the only `addSubview` you should see in your controller is `self.view.addSubview`.

OK that's enough exposition, time for the Michael Bay action sequences.

Create the `./app/controllers` directory and add a `TapController.rb` file inside. Let's start to define our controller like so:

```ruby
class TapController < UIViewController
def viewDidLoad
super

self.view.backgroundColor = UIColor.redColor
end
end
```

`viewDidLoad` is one of those "lifecycle" methods of `UIViewController`, which calls after `self.view` has been created and is ready for subviews to be added. For now, we just make it's background color red and call it a day.

You *absolutely*, *must*, *without question* call `super` in `viewDidLoad`, or else bad things will happen. Got it? Cool.

Now, go back to your `AppDelegate` and remove our old `UIView` code. We just need to add one line so it looks like this:

```ruby
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
@window.makeKeyAndVisible

@window.rootViewController = TapController.alloc.initWithNibName(nil, bundle: nil)

true
end
end
```

See the `roowViewController=` call? The window will take the given `UIViewController` and adjust it's `view`'s size to fits the window. *This* is the better way of setting up your window (as opposed to `window.addSubview` everywhere).

The other new part of that line is `initWithNibName`. Typically, this is used to load a view controller from a NIB file. NIBs are created using Interface Builder as a way of visually constructing your view. Since we aren't using Interface Builder for our controller, we can safely pass nil for both arguments.

This is also the "designated initializer" of `UIViewController`s. Whenever you want to create a controller, you *must* call this method at some point, especially in any customized initializer methods.

Now that that's out of the way, `rake` and check it out. You should see something like this:

![derp][http://hello]

Pretty modest. Let's make one small change to our controller:

```ruby
def viewDidLoad
super

self.view.backgroundColor = UIColor.whiteColor

@label = UILabel.alloc.initWithFrame(CGRectZero)
@label.text = "Taps"
@label.sizeToFit
@label.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)
self.view.addSubview @label
end
```

A wild `UILabel` appeared! `UILabel`s are views meant for displaying static text with their `text` property. We create one, set it's text to "Taps", and add it as a subview.

We use the `CGRectZero` frame when we initialize it because we don't know the exact dimensions of the text on the screen yet; however, when we call `sizeToFit` the label resizes to perfectly fit it's contents. Then we use the handy `center` property to center it in our controller's view.

`rake` again for a much pleasant-looking app. It doesn't really do...anything right now, but in the next chapter we'll make it do...stuff.

What did we learn?
1. The iOS SDK uses the Model-View-Controller paradigm.
2. `UIViewController` makes up the controller part of that, and it should be subclassed for customization.
3. Use `viewDidLoad` when to setup your controller and *don't forget to call *super*.
4. `UIWindow`s have a `rootViewController` property for displaying controllers.
5 changes: 5 additions & 0 deletions 3-controllers/Controllers/.gitignore
@@ -0,0 +1,5 @@
.repl_history
build
resources/*.nib
resources/*.momd
resources/*.storyboardc
8 changes: 8 additions & 0 deletions 3-controllers/Controllers/Rakefile
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
$:.unshift("/Library/RubyMotion/lib")
require 'motion/project'

Motion::Project::App.setup do |app|
# Use `rake config' to see complete project settings.
app.name = 'Controllers'
end
10 changes: 10 additions & 0 deletions 3-controllers/Controllers/app/app_delegate.rb
@@ -0,0 +1,10 @@
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
@window.makeKeyAndVisible

@window.rootViewController = TapController.alloc.initWithNibName(nil, bundle: nil)

true
end
end
13 changes: 13 additions & 0 deletions 3-controllers/Controllers/app/controllers/TapController.rb
@@ -0,0 +1,13 @@
class TapController < UIViewController
def viewDidLoad
super

self.view.backgroundColor = UIColor.whiteColor

@label = UILabel.alloc.initWithFrame(CGRectZero)
@label.text = "Taps"
@label.sizeToFit
@label.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)
self.view.addSubview @label
end
end
9 changes: 9 additions & 0 deletions 3-controllers/Controllers/spec/main_spec.rb
@@ -0,0 +1,9 @@
describe "Application 'Controllers'" do
before do
@app = UIApplication.sharedApplication
end

it "has one window" do
@app.windows.size.should == 1
end
end
Binary file added 3-controllers/images/1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a7bbbba

Please sign in to comment.