2013-02-21 23:50:54 -08:00
2013-02-05 12:35:24 -08:00
2013-01-29 13:35:48 -08:00
2012-09-07 16:08:30 -07:00
2013-01-29 13:35:48 -08:00
2012-08-30 09:49:33 -07:00
2012-12-17 16:08:13 -08:00
2013-02-06 14:47:51 -08:00

ProMotion - A new way to easily build RubyMotion apps.

ProMotion introduces a new object called "Screens". Screens have a one-to-one relationship with your app's designed screens.

NEW video tutorial! Go watch it here: http://www.clearsightstudio.com/insights/tutorial-make-youtube-video-app-rubymotion-promotion/

Check out the tutorial here: http://www.clearsightstudio.com/insights/ruby-motion-promotion-tutorial

Sample app here: https://github.com/jamonholmgren/promotion-tutorial

Typical app file structure:

app/
  screens/
    events/
      list_events_screen.rb
      show_event_screen.rb
      edit_event_screen.rb
    home_screen.rb
    settings_screen.rb
  models/
    event.rb
  views/
    buttons/
      save_event_button_view.rb
  app_delegate.rb

What's New in 0.4.0?

  • Screens are now UIViewControllers (they used to contain UIViewControllers, but that got too funky) so you can do normal UIViewController stuff with them
  • Screen functionality can also be inherited as a module in your own UIViewController, but you need to provide your own methods for viewDidLoad and whatnot.
  • Tons of headlessCamelCaps methods are now properly_ruby_underscored (with an alias to the old name for compatibility)
  • open_screen and close_screen can now just be open and close respectively
  • Attempted to keep 100% compatibility with 0.3.x but no guarantees...report issues, please!
  • Revamped the internal folder structure of the gem...more work on this to come
  • Built in a few helpers that were external before, like content_height(view)
  • More consistent calling of on_load (sometimes doesn't get called in 0.3.x)
  • fresh_start SomeScreen is now open_root_screen SomeScreen
  • Removed set_view_controller as we don't need it anymore
  • Better documentation (still needs work), better error messages
  • Deprecation warnings EVERYWHERE for older apps (upgrade already!)

Usage

Loading your home screen:

# In /app/app_delegate.rb
class AppDelegate < ProMotion::AppDelegate
  def on_load(app, options)
    open MyHomeScreen.new(nav_bar: true)
  end
end

Creating a basic screen:

class HomeScreen < ProMotion::Screen
  title "Home"

  def on_load
    # Set up the elements in your view with add_element:
    @label = add_element UILabel.alloc.initWithFrame(CGRectMake(5, 5, 20, 20)), {
      text: "This is awesome!",
      font: UIFont.systemFontOfSize(18)
    }
  end
  
  def on_appear
    # Refresh the data if you want
  end
end

Creating a tabbed bar with multiple screens. This will set the tab bar as the root view controller for your app, so keep that in mind. It can be done from the AppDelegate#on_load or from a screen.

Creating a tab bar with several screens

def on_load(app, options)
  @home     = MyHomeScreen.new(nav_bar: true)
  @settings = SettingsScreen.new
  @contact  = ContactScreen.new(nav_bar: true)
  @tab_bar  = open_tab_bar @home, @settings, @contact
end

For each screen that belongs to the tab bar, you need to set the tab name and icon in the files. In this example, we would need add the following to the three files (my_home_screen.rb, settings_screen.rb, contact_screen.rb):

def on_load
  set_tab_bar_item title: "Tab Name Goes Here", icon: "icons/tab_icon.png" # in resources/icons folder
  
  # or...
  set_tab_bar_item title: "Contacts", system_icon: UITabBarSystemItemContacts
end

Adding view elements

Any view item (UIView, UIButton, custom UIView subclasses, etc) can be used with add_element. The second argument is a hash of settings that get applied to the element before it is dropped into the view.

@label = add_element UILabel.alloc.initWithFrame(CGRectMake(5, 5, 20, 20)), {
  text: "This is awesome!",
  font: UIFont.systemFontOfSize(18)
}

Add nav_bar buttons:

set_nav_bar_right_button "Save", action: :save_something, type: UIBarButtonItemStyleDone
set_nav_bar_left_button "Cancel", action: :return_to_some_other_screen, type: UIBarButtonItemStylePlain

Open a new screen:

def settings_button_tapped
  # ...with a class...
  open SettingsScreen

  # ...or with an instance...
  @settings_screen = SettingsScreen.new
  open @settings_screen
end

Open a new screen as a modal:

open SettingsScreen, modal: true

You can pass in arguments to other screens if they have accessors:

class HomeScreen < ProMotion::Screen
  # ...

  def settings_button_tapped
    open ProfileScreen.new(user: some_user)
  end
end

class ProfileScreen < ProMotion::Screen
  attr_accessor :user

  def on_load
    self.user # => some_user instance
  end
end

Close a screen (modal or in a nav controller), passing back arguments to the previous screen's "on_return" method:

class ItemScreen < ProMotion::Screen
  # ...
  def save_and_close
    if @model.save
      close(model_saved: true)
    end
  end
end

class MainScreen < ProMotion::Screen
  # ...
  def on_return(args = {})
    if args[:model_saved]
      self.reload_something
    end
  end
end

The helper add_element takes any view object and adds it to the current view. You can also use the helper ProMotion::ViewHelper.set_attributes(view, attributes) to do the same thing without adding it to the current view. Screens include this helper by default.

@element = add_element UIView.alloc.initWithFrame(CGRectMake(0, 0, 20, 20)), {
  backgroundColor: UIColor.whiteColor
}

@element = set_attributes UIView.alloc.initWithFrame(CGRectMake(0, 0, 20, 20)), {
  backgroundColor: UIColor.whiteColor
}

You can create sectioned table screens easily. TableScreen, SectionedTableScreen, GroupedTableScreen. This is loosely based on motion-table (there are a few minor differences). We will eventually combine the two.

class SettingsScreen < ProMotion::GroupedTableScreen
  title "Settings"

  def on_load
    add_right_nav_button(label: "Save", action: :save)
    set_tab_bar_item(title: "Settings", icon: "settings.png")
  end
  
  # table_data is automatically called. Use this format in the return value.
  # Grouped tables are the same as plain tables
  def table_data
    [{
      title: "Your Account",
      cells: [
        { title: "Edit Profile", action: :edit_profile, arguments: { id: 3 } },
        { title: "Log Out", action: :log_out },
        { title: "Notification Settings", action: :notification_settings }
      ]
    }, {
      title: "App Stuff",
      cells: [
        { title: "About", action: :show_about },
        { title: "Feedback", action: :show_feedback }
      ]
    }]
  end

  # This method allows you to create a "jumplist", the index on the right side of the table
  def table_data_index
    return ["A", "B", "C"]
  end
  
  # Your table cells, when tapped, will execute the corresponding actions and pass in arguments:
  def edit_profile(arguments)
    # ...
  end
end

You can provide remotely downloaded images for cells by including the CocoaPod "SDWebImage" in your Rakefile and doing this:

  cells: [
    {
      title: "Cell with image",
      remoteImage: { url: "http://placekitten.com/200/300", placeholder: "some-local-image" }
    }
  ]

Using your own UIViewController or Formotion

Sometimes you want to inherit from a different UIViewController than that provided by ProMotion, such as when using Formotion. RubyMotion doesn't currently allow us to override built-in methods when including as a module, so there's a workaround for that.

class EventsScreen < Formotion::FormController # Can also be < UIViewController
  include ProMotion::ScreenModule # Not TableScreenModule since we're using Formotion for that

  # Required functions for ProMotion to work properly

  def viewDidLoad
    super
    self.view_did_load if self.respond_to?(:view_did_load)
  end

  def viewWillAppear(animated)
    super
    self.view_will_appear(animated) if self.respond_to?("view_will_appear:")
  end

  def viewDidAppear(animated)
    super
    self.view_did_appear(animated) if self.respond_to?("view_did_appear:")
  end
  
  def viewWillDisappear(animated)
    self.view_will_disappear(animated) if self.respond_to?("view_will_disappear:")
    super
  end
  
  def viewDidDisappear(animated)
    self.view_did_disappear(animated) if self.respond_to?("view_did_disappear:")
    super      
  end

  def shouldAutorotateToInterfaceOrientation(orientation)
    self.should_rotate(orientation)
  end

  def shouldAutorotate
    self.should_autorotate
  end

  def willRotateToInterfaceOrientation(orientation, duration:duration)
    self.will_rotate(orientation, duration)
  end
  
  def didRotateFromInterfaceOrientation(orientation)
    self.on_rotate
  end
end

Reference

(not comprehensive yet...working on this)

Class or Module Method Description
Screen is_modal? Returns if the screen was opened in a modal window.
  has_nav_bar? Returns if the screen is contained in a navigation controller.
  set_tab_bar_item(args) Creates the tab that is shown in a tab bar item.
Arguments: { icon: "imagename", systemIcon: UISystemIconContacts, title: "tabtitle" }
  on_appear Callback for when the screen appears.
  will_appear Callback for before the screen appears.
  will_disappear Callback for before the screen disappears.
  will_rotate(orientation, duration) Callback for before the screen rotates.
  on_opened **Deprecated** Callback when screen is opened via a tab bar. Please don't use this, as it will be removed in the future
Use will_appear
  set_nav_bar_left_button(title, args = {}) Set a left nav bar button.
  set_nav_bar_right_button(title, args = {}) Set a right nav bar button.
  should_autorotate iOS 5 return true/false if screen should rotate
  should_rotate(orientation) Return true/false for rotation to orientation.
  supported_orientation?(orientation) Returns true/false if orientation is in NSBundle.mainBundle.infoDictionary["UISupportedInterfaceOrientations"].
Shouldn't need to override this.
  supported_orientations Returns supported orientation mask
  title Returns title of current screen.
  title=(title) Sets title of current screen.
ScreenElements
Included in Screen by default
add_element(view, attrs = {}) Adds the view to the screen after applying the attributes.
  remove_element Removes the view from the superview and sets it to nil
  bounds Accessor for self.view.bounds
  frame Accessor for self.view.frame
  view Accessor for self.view
SystemHelper
Included in Screen by default
ios_version Returns the iOS version that is running on the device
  ios_version_greater?(version) Returns true if 'ios_version' is greater than the version passed in, false otherwise
  ios_version_greater_eq?(version) Returns true if 'ios_version' is greater than or equal to the version passed in, false otherwise
  ios_version_is?(version) Returns true if 'ios_version' is equal to the version passed in, false otherwise
  ios_version_less?(version) Returns true if 'ios_version' is less than the version passed in, false otherwise
  ios_version_less_eq?(version) Returns true if 'ios_version' is less than or equal to the version passed in, false otherwise
ScreenNavigation
included in Screen
app_delegate Returns the AppDelegate
  close(args = {}) Closes the current screen, passes args back to the previous screen's on_return method
  open_root_screen(screen) Closes all other open screens and opens `screen` at the root.
  open(screen, args = {}) Pushes the screen onto the navigation stack or opens in a modal
argument options :hide_tab_bar, :modal, any accessors in `screen`
  open_tab(tab) Opens the tab where the "string" title is equal to the passed in tab
  open_tab_bar(*screens) Open a UITabBarController with the specified screens as the root view controller of the current app

What about MVC?

I'm a big believer in MVC (I'm a Rails developer, too). I found that most of the time working in RubyMotion seems to happen in the ViewControllers and views are mainly custom elements. This pattern is probably best for navigation controller and tab bar based apps.

Feedback welcome via twitter @jamonholmgren or email jamon@clearsightstudio.com.

Contributing

I'm really looking for feedback. Tweet me with your ideas or open a ticket (I don't mind!) and let's discuss.

Description
No description provided
Readme MIT 2 MiB
Languages
Ruby 100%