I have a few goals in this thought:

  1. Implement a UIToolbar, because they are nifty
  2. Add some basic, but ubiquitous, controls, like add, edit, delete
  3. Take a long hard look at the code, and come with a way I would have preferred to write it. This will become my propsal for teacup

teacup is the recently planned DSL that will be developed by the rubymotion community, the first of many we hope! I would love to see a CoreData or sqlite project that can support teacup (saucer?).

First, we create our usual AppDelegate and Controller. I am not going to bother with an iPad controller this time around.

Btw, don’t do this!

“`ruby class MyApplicationController < UIViewController

def init super @leftcontroller = null @rightcontroller = null end

end ”`

Apparently it is not cool to do stuff in init… not sure what is up with that.

Building the UIToolbar is pretty easy, though I certainly had my share of missteps.

“`ruby def viewDidLoad toolbar = UIToolbar.alloc.initWithFrame [[0, 416], [320, 44]] toolbar.tintColor = rgba_color(21, 78, 118) # dark aqua blue? self.view.addSubview(toolbar)

toolbar_button = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemRefresh, target: self, action: :flipp)

toolbar.setItems([toolbar_button], animated:false) end

def flipp # todo… end ”`

This gives us a toolbar with a “refresh” button at the bottom:

  • toolbar.png

    Toolbar Png

For our views (I’m gonna call them left_view_controller and right_view_controller), we will just have a couple plain views with pretty backgrounds:

“`ruby class LeftViewController < UIViewController

def viewDidLoad self.view.backgroundColor = rgba_color(0, 136, 14) # the default frame is at [0, 20] self.view.frame = [[0, 0], [320, 460]] end

end

class RightViewController < UIViewController

def viewDidLoad self.view.backgroundColor = rgba_color(16, 110, 255) self.view.frame = [[0, 0], [320, 460]] end

end ”`

Back in the main ApplicationController, we will assign one or the other to a @current property, which we will then use to determine which one is visible.

“`ruby def viewDidLoad # … @current = leftviewcontroller self.view.insertSubview(@current.view, atIndex:0) end

def leftviewcontroller # if you can imagine what this looks like in Obj-C, you know how nice # this tiny snippet really is. @leftviewcontroller ||= LeftViewController.alloc.init end

def rightviewcontroller @rightviewcontroller ||= RightViewController.alloc.init end ”`

Okay, we’re ready to flipp!

“`ruby def flipp remove = @current

if @current == leftviewcontroller @current = rightviewcontroller else @current = leftviewcontroller end

remove.view.removeFromSuperview self.view.insertSubview(@current.view, atIndex:0) end ”`

This should run now, and when you press the button, the color changes.

LAME

Let’s make it animate! I’ll try to narrate as we go.

“`ruby def flipp # the name "flipp” does not matter, unless you are doing low-level stuff # neither does the context, which needs to be a (void*) anyway, which # is some tricky Macruby code… UIView.beginAnimations(“flipp”, context:nil) UIView.setAnimationDuration(1.25) UIView.setAnimationCurve(UIViewAnimationCurveEaseInOut)

# k, we’ve got our “old” controller, and its view is at remove.view remove = @current

if @current == leftviewcontroller @current = rightviewcontroller # different transition, depending on whether we’re going “left-right” # or “right-left” transition = UIViewAnimationTransitionCurlUp else @current = leftviewcontroller transition = UIViewAnimationTransitionCurlDown end

# transition is easy enough. # forView: is the view that “contains” the animation, which is our # rootController’s view, aka self.view # cache: true means that it should take a snapshot before the animation. # if you were animating a video, you might not cache it, so that the video # would continue during the animation UIView.setAnimationTransition(transition, forView:self.view, cache:true)

# these get called on the controller, not the view. that is unintuitive to # me, so i’m pointing it out. remove.viewWillDisappear(true) @current.viewWillAppear(true)

# now do the actual view code remove.view.removeFromSuperview self.view.insertSubview(@current.view, atIndex:0)

# and say “done”. the system will take some snapshots and animate them… remove.viewDidDisappear(true) @current.viewDidAppear(true)

# … here! UIView.commitAnimations end “`

  • page_curl.png

    Page Curl Png

Disclaimer

This is already (today is Jun 6, 2012) an OLD blog post… I will leave it here, for the sake of historical posterity, but this is not at all how teacup ended up. Check out the project at github.com/rubymotion/teacup.

Also, I will continue to write about teacup as it progresses. This is the very first mention of it on my blog (aw, adorable!).

teacup

I promised I would try and re-think this, and that would be my teacup proposal. Here goes.

At one end of the spectrum is the "don’t fix what works” argument, which is where we’re at right now, and I’m glad that Laurent did not ship rubymotion with a slew of “helpers” or DSLs. Instead, he is the framework/engine guru, and us lowly hackers can mess around with lots of ideas and “see what sticks”.

At the other end is the desire to use Ruby to make writing iOS apps really fun, in the spirit of what Sinatra did for writing web apps. As long as the ability to dig back into the “heart” of Cocoa, I think this approach is fun, too, but I’ve noticed some frameworks out there that don’t go far enough, they provide a very small amount sugar. I think we can go further. Go big or go home, right!? :-)

My main paradigm here is for teacup to accept some stuff, you give it a block where you use a DSL that replaces all the crap that Cocoa wants you to type.

Let’s start at the AppDelegate. Lots of code, just to get a window to load a view controller. We are always going to build a window with the frame UIScreen.mainScreen.bounds, right? And we’ll always makeKeyAndVisible, and always return true? The only thing that changes is the controller class!

ruby Teacup::App do |application, options, window| MyApplicationController # BAM, suckas. end

WOAH, wait, we need ipad / iphone support!

“`ruby def ipad? UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad end

Teacup::App do |application, options, window| if ipad? MyIpadController else MyIphoneController end end ”`

You do need custom window stuff?

“`ruby Teacup::Window do |application, options| window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds) window.makeKeyAndVisible

# do your custom stuff here

window end ”`

There. Now, in our UIViewController, let’s see what we can do about creating the toolbar and buttons.

ruby def viewDidLoad toolbar = Teacup::Toolbar( frame: [[0, 416], [320, 44]], # i like this more than just supporting a bunch of types for :tint, instead # add acolor` method to String (‘#113B66’.color), Symbol (:black.color), and # Array ([bw] => grayscale, [r,g,b], or [r,g,b,a]) tint: [21, 78, 118].color, ) self.view << toolbar # that looks like ruby, right? :-)

target = self toolbar_button = Teacup::BarButtonItem(:refresh) { target.flipp }

toolbar << toolbar_button # setItems:animated:true

@current = leftviewcontroller self.view.insertSubview(@current.view, atIndex:0) # we could change this, but i don’t know… self.view[0, 0] = @current.view # looks weird to me… end ”`

Another way to do the same thing:

“`ruby target = self

toolbar = Teacup::Toolbar( frame: [[0, 416], [320, 44]], tint: [21, 78, 118].color, ) do # self refers to the newly created UIToolbar, so self.BarButtonItem will # know where to add the button toolbar_button = self.BarButtonItem(:refresh) { target.flipp } end ”`

So far so good! Let’s mess with this animation crap.

“`ruby def flipp remove = @current

if @current == leftviewcontroller current = rightviewcontroller transition = :curlup else current = leftviewcontroller transition = :curldown end

@current = current

Teacup::Animate(‘flipp’, # don’t need this! just showing that it is optional # context: nil, duration: 1.25, ease: :easeinout, transition: transition, view: self.view ) do # hmmm, not sure how I feel about THIS: hide remove show current, index: 0 end end ”`

lastly,

Let’s play with styling.

“`ruby class LeftViewController < UIViewController

def viewDidLoad Teacup::style(self.view) do backgroundcolor = [0, 136, 14].color frame = [[0, 0], [320, 460]] statusbar = :black opacity = 0.5 end end

end

class RightViewController < UIViewController

def viewDidLoad Teacup::style(self.view) do backgroundcolor = [0, 136, 14].color frame = [[0, 0], [320, 460]] statusbar = :black opacity = 0.5 end end

end ”`

Code repetition!?

Not on my watch!

“`ruby framed_view = Teacup::Style do frame = [[0, 0], [320, 460]] end

coloredview = Teacup::Style(framedview) do status_bar = :black opacity = 0.5 end

class LeftViewController < UIViewController

def viewDidLoad Teacup::style(self.view, coloredview) do backgroundcolor = [0, 136, 14].color end end

end

class RightViewController < UIViewController

def viewDidLoad Teacup::style(self.view, coloredview) do backgroundcolor = [0, 136, 14].color end end

end ”`

The propsal is at teacup proposal