Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
clayallsopp committed Jul 27, 2012
2 parents 648c959 + f04b2ca commit 88d3928
Show file tree
Hide file tree
Showing 9 changed files with 29 additions and 22 deletions.
10 changes: 9 additions & 1 deletion README.md
@@ -1,5 +1,13 @@
# RubyMotion-Tutorial

## Forking

- Edit the chapters in `_posts`
- Code sources are in each chapter's folder
- Use [Jekyll](https://github.com/mojombo/jekyll/) to preview your changes
- Use [Jekyll](https://github.com/mojombo/jekyll/) to preview your changes

### Installation

1. `gem install jekyll`
2. `gem install redcarpet` (used for improved markdown parsing)
3. `sudo easy_install Pygments` (used for syntax highlighting)
2 changes: 1 addition & 1 deletion _posts/chapters/1900-01-01-1-hello-motion.md
Expand Up @@ -87,7 +87,7 @@ In the `motion` generated code, we only implement `def application(application,

In most languages, functions look like this: `obj.makeBox(origin, size)`. This can be a bit of a pain because now you need to look up the implementation of that function and figure out what variables go where. Objective-C uses "named parameters" to solve this problem. In Objective-C, that same function looks like this: `[obj makeBoxWithOrigin: origin andSize: size];`. See how each variable directly proceeds the part of the function name which refers to it, removing ambiguity? Pretty clever. We refer to those functions by putting colons in place of variable names, like so: `makeBoxWithOrigin:andSize:`.

In Ruby, named arguments don't exist; it has traditional methods like `obj.make_box(origin, size)`. RubyMotion decided to add named arguments to it's implementation of Ruby so the original Apple APIs were compatible, like so: `obj.makeBox(origin, andSize: size)`. That `andSize` isn't just sugar; it's a real part of the method name. `obj.call_method(var1, withParam: var2)` is totally different than `obj.call_method(var1, withMagic: var2)`, despite their normal Ruby forms looking like `obj.call_method`.
In Ruby, named arguments don't exist; it has traditional methods like `obj.make_box(origin, size)`. RubyMotion decided to add named arguments to its implementation of Ruby so the original Apple APIs were compatible, like so: `obj.makeBox(origin, andSize: size)`. That `andSize` isn't just sugar; it's a real part of the method name. `obj.call_method(var1, withParam: var2)` is totally different than `obj.call_method(var1, withMagic: var2)`, despite their normal Ruby forms looking like `obj.call_method`.

Anyway, back to the main plot. `application:didFinishLaunchingWithOptions:` is called when the system finishes setting up the app and becomes ready for us to do our own setup:

Expand Down
4 changes: 2 additions & 2 deletions _posts/chapters/1900-01-04-4-containers.md
Expand Up @@ -77,7 +77,7 @@ Anyway, `target` is the object you want to call the `action` function on. Let's

Make more sense now? When the user taps the bar button, `push` gets called on the `target`, which in this case is our controller.

In addition to `navigationItem`, `UIViewController`s also have a propery for their `navigationController`, if available. On the nav controller we call `pushViewController` which pushes the passed controller onto the stack. By default, the navigation controller will also show a back button which handles popping the current controller for us (normally we call `popViewControllerAnimated:` on the navigation controller). Kind of neat, right?
In addition to `navigationItem`, `UIViewController`s also have a property for their `navigationController`, if available. On the nav controller we call `pushViewController` which pushes the passed controller onto the stack. By default, the navigation controller will also show a back button which handles popping the current controller for us (normally we call `popViewControllerAnimated:` on the navigation controller). Kind of neat, right?

Let's have one last bit of fun before we run the app. Have the controller's title reflect its position in the navigation stack, like so:

Expand All @@ -99,7 +99,7 @@ Now, I said we'd cover `UITabController`s too, so let's get to it.

![Navigation controller in Mail.app](images/tab_bar.png)

Tab controllers are a lot like `UINavigationController` and other container controllers: it has a list of `viewControllers` which are presented within the container's "chrome" (the black bar). However, unlike the other containers, `UITabBarController`s are only supposed act as the `rootViewController` of the window (i.e. you shouldn't push a tab bar controller inside a navigation controller).
Tab controllers are a lot like `UINavigationController` and other container controllers: it has a list of `viewControllers` which are presented within the container's "chrome" (the black bar). However, unlike the other containers, `UITabBarController`s are only supposed to act as the `rootViewController` of the window (i.e. you shouldn't push a tab bar controller inside a navigation controller).

In `AppDelegate`, let's make a small change:

Expand Down
2 changes: 1 addition & 1 deletion _posts/chapters/1900-01-06-6-animations.md
Expand Up @@ -111,7 +111,7 @@ It *looks* just like the old one, but its behavior is quite a bit different, no?

So that covers basic iOS animations. What all did we go over?

- Animations work by changing views' properties in the `animations:` lambda of `animateWithDuration:animations:` and it's variants
- Animations work by changing views' properties in the `animations:` lambda of `animateWithDuration:animations:` and its variants
- Animations are guided by "animation curves"; examples include UIViewAnimationOptionCurveLinear and UIViewAnimationOptionCurveEaseInOut

[Do a barrel roll to our next chapter and learn about Models!](/7-models)
Expand Down
2 changes: 1 addition & 1 deletion _posts/chapters/1900-01-07-7-models.md
Expand Up @@ -76,7 +76,7 @@ The `NSUserDefaults` object exists as a persistent key-value store. Typically, w

Pretty nice right? The values in `NSUserDefaults` are saved as long as your app is installed; if you want to hasten that process, you can call `NSUserDefaults.resetStandardUserDefaults` to purge all entries.

Now, there's one caveat: we can't store any old object in `NSUserDefaults`. We can either store primitives (like strings, integers, and hashes), or we can use raw data/bytes. Since our `User` object isn't a primitive, we have to use data the method. How does that work?
Now, there's one caveat: we can't store any old object in `NSUserDefaults`. We can either store primitives (like strings, integers, and hashes), or we can use raw data/bytes. Since our `User` object isn't a primitive, we have to use the data method. How does that work?

We can put our model through a process called "archiving" using `NSKeyedArchiver`. This class takes an object and creates an instance of `NSData` that `NSUserDefaults` can save. The objects eligible for archiving are known as "`NSCoding` compliant". Basically that means they implement two methods which define how to serialize and unserialize themselves using a standard API. If your models don't implement these methods, they can't be archived.

Expand Down
8 changes: 4 additions & 4 deletions _posts/chapters/1900-01-08-8-testing.md
Expand Up @@ -23,7 +23,7 @@ Let's take a look at just how easy it is.

Create a brand new RubyMotion project with `motion create Tests` and `cd` into it. Remember way back how we discussed the default folders `motion create` makes? Well, we're going to take a look at a new one: `spec`.

RubyMotion loads your tests from the `./spec` folder, looking at every single `*.rb` file. When you create a RubyMotion app, by it creates a default test in `./spec/main_spec.rb` that looks like this:
RubyMotion loads your tests from the `./spec` folder, looking at every single `*.rb` file. When you create a RubyMotion app, it creates a default test in `./spec/main_spec.rb` that looks like this:

```ruby
describe "Application 'Tests'" do
Expand Down Expand Up @@ -110,7 +110,7 @@ end

Pretty normal `UIButton` pattern, except one new thing: `accessibilityLabel`.

`accessibilityLabel` is a string property available for every `UIView`. It's used by the operating system as the audible text to read aloud when someone uses the device's VoiceOver functionality (so don't fill it with garbage). Why are we introducing this now? Because RubyMotion's functional tests look up views by their `accesibilityLabel`, which ensures that views you're testing have been added to the hierarchy and don't just exist somewhere in memory.
`accessibilityLabel` is a string property available for every `UIView`. It's used by the operating system as the audible text to read aloud when someone uses the device's VoiceOver functionality (so don't fill it with garbage). Why are we introducing this now? Because RubyMotion's functional tests look up views by their `accessibilityLabel`, which ensures that views you're testing have been added to the hierarchy and don't just exist somewhere in memory.

Finally we need to actually hook this controller up to our window in `AppDelegate`:

Expand Down Expand Up @@ -145,7 +145,7 @@ Nice, looks like we've got some new toys to play with.

`tests <class>` links our `describe` to a specific `UIViewController` class. It does something really neat: it will instanstiate the tested controller with a new `UIWindow`. We can access the window and controller inside the tests with `self.window` and `self.controller`, respectively. This ensures that there's no junk UI or state lying around which could corrupt our procedures.

So we set the spec to test our controller and start our assertions in the `it` block. Our other new toy is `tap`. `tap` will take as its first argument either a raw `UIView` *or* an accessibility label. By default, `UIButton`s and `UILabel`s will use their `title` as the accessibility label; we overrode that in our implementation to prove explicitly show how we can access most other views in tests.
So we set the spec to test our controller and start our assertions in the `it` block. Our other new toy is `tap`. `tap` will take as its first argument either a raw `UIView` *or* an accessibility label. By default, `UIButton`s and `UILabel`s will use their `title` as the accessibility label; we overrode that in our implementation to explicitly show how we can access most other views in tests.

In addition to `tap` we can also use `flick`, `drag`, `pinch_close`, `pinch_open`, and `rotate`. Check out RubyMotion's [full documentation][1] for the details on each.

Expand All @@ -165,7 +165,7 @@ What did we learn about testing today?
- Tests are collections of `describe` and `it` blocks. These both take one a label argument which helps group alike tests.
- Use `<any object>.should` to assert equality. Example: `greeting.should == "hello"`
- `UIViewController`s have functional tests, which let us simulate events like `tap` and `pinch`. In your `describe` block, use the `tests <controller class>` function to enable these features.
- `tap <accesibility label>` programatically taps the view with its `accessibilityLabel` property set to the argument.
- `tap <accessibility label>` programmatically taps the view with its `accessibilityLabel` property set to the argument.

[Qwop over to the next chapter to see how to run HTTP requests!](/9-http)

Expand Down
2 changes: 1 addition & 1 deletion _posts/chapters/1900-01-09-9-http.md
Expand Up @@ -7,7 +7,7 @@ categories:

# An Introduction to HTTP

We've covered the building blocks of UI on iOS, but most of the time we want to populate our interface with some data from a server. The Apple-perscribed method of doing this is to use `NSURLConnection`, which is a very powerful but verbose class for sending HTTP requests; instead, we're going to use a new-fangled RubyMotion library called `BubbleWrap`.
We've covered the building blocks of UI on iOS, but most of the time we want to populate our interface with some data from a server. The Apple-prescribed method of doing this is to use `NSURLConnection`, which is a very powerful but verbose class for sending HTTP requests; instead, we're going to use a new-fangled RubyMotion library called `BubbleWrap`.

## BubbleWrap It Up

Expand Down
17 changes: 8 additions & 9 deletions _posts/chapters/1900-01-10-10-api-driven-example.md
Expand Up @@ -8,7 +8,7 @@ categories:

# API Driven Example: Colr

We're going to build a front-end for the [Colr JSON API][colr]. Our users can type in a color hex code (i.e. "#3B5998") and then see what tags Colr users have assigned to that color. If our user is feeling particularly adventerous, they can add a new tag!
We're going to build a front-end for the [Colr JSON API][colr]. Our users can type in a color hex code (i.e. "#3B5998") and then see what tags Colr users have assigned to that color. If our user is feeling particularly adventurous, they can add a new tag!

Let's talk about high-level architecture. We need two controllers: one for search, and one for a color. These should be wrapped inside a `UINavigationController`, our top level controller. We're also going to need some models: `Color` and `Tag`. I'm going to be honest: our app won't be the next top post on Dribbble, but it will work.

Expand All @@ -18,7 +18,7 @@ Let's talk about high-level architecture. We need two controllers: one for searc

## Models

First lets dig into our models. The Colr API returns its colors as JSON objects as the form:
First let's dig into our models. The Colr API returns its colors as JSON objects as the form:

```
{
Expand Down Expand Up @@ -160,7 +160,7 @@ Good start! Time to fill in our `SearchController`.

## SearchController

We're going to add a new type of control we haven't used, `UITextField`, to retreive the hex code from the user. When the user pushes a "Search" button, we'll run the appropriate API request and lock the UI until it finishes. If we found a result, we'll push a new `ColorController`; else, we'll show a sad alert. Sound gravy?
We're going to add a new type of control we haven't used, `UITextField`, to retrieve the hex code from the user. When the user pushes a "Search" button, we'll run the appropriate API request and lock the UI until it finishes. If we found a result, we'll push a new `ColorController`; else, we'll show a sad alert. Sound gravy?

We can setup our views in `SearchController` using something like this:

Expand Down Expand Up @@ -189,7 +189,7 @@ We can setup our views in `SearchController` using something like this:
end
```

A lot of the specific positioning (`self.view.frame.size.height / 2 - 100`) are based on my personal guess and check, there's not special magic going on (unfortunately). Some new bits are `UIControlStateDisabled`, which corresponds to what the button looks like if we do `@search.enabled = false`, and `UITextBorderStyleRoundedRect`, which is a nice-looking style of `UITextField`s.
A lot of the specific positioning (`self.view.frame.size.height / 2 - 100`) are based on my personal guess and check, there's no special magic going on (unfortunately). Some new bits are `UIControlStateDisabled`, which corresponds to what the button looks like if we do `@search.enabled = false`, and `UITextBorderStyleRoundedRect`, which is a nice-looking style of `UITextField`s.

`rake` now and get a feel for our interface:

Expand Down Expand Up @@ -239,15 +239,15 @@ class Color
end
```

Look bad? We use the basic `HTTP.get` to get some data from the server via the proper API URL. Note that we use the `&block` notation to make it plain that this function is intended to be used with a block. This block isn't explicility passed as another argument, but rather is implicilty passed when we put a `do/end` after we call the method. The number and order of variables in `.call(some, variables)` corresponds to `do |some, variables|`.
Look bad? We use the basic `HTTP.get` to get some data from the server via the proper API URL. Note that we use the `&block` notation to make it plain that this function is intended to be used with a block. This block isn't explicitly passed as another argument, but rather is implicilty passed when we put a `do/end` after we call the method. The number and order of variables in `.call(some, variables)` corresponds to `do |some, variables|`.

Anyway, `rake` and give it a go with a color like "3B5998". You should see something like this output in the console:

```
(main)> "{\"colors\": [{\"timestamp\": 1285886579, \"hex\": \"ff00ff\", \"id\": 3976, \"tags\": [{\"timestamp\": 1108110851, \"id\": 2583, \"name\": \"fuchsia\"}, {\"timestamp\": 1108110864, \"id\": 3810, \"name\": \"magenta\"}, {\"timestamp\": 1108110870, \"id\": 4166, \"name\": \"magic\"}, {\"timestamp\": 1108110851, \"id\": 2626, \"name\": \"pink\"}, {\"timestamp\": 1240447803, \"id\": 24479, \"name\": \"rgba8b24ff00ff\"}, {\"timestamp\": 1108110864, \"id\": 3810, \"name\": \"magenta\"}]}], \"schemes\": [], \"schemes_history\": {}, \"success\": true, \"colors_history\": {\"ff00ff\": [{\"d_count\": 0, \"id\": \"4166\", \"a_count\": 1, \"name\": \"magic\"}, {\"d_count\": 0, \"id\": \"2626\", \"a_count\": 1, \"name\": \"pink\"}, {\"d_count\": 0, \"id\": \"24479\", \"a_count\": 1, \"name\": \"rgba8b24ff00ff\"}, {\"d_count\": 0, \"id\": \"3810\", \"a_count\": 1, \"name\": \"magenta\"}]}, \"messages\": [], \"new_color\": \"ff00ff\"}\n"
```

Hey...that looks an awful like JSON, doesn't it? (if you didn't notice the "/json/" in the URL). Wouldn't it be great if we could parse that into a normal Ruby hash?
Hey...that looks an awful lot like JSON, doesn't it? (if you didn't notice the "/json/" in the URL). Wouldn't it be great if we could parse that into a normal Ruby hash?

BubbleWrap to the rescue again! Our nifty friend also has a `BW::JSON.parse` method which does exactly what it sounds like. Let's update `Color.find` to use it:

Expand Down Expand Up @@ -293,7 +293,7 @@ And in our `SearchController`, our callback can adapt appropriately if we got an
end
```

This seems pretty reasonable. We parse the JSON, check for the non-existentant/-1 id, and alter the UI accordingly:
This seems pretty reasonable. We parse the JSON, check for the non-existent/-1 id, and alter the UI accordingly:

![search controller in app](images/2.png)

Expand Down Expand Up @@ -336,8 +336,7 @@ Next, let's layout our interface:

# A light grey background to separate the Tag table from the Color info
@info_container = UIView.alloc.initWithFrame [[0, 0], [self.view.frame.size.width, 110]]
# need to guarantee .hex is a String
@color_view.backgroundColor = String.new(self.color.hex).to_color
@info_container.backgroundColor = UIColor.lightGrayColor
self.view.addSubview @info_container

# A visual preview of the actual color
Expand Down
4 changes: 2 additions & 2 deletions more-tables/more-tables.md
Expand Up @@ -6,13 +6,13 @@ Your first exposure to tables just had rows, but tables can also have "sections"

If you go to Phone.app or Contacts.app, you'll notice that the alphabetical listing of your contacts has these "sticky" headers at the top of the screen that denote which part of the alphabet you're on. These are the default `UITableView` section dividers. You'll also notice a bar on the right side that corresponds to these sections; this is also a default behavior of `UITableView` and sections

Adding sections to a table is pretty easy: you just need to implement afew more methods and add some more logic in your existing `dataSource` implementations. Ideally, your data should be structured as a hash where the keys are your section identifiers and their values are the rows contained in that section. Take our alphabet as an example:
Adding sections to a table is pretty easy: you just need to implement a few more methods and add some more logic in your existing `dataSource` implementations. Ideally, your data should be structured as a hash where the keys are your section identifiers and their values are the rows contained in that section. Take our alphabet as an example:

```ruby
@data = { "A" => ["Adam", "Apple"], "B" => ["Barry", "Berry"], "C" => ["Carlos", "Charlie"], ....}
```

You get the idea. Your data doens't absolutely *have* to be a hash, but you should have a way of referencing sections by index and then accessing the row data inside each section using a seperate index.
You get the idea. Your data doesn't absolutely *have* to be a hash, but you should have a way of referencing sections by index and then accessing the row data inside each section using a separate index.

It's often useful to just create some helper methods in your controller so you don't repeat a lot of the same lookup code, which can be a pain if you change your structure later:

Expand Down

0 comments on commit 88d3928

Please sign in to comment.