I saw in someone's rubymotion code that it was easy to add an application icon, so I'm gonna do that to the previous app.

  • icon_iphone.png

    iPhone, 57x57 (gradient is bottom-to-top)

  • icon_ipad.png

    iPad, 72x72 (gradient is right-to-left)

  • icon_iphone_retina.png

    iPhone-retina, 114x114 (gradient is top-to-bottom)

  • icon_ipad_retina.png

    iPad-retina, 144x144 (gradient is left-to-right)

And my Rakefile becomes:

Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.icons = ['icon_iphone.png', 'icon_ipad.png', 'icon_iphone_retina.png', 'icon_ipad_retina.png']
  app.prerendered_icon = true  # it's gonna look awful, btw
  app.name = 'thought4'
  app.device_family = [:iphone, :ipad]
end

And on my home screen(s), we get:

  • icon_screenshot_iphone.png

    Icon demo on iPhone

  • icon_screenshot_iphone_retina.png

    Icon demo on iPhone-retina

  • icon_screenshot_ipad.png

    Icon demo on iPad

  • icon_screenshot_ipad_retina.png

    Icon demo on iPad-retina

With these, it was easy to create an icon template, I offer it here in png and pxm formats:

  • icon_template.png

    Icon Template
    png | pxm

Next up, adding a custom font. This was discussed in the #rubymotion chat room this morning, and it was super easy to implement!

  1. get your font file. .ttf is a good format to stick to. here's one
  2. drop it in resources/
  3. It will be detected automatically, you don't have to include it in your Rakefile, but if you did want to (because your file doesn't end in .ttf?), it would look likes this:
   Motion::Project::App.setup do |app|
     #...
     app.fonts = ["Inconsolata.ttf"]  # but not necessary - all .ttf files
                                      # will be included automatically.
   end
  1. Use it by creating a font object, and assigning that to a font property, like on a UILabel. It's important to use the font name, not the font filename:
   font = UIFont.fontWithName("Inconsolata", size:20)

   @label = UILabel.new
   @label.font = font
   @label.text = "Inconsolata font example"
   @label.frame = [[0, 0], [320, 30]]
   @label.setBackgroundColor(UIColor.clearColor);

   view.addSubview(@label)

I was never able to accomplish this mundane task using Xcode & Objective-C. So I think this is pretty cool stuff!

Next, let's deal with rotation. We'll add a button in the lower-right corner, and when the user rotates the phone, we need to move the button to the new "correct" location.

We will log the orientation to the label we just added, and we'll need to adjust its size each time, and adjust width/height when the phone is rotated.

First, to get word wrapping enabled, add:

@label.lineBreakMode = UILineBreakModeWordWrap
@label.numberOfLines = 0  # any number of lines
@label.sizeToFit  # adjusts the height - this is an easy way to get "vertical alignment"

The button:

@button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
@button.setTitle('Do Something', forState:UIControlStateNormal)
@button.addTarget(self, action: :resetLabel,
                        forControlEvents:UIControlEventTouchUpInside)
# I'm using a CGSize struct here, just for kicks.  You can use
# arrays anywhere a CGSize, CGPoint, or CGRect is expected
size = CGSizeMake(120, 35)
# For example, here I mix an array for the point, and a CGSize struct for the size
@button.frame = [[320 - size.width, 440 - size.height], size]
view.addSubview(@button)

And the resetLabel method:

def resetLabel
  @label.text = 'Inconsolata font example'
  @label.sizeToFit
end

Now we will add the code that enables and responds to a rotation.

The default setting of a UIViewController is that it does not respond to a rotation. You "enable" it by returning true for other orientations:

  def shouldAutorotateToInterfaceOrientation(orientation)
    true if ipad? or
      orientation != UIInterfaceOrientationPortraitUpsideDown
  end

You'll need the ipad? method from a previous post

Our rotation method is a CocoaTouch method, and so is superLongAndVerbose: willAnimateRotationToInterfaceOrientation(orientation, duration: duration)

def willAnimateRotationToInterfaceOrientation(orientation, duration: duration)
  button_size = @button.frame.size  # we will be moving the button, and use its
                                    # width and height as offsets
  button_offset = nil  # this will hold our "starting" point
  case orientation
  when UIInterfaceOrientationPortrait, UIInterfaceOrientationPortrait
      append = if orientation == UIInterfaceOrientationPortraitUpsideDown
               'Upright'
             else
               'UpsideDown'
             end
    label_size = [320, 480]
    button_offset = CGPointMake(320, 440)
  when UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight
    append = if orientation == UIInterfaceOrientationLandscapeLeft
               'Left'
             else
               'Right'
             end
    label_size = [480, 320]
    button_offset = CGPointMake(480, 300)
  end

  @button.frame = [[button_offset.x - button_size.width, button_offset.y - button_size.height], button_size]

  @label.text += ", #{append}"
  # resize - first, we need to set the maximum width and height again
  @label.frame = [[0, 0], label_size]
  # and then we can "sizeToFit"
  @label.sizeToFit
end

The shouldAutorotateToInterfaceOrientation method checks for the iPad, and so will allow all four rotations when an ipad is used, but we are not checking for the ipad when we calculate our width/height. We should! Using code from an earlier post, let's subclass our UIViewController and turn width, height, and margins into properties of our class (actually, we'll write them as methods). They will return the width and height of the device when it is in portrait view.

class MyIphoneController < MyApplicationController
  def width
    320
  end
  def height
    480
  end
  def margin_bottom
    20
  end
  def margin_right
    10
  end
end

class MyIpadController < MyApplicationController
  def width
    768
  end
  def height
    1024
  end
  def margin_bottom
    60
  end
  def margin_right
    20
  end
end

And in our AppDelegate, we will check for the device, and use the correct class.

@window.rootViewController = if ipad?
                               MyIpadController.alloc.init
                             else
                               MyIphoneController.alloc.init
                             end

Change the code to use these methods. For example:

# ...
@label.frame = [[0, 0], [self.width, self.height]]
# in ruby, you don't *need* to use `self`
# @label.frame = [[0, 0], [width, height]]
# but I like to, because I'm coming from python
# ...
# this code does the same that from above, but uses a CGPoint
point = CGPointMake(self.width - self.margin_left - size.width, self.width - self.margin_height - size.height)
@button.frame = [point, size]
# ...

And so on. The full code is available in my thoughts repository

Here's the end result, though I'm using a different font than "Inconsolata":

  • thought4_iphone.png

    Screenshot of wrapped text on iPhone

  • thought4_ipad.png

    Screenshot of wrapped text on iPad