forked from clayallsopp/rubymotion-tutorial
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c9a4f3c
commit a7bbbba
Showing
7 changed files
with
126 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.repl_history | ||
build | ||
resources/*.nib | ||
resources/*.momd | ||
resources/*.storyboardc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
13
3-controllers/Controllers/app/controllers/TapController.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.