README.md in ProMotion-0.0.2 vs README.md in ProMotion-0.1.0

- old
+ new

@@ -1,13 +1,10 @@ -# ProMotion +# ProMotion - A new way to organize RubyMotion apps. -**Please note: this is a proof of concept and does not yet work.** +ProMotion introduces a new object called "Screens". Screens have a one-to-one relationship +with your app's screens and can (usually) take the place of view controllers. -ProMotion is a new way to organize RubyMotion apps. Instead of dealing -with UIViewControllers and UIViews, you work with Screens. Screens are -a logical way to think of your app. - Typical /app file structure: app screens photos @@ -15,277 +12,163 @@ show_photo_screen.rb edit_photo_screen.rb home_screen.rb settings_screen.rb models + view_controllers views app_delegate.rb -The "views" folder contains custom view components, written in normal RubyMotion. "models" can be whatever ORM you're using. - -### 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. This pattern may be best for simpler, smaller apps. - -This is a proof of concept. I'd really appreciate feedback on it at my email address (jamon@clearsightstudio.com) or Twitter (@jamonholmgren). - -## Installation - -Add this line to your application's Gemfile: - - gem 'ProMotion' - -And then execute: - - $ bundle - -Or install it yourself as: - - $ gem install ProMotion - ## Usage -It's easy to load your first screen with a navigation bar (the screen is opened in a UINavigationController behind the scenes): +Loading your home screen: ```ruby -# In /app/app_delegate.rb (note that AppDelegate extends ProMotion::AppDelegate) -class AppDelegate < ProMotion::AppDelegate - def application(application, didFinishLaunchingWithOptions:launchOptions) - open_with_nav_bar HomeScreen - - true +# In /app/app_delegate.rb (note that AppDelegate extends ProMotion::AppDelegateParent) +class AppDelegate < ProMotion::AppDelegateParent + def on_load(options) + open_screen MyHomeScreen.new(nav_bar: true) end end ``` -Screens are pretty straightforward. You extend ProMotion::Screen and provide a title and an on_load method. +Creating a basic screen: ```ruby -# In /app/screens/home_screen.rb: class HomeScreen < ProMotion::Screen - # Set the title for use in nav bars and other containers title "Home" - # Called when this screen is first "opened" and allows you to set up your view components def on_load - @default_image = add_image(:default_image, src: "default.png", frame: [10, 50, 100, 100]) + # 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.UIFont.systemFontOfSize(18) + } end + + def on_appear + # Refresh the data if you want + end end ``` -In on_load, you can add images, buttons, labels, custom views to your screen. +Creating a tabbed bar: ```ruby -# In /app/screens/home_screen.rb: -class HomeScreen < ProMotion::Screen - # Set the title for use in nav bars and other containers - title "Home" - - def on_load - # Add view items as instance vars so you can access them in other methods - - # This adds a right nav bar button. on_tap allows you to set a method to call when it's tapped. - @right_bar_button = add_right_nav_button(label: "Save", on_tap: :save) - - # Helper function for adding a button - @settings_button = add_button(label: "Settings", frame: [10, 10, 100, 30]) - - # Helper function for adding an image - @default_image = add_image(:default_image, src: "default.png", frame: [10, 50, 100, 100]) - - # You can also add custom UIViews through the add_view method. - @custom_view = add_view(ChatView.alloc.initWithFrame(CGRectMake(10, 300, 40, 40))) - end +def on_load(options) + @home = MyHomeScreen.new(nav_bar: true) + @settings = SettingsScreen.new + @contact = ContactScreen.new(nav_bar: true) + open_tab_bar @home, @settings, @contact end ``` -View components can be bound to events (like jQuery) and run methods or run a block. +Any view item (UIView, UIButton, 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. ```ruby -# settings_pushed is executed when the button is tapped -@settings_button = add_button(label: "Settings", frame: [10, 10, 100, 30]) -@settings_button.on(:tap, :settings_pushed) +@label = add_element UILabel.alloc.initWithFrame(CGRectMake(5, 5, 20, 20)), { + text: "This is awesome!", + font: UIFont.UIFont.systemFontOfSize(18) +} +``` -# This demonstrates a block -@settings_button.on(:tap) do - # Do something -end +Add a nav_bar button and a tab_bar icon: -# This button passes in arguments to the method when it's tapped -@edit_button = add_button(label: "Edit", frame: [10, 10, 100, 30]) -@edit_button.on(:tap, :edit_pushed, id: 4) +```ruby +add_right_nav_button(label: "Save", action: :save) +set_tab_bar_item(title: "Contacts", system_icon: UITabBarSystemItemContacts) ``` -To open other screens, just call the built-in "open" method on a new instance or class: +Open a new screen: ```ruby def settings_button_tapped # ...with a class... - open SettingsScreen + open_screen SettingsScreen # ...or with an instance... - @settings_screen = SettingsScreen.new(some_attr: 4) - open @settings_screen - - # ...or if you like... - open SettingsScreen.new + @settings_screen = SettingsScreen.new + open_screen @settings_screen end ``` -You can pass in arguments to those screens if they have accessors: +You can pass in arguments to other screens if they have accessors: ```ruby -# /app/screens/settings_screen.rb -class SettingsScreen < ProMotion::Screen - attr_accessor :user_type - - def on_load - if self.user_type == "Admin" - # Stuff - end - end - - # ... -end - -# /app/screens/home_screen.rb class HomeScreen < ProMotion::Screen # ... def settings_button_tapped - open SettingsScreen.new(user_type: "Admin") + open_screen ProfileScreen.new(user: some_user) end end -``` -When you're done with a screen, just close it: +class ProfileScreen < ProMotion::Screen + attr_accessor :user -```ruby -def save_and_close - if @model.save - close + def on_load + self.user # => some_user instance end end + ``` -If you want to pass arguments back to the previous screen, go for it. +Close a screen, passing back arguments to the previous screen's "on_return" method: ```ruby -class SettingsScreen < ProMotion::Screen +class ItemScreen # ... - def save_and_close - close(saved_changes: true) + if @model.save + close_screen(model_saved: true) + end end end class MainScreen < ProMotion::Screen # ... - def on_return(args = {}) - if args[:saved_changes] + if args[:model_saved] self.reload_something end end end ``` -You can create sectioned table screens easily. +Use a custom view controller: ```ruby -class HomeScreen < ProMotion::Screen - title "Home" - - # Defaults to :normal. :plain_table, :grouped_table are options. - screen_type :grouped_table - - def on_load - # No need to set anything up, really - end - - # If you define your screen_type as some sort of table, this gets called to get the data. - # You can also refresh the table data manually by calling `self.reload_table_data` - def table_data - # You can create a new table section here and add cells to it like so: - @account_section = add_section(label: "Your Account") - @account_section.add_cell(title: "Edit Profile", action: :edit_profile, arguments: { account_id: @account.id }) - @account_section.add_cell(title: "Log Out", action: :log_out) - - # Or just pass back an array with everything defined and we'll build it for you: - [{ - title: "Your Account", - cells: [ - { title: "Edit Profile", action: :edit_profile }, - { 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 +def on_load + set_view_controller MyCustomViewController + + # Note: on_appear will not fire when using a custom + # view controller. end ``` -Here's a full demo of a screen: +The helper add_element takes a +You can create sectioned table screens easily. TableScreen, SectionedTableScreen, GroupedTableScreen + ```ruby -# In /app/screens/home_screen.rb: +class SettingsScreen < ProMotion::GroupedTableScreen + title "Settings" -class HomeScreen < ProMotion::Screen - # Accessors allow screens to set parameters when opening this screen - attr_accessor :foo - - # Set the title for use in nav bars and other containers - title "Home" - - # Defaults to :normal. :plain_table, :grouped_table are options. - screen_type :plain_table - - # Called when this screen is first "opened" and allows you to set up your view components def on_load - # Add view items as instance vars so you can access them in other methods - # This adds a right nav bar button. on_tap allows you to set a method to call when it's tapped. - @right_bar_button = add_right_nav_button(label: "Save", on_tap: :save) - - # Helper function for adding a button - @settings_button = add_button(label: "Settings", frame: [10, 10, 100, 30]) - - # View items can be bound to events (like jQuery) and run methods or run a block. - @settings_button.on(:tap, :settings_pushed) - @settings_button.on(:tapHold) do - # Do something - end - - # Helper function for adding an image - @default_image = add_image(:default_image, src: "default.png", frame: [10, 50, 100, 100]) - - # This button passes in arguments to the method when it's tapped - @edit_button = add_button(label: "Edit", frame: [10, 10, 100, 30]) - @edit_button.on(:tap, :edit_pushed, id: 4) - - # You can also add custom UIViews through the add_view method. - @custom_view = add_view(ChatView.alloc.initWithFrame(CGRectMake(10, 300, 40, 40))) + add_right_nav_button(label: "Save", action: :save) + set_tab_bar_item(title: "Settings", icon: "settings.png") end - - # If you define your screen_type as some sort of table, this gets called to get the data. - # You can also refresh the table data manually by calling `self.reload_table_data` + + # table_data is automatically called. Use this format in the return value. + # Grouped tables are the same as plain tables def table_data - # You can create a new table section here and add cells to it like so: - @account_section = add_section(label: "Your Account") - @account_section.add_cell(title: "Edit Profile", action: :edit_profile, arguments: { account_id: @account.id }) - @account_section.add_cell(title: "Log Out", action: :log_out) - - # Or just pass back an array with everything defined and we'll build it for you: [{ title: "Your Account", cells: [ - { title: "Edit Profile", action: :edit_profile }, + { title: "Edit Profile", action: :edit_profile, arguments: { id: 3 } }, { title: "Log Out", action: :log_out }, { title: "Notification Settings", action: :notification_settings } ] }, { title: "App Stuff", @@ -294,54 +177,27 @@ { title: "Feedback", action: :show_feedback } ] }] end - # Custom method, invoked when tapping something with this as the action - def save - # Assuming some sort of ORM, like ParseModel - @my_model.save - - # When you want to close the current screen (usually in a navigation controller), just run this. - close - - # You can also pass back arguments to the previous screen as you close. - # If the previous screen has an `on_return` method, this will be passed into that method - close(did_stuff: true) + # 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 - - # This is called any time a screen "above" this screen is closed. args = {} is required. - def on_return(args = {}) - if args[:did_stuff] - # Refresh? - end + + # Your table cells, when tapped, will execute the corresponding actions and pass in arguments: + def edit_profile(arguments) + # ... end - - # Custom method - def settings_pushed - # Just open a settings screen - open SettingsScreen - - # If you prefer to pass in an instance, that works too: - open SettingsScreen.new - end - - def close_pushed - close - end - - # Custom method with passed in arguments - def edit_pushed(args) - # Open a screen and set some of its attributes - open EditScreen.new(id: args[:id]) - end end ``` +### 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 may be best for simpler, smaller apps. + +Feedback welcome via twitter @jamonholmgren. + ## Contributing -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Added some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request +I'm really looking for feedback. Tweet me with your ideas or open a ticket (I don't mind!) and let's discuss.