Interface Builder

332 views
Skip to first unread message

Ian Phillips

unread,
May 7, 2012, 3:00:21 PM5/7/12
to rubym...@googlegroups.com
Hi Folks,

If anybody is interested I've written up a brief description of how to use Interface Builder with RubyMotion projects here http://ianp.org/2012/05/07/rubymotion-and-interface-builder

Id be interested in hearing if anybody else has been trying this out and is you have any improvements on my workflow.

Cheers,
Ian.

Ben Damman

unread,
May 7, 2012, 3:18:22 PM5/7/12
to rubym...@googlegroups.com
Thanks; been wondering about this!

Ben

Andrew Chalkley

unread,
May 7, 2012, 3:22:41 PM5/7/12
to rubym...@googlegroups.com
Great stuff Ian!

Ian Phillips

unread,
May 7, 2012, 5:10:29 PM5/7/12
to rubym...@googlegroups.com
Thanks!

Also: I've submitted the gesture recognizer stuff from helpers.rb to https://github.com/mattetti/BubbleWrap so if you like the way that code works consider using BubbleWrap instead of copy-pasting.

(this is really just an excuse to pimp^H^H^H^H mention BubbleWrap, as I think it will be better if we have one common set of utility code rather than several competing libs)

On Monday, 7 May 2012 21:22:41 UTC+2, Andrew Chalkley wrote:
Great stuff Ian!

Robert J Samson

unread,
May 7, 2012, 7:00:16 PM5/7/12
to rubymotion
This is great! Thanks Ian!

On May 7, 5:10 pm, Ian Phillips <i...@ianp.org> wrote:
> Thanks!
>
> Also: I've submitted the gesture recognizer stuff from helpers.rb
> tohttps://github.com/mattetti/BubbleWrapso if you like the way that code

sakira

unread,
May 7, 2012, 9:03:04 PM5/7/12
to rubymotion
Thanks! Great!

SAkira

On 5月8日, 午前4:00, Ian Phillips <i...@ianp.org> wrote:
> Hi Folks,
>
> If anybody is interested I've written up a brief description of how to use Interface Builder with RubyMotion projects herehttp://ianp.org/2012/05/07/rubymotion-and-interface-builder

Mark Wilden

unread,
May 7, 2012, 10:23:34 PM5/7/12
to rubym...@googlegroups.com
> On 5月8日, 午前4:00, Ian Phillips <i...@ianp.org> wrote:
>> Hi Folks,
>>
>> If anybody is interested I've written up a brief description of how to use Interface Builder with RubyMotion projects herehttp://ianp.org/2012/05/07/rubymotion-and-interface-builder

This is awesome, and came just when I wanted it.

It wasn't clear to this n00b that you had to add a UIView to the
controller even if you didn't want a UIImageView. I tried adding my
controls to the controller and nothing good came of it. You might want
to make that explicit.

Also, Ruby style prefers avoiding 'self' unless necessary (the one
time you'll see it is in Rails, when calling dynamically generated
setter methods). So I would not do 'self.view.subviews.first', but
just 'view.subviews.first'. To get even more picky, I would use [0]
instead of #first, because there's nothing special about the first one
(even if it's the only one).

But thanks for saving me a lot of time with this writeup, and with the
BubbleWrap stuff in general. Kind of reminds me of another Ian (Ian
Bartholomew) who jumped on Dolphin Smalltalk in its early days and
contributed lots of fundamental stuff.

///ark

Ian Phillips

unread,
May 8, 2012, 8:02:14 AM5/8/12
to rubym...@googlegroups.com
It wasn't clear to this n00b that you had to add a UIView to the
controller even if you didn't want a UIImageView. I tried adding my
controls to the controller and nothing good came of it. You might want
to make that explicit.

There's a whole paragraph about that :-)

Although sure, it mainly talks about why you want to add it as a UIView instead of a UIImageView in IB.
 
Also, Ruby style prefers avoiding 'self' unless necessary

I've removed all of the unnecessary "self." bits.

Thanks for the style tips!
Message has been deleted

J'aime Ohm

unread,
May 8, 2012, 9:40:40 AM5/8/12
to rubym...@googlegroups.com
Patrick Rogers answered this on this mailing list with subject 'storyboard':

"Much like XIB files, Storyboards are really just a collection of XIB files that need to be compiled down to NIB files. Luckily there's a command line tool for that (ibtool).

If WordSearcher.storyboard is in your resources/ dir, run from the root dir of your project.

ibtool --compile resources/WordSearcher.storyboardc resources/WordSearcher.storyboard"

Best of luck,
J'aime

Sent from my iPad

On May 8, 2012, at 5:36 AM, mneorr <mari...@gmail.com> wrote:

Is there a way to use it with Storyboards?

Jen-Mei Wu

unread,
May 9, 2012, 2:00:35 AM5/9/12
to rubym...@googlegroups.com


On Tue, May 8, 2012 at 5:02 AM, Ian Phillips <ia...@ianp.org> wrote:
Also, Ruby style prefers avoiding 'self' unless necessary

I've removed all of the unnecessary "self." bits.

Thanks for the style tips!

Umm ... I didn't look through the code extensively (actually, I've only looked at that one commit), but I noticed that

self.userInteractionEnabled = true

was changed to

userInteractionEnabled = true

which is pretty different. In the first case, you're calling a method #userInteractionEnabled= with a parameter of true; in the second, you're setting a local variable named userInteractionEnabled to true. I don't think that's what you wanted ... 

Jen-Mei

Ian Phillips

unread,
May 9, 2012, 7:20:57 AM5/9/12
to rubym...@googlegroups.com
which is pretty different. In the first case, you're calling a method #userInteractionEnabled= with a parameter of true; in the second, you're setting a local variable named userInteractionEnabled to true. I don't think that's what you wanted ... 

Oops! You're right.

Actually, I've removed that line altogether, it's set in the nib now.

Cheers,
Ian.

Mark Wilden

unread,
May 9, 2012, 9:28:34 AM5/9/12
to rubym...@googlegroups.com
On Wed, May 9, 2012 at 4:20 AM, Ian Phillips <ia...@ianp.org> wrote:
>> which is pretty different. In the first case, you're calling a method
>> #userInteractionEnabled= with a parameter of true; in the second, you're
>> setting a local variable named userInteractionEnabled to true. I don't think
>> that's what you wanted ...
>
>
> Oops! You're right.

i'm afraid I misspoke - Jen-Mei is correct that (similar to a private
method, actually), a setter must be called on an object, regardless of
whether the setter is dynamically generated. This is to allow local
variables with the same name as another method (created now or in the
future).

Neil Berget

unread,
May 9, 2012, 8:34:32 PM5/9/12
to rubym...@googlegroups.com
Thanks much Ian!

I got to playing around with using xib files in RubyMotion and got frustrated with the awkwardness of getting references to the UI elements.  As far as I know, the best way is with view.viewWithTag(1) where '1' would the tag set on an element.

To make this easier, I wrote a ruby script that processes the xib script and generates auto-incrementing tags for every element that has an accessibility label set and generates a ruby constant relating the label to the generated tag.  So, if you have a UIButton with an accessibility label of "add_entry_button" it will generate a ruby file with:   ADD_ENTRY_BUTTON = 1 in it.

Then, in your rubymotion code, you can reference that button with: view.viewWithTag(ADD_ENTRY_BUTTON).

Also, played around with a method_missing on UIView to shorten it up even further:  view.add_entry_button, but I'm not sure if we're supposed to be using method_missing in RubyMotion projects? (if anyone knows let me know).

Anyway, would love to hear if people think this is a good idea:

Neil

Mischa Molhoek

unread,
May 10, 2012, 4:01:44 AM5/10/12
to rubym...@googlegroups.com
Hi Neil,

That might make it a lot easer, except for the problem that you might
create a constant that already exists in the system...

It might be smarter to add some string infront to distinguish all the
tags like XIB_

so ADD_ENTRY_BUTTON will become XIB_ADD_ENTRY_BUTTON

even thought you created the label "add_entry_button"

makes it easer to find with tab-completion too :)

what do you think?

greetings,

Mischa

Eloy Durán

unread,
May 10, 2012, 4:34:02 AM5/10/12
to rubym...@googlegroups.com
How about not having it create constants, but a class definition of the “File’s owner” which tries to assign elements to available attr accessors? I.e. extend UIViewController like this:

class UIViewController
  def self.elementNameToTagMappings
    @elementNameToTagMappings ||= {}
  end

  def self.attr_accessor(name)
    attr_writer(name)
    # Defines the reader method which either gets the assigned value or tries to find a view by tag and caches that.
    define_method(name) do
      instance_variable_get("@#{name}") || instance_variable_set("@#{name}", view.viewWithTag(self.class.elementNameToTagMappings[name])
    end
  end
end

And then have your tool generate something like:

class TheFileOwnerOfTheNib < UIViewController
  elementNameToTagMappings.merge! {
    :addEntryButton => 1,
  }
end

In your own code you would only need:

class TheFileOwnerOfTheNib < UIViewController
  attr_accessor :addEntryButton
end

This is all untested code, but I hope you get the idea :)

My 2 cents.

Mischa Molhoek

unread,
May 10, 2012, 5:24:15 AM5/10/12
to rubym...@googlegroups.com
awesome Eloy, that's a great approach!

Patrick Rogers

unread,
May 10, 2012, 8:58:05 AM5/10/12
to rubym...@googlegroups.com
This sadly doesn't work. RubyMotion doesn't allow define_method.


*** Terminating app due to uncaught exception 'RuntimeError', ... define_method:: not supported in static compilation

class UIViewController
  def self.ib_outlet(name, tag)
    attr_writer(name)
    
    define_method(name) do
      instance_variable_get("@#{name}") || instance_variable_set("@#{name}", view.viewWithTag(tag))
    end
  end
end

That was my attempt.

Patrick Rogers
Hello, Resolven Apps

Eloy Durán

unread,
May 10, 2012, 9:08:11 AM5/10/12
to rubym...@googlegroups.com
Fair enough, I’ll have to look into this a bit later, but I’m sure we can make it work some way or another :)

Eloy Durán

unread,
May 10, 2012, 9:23:57 AM5/10/12
to rubym...@googlegroups.com
After thinking about this a bit more, we don’t even need to go the metaprogramming-route, we can have the tool just generate the appropriate reader method. I.e. it would generate this:

class TheFileOwnerOfTheNib < UIViewController
  def addEntryButton
    @addEntryButton ||= view.viewWithTag(1)
  end
end

That would simply override the reader that was generated by `attr_accessor` in the user's source file.

On May 10, 2012, at 2:58 PM, Patrick Rogers wrote:

sakira

unread,
May 10, 2012, 12:21:05 PM5/10/12
to rubymotion
Hi,

I noticed we can use IBAction and IBOutlet with .xib files.
I created a dummy Xcode project and create MyViewController.xib and
MyViewController.h.
In xib file, I make UILabel and UIButton in a usual way with placing
UIViewController in it as Ian denoted.
I made the class of the View Controller to 'MyViewController', outlet
from UILabel to the header file with name 'theLabel', and connected
IBAction from UIButton for TouchUpInside-event to the header with name
'buttonPressed'.

Then I make my_view_controller.rb as follows, I mean, defined getter/
setter method for 'theLabel', and an IBAction-method for
'buttonPressed'.
class MyViewController < UIViewController
def initialize
@count = 0
@label = nil
end

def theLabel
@label
end

def setTheLabel(l)
@label = l
end

def buttonPressed(btn)
@count = 0 if !@count
@count += 1
p @count
@label.text = "count: " + @count.to_s if @label
end

end

Then, I copied MyViewController.xib to 'resources' dicrectory, then
just tried rake.
It seems to work as I disired. Pressing 'OK' button, I see the text of
the label changes!
Please try it. Can someone confirm it?
The sample code is here:
https://github.com/sakisakira/IBOutletActionRubyMotion.git

SAkira

Kam Dahlin

unread,
May 10, 2012, 12:48:04 PM5/10/12
to rubymotion
Yeah, wiring things up in Xcode to the ViewController.h class works well.

As long as the .xib / .nib name are the same as the ViewController (MyViewController.xib and MyViewController.rb) UIViewController should be able to look up the .nib and load it when you alloc.init (or new) on the controller.

I just use something like:

class MyViewController < UIViewController
attr_accessor :myLabel

def buttonPressed(sender)
self.myLabel.text = "Hi There"
end
end

class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
@window.rootViewController = ViewController.alloc.init
@window.makeKeyAndVisible
true
end
end

Cheers,

kam

sakira

unread,
May 10, 2012, 7:11:51 PM5/10/12
to rubymotion
Hi, Kam

That is straightforward, and better than mine. Thanks!
I did not know that UIViewController::init automatically look for an
appropriate nib file.

SAkira

Andrew Chalkley

unread,
May 10, 2012, 7:36:50 PM5/10/12
to rubym...@googlegroups.com
I'm guessing this will work for Storyboards then?

Kam Dahlin

unread,
May 10, 2012, 8:17:02 PM5/10/12
to rubymotion
I have a start on a OBJC header file to ruby class generator here: https://github.com/haxie1/MotionGenerator

Pretty rough, but for simple uses, like what we are doing here, it might save some time.
You can use it with MacRuby or with the ruby that comes with RubyMotion in /Library/RubyMotion/bin.

Feel free to beat it up.

Cheers,

k

sakira

unread,
May 10, 2012, 8:27:55 PM5/10/12
to rubymotion
I read the Reference of UIViewController again. nib file will be
looked for and loaded
NOT in 'init:' but 'loadView'. Anyway it works fine. I am happy.

SAkira

sakira

unread,
May 10, 2012, 8:59:29 PM5/10/12
to rubymotion
Yes, Andrew, it works. I just checked it.

With ibtool, execute just like this:
$ ibtool --compile resources/MainStoryboard.storyboardc dummy/dummy/
MainStoryboard.storyboard

and my app_delegate.rb is as follow:
class AppDelegate
def application(application,
didFinishLaunchingWithOptions:launchOptions)

@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
@sb = UIStoryboard.storyboardWithName('MainStoryboard',
bundle:nil)
@window.rootViewController = @sb.instantiateInitialViewController
@window.makeKeyAndVisible

true
end
end

SAkira

Andrew Chalkley

unread,
May 10, 2012, 9:03:28 PM5/10/12
to rubym...@googlegroups.com
EPIC!

Andrew Vega

unread,
May 10, 2012, 9:19:08 PM5/10/12
to rubym...@googlegroups.com
I submitted a support request to have the compilation happen automatically as it currently does for XIB files and the request has been approved. It should appear in a later version. Hopefully soon.

Andrew

sakira

unread,
May 10, 2012, 9:32:05 PM5/10/12
to rubymotion
Hi, Kam

I tried it! It will speed-up the development speed. That's Great!
Because I am newbie of MacRuby, I cannot found a way to build
your MotionGenerator project to a binary file.
So I combined your 'motion generator' and
'OBJCHeaderToRubyGenerator.rb' into single Ruby script.
If there is a way to build, would you show me the pointer? Thank you.

SAkira

davebaldwin

unread,
May 12, 2012, 5:57:06 PM5/12/12
to rubymotion
Here is my take on how to do this. You only need to set up tags if
you have more than one UI element of the same kind. It will also
report errors if the search fails or gives an ambiguous result. Not
convinced these are the best names - still debating if it is better to
use objectiveC conventions or Ruby conventions when extending classes
like this.

Extend UIView and UIViewController classes:

class UIView

# Match an element on its class, tag, or class and tag.
def matchUIElement? (uiClass, uiTag)
(!uiClass || self.kind_of?(uiClass)) && (!uiTag || (uiTag ==
self.tag))
end

def findUIElements (uiClass = nil, uiTag = nil)
match = matchUIElement?(uiClass, uiTag) ? [self] : []
match += self.subviews.find_all {|v| v.matchUIElement?(uiClass,
uiTag)} if self.subviews
match.flatten
end
end

class UIViewController
def uiElement (uiClass = nil, uiTag = nil)
ui = view.findUIElements(uiClass, uiTag)
raise IndexError, "No matches found for class '#{uiClass}' and tag
'#{uiTag}'" if ui.empty?
raise IndexError, "Multiple matches found for class '#{uiClass}' and
tag '#{uiTag}'" if ui.length > 1
ui[0]
end

def actionForUIElement (action, events, uiClass = nil, uiTag = nil)
ui = uiElement(uiClass, uiTag)
ui.addTarget(self, action: action, forControlEvents: events)
ui
end
end

And then in your view controller class do something like:

class TimerController < UIViewController
def loadView
nibContents = NSBundle.mainBundle.loadNibNamed("ViewUI", owner:
self, options: nil)
self.view = nibContents.first

@state = uiElement(UILabel)
@action = actionForUIElement('actionTapped',
UIControlEventTouchUpInside, UIButton)
end

....


Hope this helps someone.

Dave.

Mark Wilden

unread,
May 13, 2012, 5:30:25 PM5/13/12
to rubym...@googlegroups.com
On the other hand, if you have a simple view, you can use the following Ruby idiom to assign subviews to instance variables:

@last_24_hours_label, @last_sleep_label, @energy_label, @sleep_state_label, @button = view.subviews

Reply all
Reply to author
Forward
0 new messages