Merge branch 'refs/heads/version-0.6' into version-0.6-tableview-fixes

* refs/heads/version-0.6: (25 commits)
  Added example to readme for cell height.
  Allow the user to specify individual cell height in the cell hash.  If not specified, the cell with use the tableView.rowHeight property.
  Return element after set_attribute.
  Use snake_case for Objective-C methods. Fixes Issue #56.
  Fixing bug in set_nav_bar button methods
  README update for add improvements
  `set_easy_attributes` now working. autoresizingMask and frame are the first improvements this brings.
  Updated table screen specs
  Initial stab at tablescreen tests
  Minor refactors to clean up the screen module
  Added an  method for adding any view to any other parent view with attributes. Closes issue #18.
  Huzzah! Works brilliantly in ios 6 and ios 5 now!
  Better handling of the callback param.
  Modify console log to name the actual on_refresh method they need to implement if they defined a different callback.
  Use more ruby syntax instead of Obj-C syntax.
  Add a warning if the user didn't implement the on_refresh method or they specified a callback method but didn't implement it.
  Made callback pattern more consistent. Made all strings configurable. Updated documentation.
  Better readme formatting for refreshable.
  fix readme typo.
  Make sure that we're running on ios6 or higher
  ...
This commit is contained in:
Mark Rickert
2013-05-07 22:53:50 -04:00
32 changed files with 562 additions and 140 deletions

View File

@@ -141,6 +141,7 @@ Run `rake`. You should now see the simulator open with your home screen and a na
* Added `open_split_screen` for iPad-supported apps (thanks @rheoli for your contributions to this)
* `ProMotion::AppDelegateParent` renamed to `ProMotion::Delegate` (`AppDelegateParent` is an alias)
* Added `add_to` method for adding views to any parent view. `remove` works with this normally.
# Usage
@@ -329,10 +330,17 @@ Any view item (UIView, UIButton, custom UIView subclasses, etc) can be added to
`add` accepts a second argument which is a hash of attributes that get applied to the element before it is
dropped into the view.
`add(view, attr={})`
```ruby
@label = add UILabel.alloc.initWithFrame(CGRectMake(5, 5, 20, 20)), {
@label = add UILabel.new, {
text: "This is awesome!",
font: UIFont.systemFontOfSize(18)
font: UIFont.systemFontOfSize(18),
resize: [ :left, :right, :top, :bottom, :width, :height ], # autoresizingMask
left: 5, # These four attributes are used with CGRectMake
top: 5,
width: 20,
height: 20
}
@element = add UIView.alloc.initWithFrame(CGRectMake(0, 0, 20, 20)), {
@@ -341,9 +349,24 @@ dropped into the view.
```
The `set_attributes` method is identical to add except that it does not add it to the current view.
If you use snake_case and there isn't an existing method, it'll try camelCase. This allows you to
use snake_case for Objective-C methods.
`set_attributes(view, attr={})`
```ruby
@element = set_attributes UIView.alloc.initWithFrame(CGRectMake(0, 0, 20, 20)), {
# `background_color` is translated to `backgroundColor` automatically.
background_color: UIColor.whiteColor
}
```
You can use `add_to` to add a view to any other view, not just the main view.
`add_to(parent_view, new_view, attr={})`
```ruby
add_to @some_parent_view, UIView.alloc.initWithFrame(CGRectMake(0, 0, 20, 20)), {
backgroundColor: UIColor.whiteColor
}
```
@@ -684,6 +707,21 @@ end
<td>searchable(placeholder: "placeholder text")</td>
<td>Class method to make the current table searchable.</td>
</tr>
<tr>
<td><pre><code>refreshable(
callback: :on_refresh,
pull_message: "Pull to refresh",
refreshing: "Refreshing data…",
updated_format: "Last updated at %s",
updated_time_format: "%l:%M %p"
)</code></pre></td>
<td>Class method to make the current table refreshable.
<p>All parameters are optional. If you do not specify a a callback, it will assume you've implemented an <code>on_refresh</code> method in your tableview.</p>
<pre><code>def on_refresh
# Code to start the refresh
end</code></pre>
<p>And after you're done with your asyncronous process, call <code>end_refreshing</code> to collapse the refresh view and update the last refreshed time and then <code>update_table_data</code>.</p></td>
</tr>
<tr>
<td colspan="2">
<h3>table_data</h3>
@@ -709,6 +747,7 @@ def table_data
subtitle: "This is way too huge..see note",
arguments: { data: [ "lots", "of", "data" ] },
action: :tapped_cell_1,
height: 50, # manually changes the cell's height
cell_style: UITableViewCellStyleSubtitle,
cell_identifier: "Cell",
cell_class: ProMotion::TableViewCell,
@@ -777,6 +816,15 @@ Opening a ticket is usually the best and we respond to those pretty quickly.
I'm very open to ideas. Tweet me with your ideas or open a ticket (I don't mind!)
and let's discuss.
## Working on Features
1. Clone the repos into `Your-Project/Vendor/ProMotion`
2. Update your `Gemfile`to reference the project as `gem 'ProMotion', :path => "vendor/ProMotion/"`
3. If you're also using [BubbleWrap](http://www.bubblewrap.io), add this line to your `Rakefile`: `app.detect_dependencies = false` *(This is a RubyMotion bug that should be resolved soon)*
4. Run `bundle`
5. Run `rake clean` and then `rake`
6. Contribute!
## Submitting a Pull Request
1. Fork the project

View File

@@ -12,7 +12,7 @@ Motion::Project::App.setup do |app|
app.redgreen_style = :focused # :focused, :full
# Devices
app.deployment_target = "5.0"
app.deployment_target = "6.0"
app.device_family = [:ipad] # so we can test split screen capability
app.detect_dependencies = true

View File

@@ -1,2 +1,5 @@
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
return true if RUBYMOTION_ENV == 'test'
end
end

View File

@@ -14,7 +14,7 @@ class SplitViewController < UISplitViewController
# set the button from the old detail screen to the new one
button = detail_screen.navigationItem.leftBarButtonItem
s.navigationItem.leftBarButtonItem = button
self.viewControllers = [self.viewControllers.first, s.main_controller]
end
def screens=(s_array)

View File

@@ -20,15 +20,15 @@ module ProMotion
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
super
end
def viewDidDisappear(animated)
self.view_did_disappear(animated) if self.respond_to?("view_did_disappear:")
super
super
end
def shouldAutorotateToInterfaceOrientation(orientation)
@@ -42,7 +42,7 @@ module ProMotion
def willRotateToInterfaceOrientation(orientation, duration:duration)
self.will_rotate(orientation, duration)
end
def didRotateFromInterfaceOrientation(orientation)
self.on_rotate
end

View File

@@ -20,17 +20,17 @@ module ProMotion
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
super
end
def viewDidDisappear(animated)
if self.respond_to?("view_did_disappear:")
self.view_did_disappear(animated)
end
super
super
end
def shouldAutorotateToInterfaceOrientation(orientation)
@@ -44,7 +44,7 @@ module ProMotion
def willRotateToInterfaceOrientation(orientation, duration:duration)
self.will_rotate(orientation, duration)
end
def didRotateFromInterfaceOrientation(orientation)
self.on_rotate
end

View File

@@ -3,7 +3,7 @@ module ProMotion
include ProMotion::ScreenTabs
include ProMotion::SplitScreen if NSBundle.mainBundle.infoDictionary["UIDeviceFamily"].include?("2")
attr_accessor :window
def application(application, didFinishLaunchingWithOptions:launch_options)
return true if RUBYMOTION_ENV == "test"
@@ -15,7 +15,7 @@ module ProMotion
true
end
def app_delegate
UIApplication.sharedApplication.delegate
end
@@ -50,7 +50,7 @@ module ProMotion
get_home_screen.send(:on_load) if get_home_screen.respond_to?(:on_load)
load_root_screen get_home_screen
end
def get_home_screen
@home_screen
end

View File

@@ -3,15 +3,15 @@ module ProMotion
NAME = "RubyMotion::Console: "
DEFAULT_COLOR = [ '', '' ]
RED_COLOR = [ "\e[0;31m", "\e[0m" ] # Must be in double quotes
GREEN_COLOR = [ "\e[0;32m", "\e[0m" ]
PURPLE_COLOR = [ "\e[0;35m", "\e[0m" ]
GREEN_COLOR = [ "\e[0;32m", "\e[0m" ]
PURPLE_COLOR = [ "\e[0;35m", "\e[0m" ]
class << self
def log(log, with_color:color)
return if RUBYMOTION_ENV == "test"
puts color[0] + NAME + log + color[1]
end
def log(log, withColor:color)
return if RUBYMOTION_ENV == "test"
warn "[DEPRECATION] `log(log, withColor:color)` is deprecated. Use `log(log, with_color:color)`"

View File

@@ -1,19 +1,52 @@
module ProMotion
module ViewHelper
def set_attributes(element, args = {})
args.each do |k, v|
if v.is_a? Hash
sub_element = element.send(k)
set_attributes sub_element, v
elsif v.is_a? Array
element.send("#{k}", *v) if element.respond_to?("#{k}")
else
element.send("#{k}=", v) if element.respond_to?("#{k}=")
args.each { |k, v| set_attribute(element, k, v) }
element
end
def set_attribute(element, k, v)
if v.is_a?(Hash) && element.respond_to?(k)
sub_element = element.send(k)
set_attributes sub_element, v
elsif v.is_a?(Array) && element.respond_to?("#{k}")
element.send("#{k}", *v)
elsif element.respond_to?("#{k}=")
element.send("#{k}=", v)
else
# Doesn't respond. Check if snake case.
if k.to_s.include?("_")
set_attribute(element, objective_c_method_name(k), v)
end
end
element
end
def objective_c_method_name(meth)
meth.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
end
def set_easy_attributes(parent, element, args={})
attributes = {}
if args[:resize]
attributes[:autoresizingMask] = UIViewAutoresizingNone
attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleLeftMargin if args[:resize].include?(:left)
attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleRightMargin if args[:resize].include?(:right)
attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleTopMargin if args[:resize].include?(:top)
attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleBottomMargin if args[:resize].include?(:bottom)
attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleWidth if args[:resize].include?(:width)
attributes[:autoresizingMask] |= UIViewAutoresizingFlexibleHeight if args[:resize].include?(:height)
end
if [:left, :top, :width, :height].select{ |a| args[a] && args[a] != :auto }.length == 4
attributes[:frame] = CGRectMake(args[:left], args[:top], args[:width], args[:height])
end
set_attributes element, attributes
element
end
def frame_from_array(array)
return CGRectMake(array[0], array[1], array[2], array[3]) if array.length == 4
Console.log(" - frame_from_array expects an array with four elements: [x, y, width, height]", with_color: Console::RED_COLOR)
@@ -32,5 +65,9 @@ module ProMotion
end
height
end
def positioning_attributes(attr={})
end
end
end

View File

@@ -0,0 +1,42 @@
module ProMotion::MotionTable
module RefreshableTable
def make_refreshable(params={})
pull_message = params[:pull_message] || "Pull to refresh"
@refreshing = params[:refreshing] || "Refreshing data..."
@updated_format = params[:updated_format] || "Last updated at %s"
@updated_time_format = params[:updated_time_format] || "%l:%M %p"
@refreshable_callback = params[:callback]|| :on_refresh
@refresh_control = UIRefreshControl.alloc.init
@refresh_control.attributedTitle = NSAttributedString.alloc.initWithString(pull_message)
@refresh_control.addTarget(self, action:'refreshView:', forControlEvents:UIControlEventValueChanged)
self.refreshControl = @refresh_control
end
alias :makeRefreshable :make_refreshable
######### iOS methods, headless camel case #######
# UIRefreshControl Delegates
def refreshView(refresh)
refresh.attributedTitle = NSAttributedString.alloc.initWithString(@refreshing)
if @refreshable_callback && self.respond_to?(@refreshable_callback)
self.send(@refreshable_callback)
else
ProMotion::Console.log("ProMotion Warning: you must implement the '#{@refreshable_callback}' method in your TableScreen.", with_color: ProMotion::Console::RED_COLOR)
end
end
def start_refreshing
return unless @refresh_control
@refresh_control.beginRefreshing
end
def end_refreshing
return unless @refresh_control
@refresh_control.attributedTitle = NSAttributedString.alloc.initWithString(sprintf(@updated_format, Time.now.strftime(@updated_time_format)))
@refresh_control.endRefreshing
end
end
end

View File

@@ -22,13 +22,13 @@ module ProMotion::MotionTable
@contacts_search_display_controller.delegate = params[:delegate]
@contacts_search_display_controller.searchResultsDataSource = params[:data_source]
@contacts_search_display_controller.searchResultsDelegate = params[:search_results_delegate]
self.table_view.tableHeaderView = search_bar
end
alias :makeSearchable :make_searchable
######### iOS methods, headless camel case #######
def searchDisplayController(controller, shouldReloadTableForSearchString:search_string)
@mt_filtered_data = nil
@mt_filtered_data = []

View File

@@ -7,6 +7,13 @@ module ProMotion::MotionTable
if self.class.respond_to?(:get_searchable) && self.class.get_searchable
self.make_searchable(content_controller: self, search_bar: self.class.get_searchable_params)
end
if self.class.respond_to?(:get_refreshable) && self.class.get_refreshable
if defined?(UIRefreshControl)
self.make_refreshable(self.class.get_refreshable_params)
else
ProMotion::Console.log("ProMotion Warning: to use the refresh control on < iOS 6, you need to include the Cocoapod 'CKRefreshControl'.", with_color: ProMotion::Console::RED_COLOR)
end
end
end
# @param [Array] Array of table data
@@ -55,7 +62,7 @@ module ProMotion::MotionTable
ProMotion::Console.log("Action not implemented: #{action.to_s}", with_color: ProMotion::Console::RED_COLOR)
end
end
def set_cell_attributes(element, args = {})
args.each do |k, v|
if v.is_a? Hash
@@ -78,7 +85,7 @@ module ProMotion::MotionTable
data_cell[:arguments] = {} unless data_cell[:arguments]
data_cell[:arguments][:value] = switch.isOn if data_cell[:arguments].is_a? Hash
data_cell[:accessory_action] ||= data_cell[:accessoryAction] # For legacy support
trigger_action(data_cell[:accessory_action], data_cell[:arguments]) if data_cell[:accessory_action]
end
@@ -105,14 +112,14 @@ module ProMotion::MotionTable
# Set table_data_index if you want the right hand index column (jumplist)
def sectionIndexTitlesForTableView(table_view)
if self.respond_to?(:table_data_index)
self.table_data_index
self.table_data_index
end
end
def remap_data_cell(data_cell)
# Re-maps legacy data cell calls
mappings = {
cell_style: :cellStyle,
mappings = {
cell_style: :cellStyle,
cell_identifier: :cellIdentifier,
cell_class: :cellClass,
masks_to_bounds: :masksToBounds,
@@ -143,9 +150,9 @@ module ProMotion::MotionTable
data_cell = cell_at_section_and_index(indexPath.section, indexPath.row)
return UITableViewCell.alloc.init unless data_cell
data_cell = self.remap_data_cell(data_cell)
data_cell[:cell_style] ||= UITableViewCellStyleDefault
data_cell[:cell_identifier] ||= "Cell"
cell_identifier = data_cell[:cell_identifier]
@@ -154,7 +161,7 @@ module ProMotion::MotionTable
table_cell = table_view.dequeueReusableCellWithIdentifier(cell_identifier)
unless table_cell
table_cell = data_cell[:cell_class].alloc.initWithStyle(data_cell[:cell_style], reuseIdentifier:cell_identifier)
# Add optimizations here
table_cell.layer.masksToBounds = true if data_cell[:masks_to_bounds]
table_cell.backgroundColor = data_cell[:background_color] if data_cell[:background_color]
@@ -165,7 +172,7 @@ module ProMotion::MotionTable
if data_cell[:cell_class_attributes]
set_cell_attributes table_cell, data_cell[:cell_class_attributes]
end
if data_cell[:accessory_view]
table_cell.accessoryView = data_cell[:accessory_view]
table_cell.accessoryView.autoresizingMask = UIViewAutoresizingFlexibleWidth
@@ -254,6 +261,15 @@ module ProMotion::MotionTable
return table_cell
end
def tableView(tableView, heightForRowAtIndexPath:indexPath)
cell = cell_at_section_and_index(indexPath.section, indexPath.row)
if cell[:height]
cell[:height].to_f
else
tableView.rowHeight
end
end
def tableView(table_view, didSelectRowAtIndexPath:indexPath)
cell = cell_at_section_and_index(indexPath.section, indexPath.row)
table_view.deselectRowAtIndexPath(indexPath, animated: true);

View File

@@ -1,7 +1,8 @@
module ProMotion::MotionTable
module GroupedTable
include SectionedTable
include RefreshableTable
def table_view
@table_view ||= UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStyleGrouped)
@table_view.dataSource = self;

View File

@@ -2,6 +2,7 @@ module ProMotion::MotionTable
module PlainTable
include SectionedTable
include SearchableTable
include RefreshableTable
def table_view
@table_view ||= UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStylePlain)

View File

@@ -1,28 +1,33 @@
module ProMotion
module ScreenElements
include ProMotion::ViewHelper
def add(v, attrs = {})
if attrs && attrs.length > 0
set_attributes(v, attrs)
end
self.view.addSubview(v)
v
def add(element, attrs = {})
add_to self.view, element, attrs
end
alias :add_element :add
alias :add_view :add
def remove(v)
v.removeFromSuperview
v = nil
def remove(element)
element.removeFromSuperview
element = nil
end
alias :remove_element :remove
alias :remove_view :remove
def add_to(parent_element, element, attrs = {})
if attrs && attrs.length > 0
set_attributes(element, attrs)
set_easy_attributes(parent_element, element, attrs)
end
parent_element.addSubview element
element
end
def bounds
return self.view.bounds
end
def frame
return self.view.frame
end

View File

@@ -2,20 +2,20 @@ module ProMotion
module ScreenNavigation
def open_screen(screen, args = {})
# Apply properties to instance
screen = setup_screen_for_open(screen, args)
ensure_wrapper_controller_in_place(screen, args)
screen.send(:on_load) if screen.respond_to?(:on_load)
screen.send(:on_load) if screen.respond_to?(:on_load)
animated = args[:animated] || true
return self.split_screen.detail_screen = screen if args[:in_detail] && self.split_screen
return self.split_screen.master_screen = screen if args[:in_master] && self.split_screen
if args[:close_all]
open_root_screen screen
elsif args[:modal]
present_modal_view_controller screen, animated
@@ -44,7 +44,7 @@ module ProMotion
def app_delegate
UIApplication.sharedApplication.delegate
end
def close_screen(args = {})
args ||= {}
args[:animated] ||= true
@@ -58,7 +58,7 @@ module ProMotion
else
Console.log("Tried to close #{self.to_s}; however, this screen isn't modal or in a nav bar.", withColor: Console::PURPLE_COLOR)
end
end
alias :close :close_screen
@@ -95,7 +95,7 @@ module ProMotion
screen.parent_screen = self if screen.respond_to?("parent_screen=")
screen.title = args[:title] if args[:title] && screen.respond_to?("title=")
screen.modal = args[:modal] if args[:modal] && screen.respond_to?("modal=")
# Hide bottom bar?
screen.hidesBottomBarWhenPushed = args[:hide_tab_bar] == true

View File

@@ -10,33 +10,33 @@ module ProMotion
screens.each do |s|
s = s.new if s.respond_to?(:new)
s.tabBarItem.tag = tag_index
s.parent_screen = self if self.is_a?(UIViewController) && s.respond_to?("parent_screen=")
s.tab_bar = tab_bar_controller if s.respond_to?("tab_bar=")
vc = s.respond_to?(:main_controller) ? s.main_controller : s
view_controllers << vc
tag_index += 1
s.on_load if s.respond_to?(:on_load)
end
tab_bar_controller.viewControllers = view_controllers
tab_bar_controller
end
# Open a UITabBarController with the specified screens as the
# root view controller of the current app.
# @param [Array] A comma-delimited list of screen classes or instances.
# @return [UITabBarController]
def open_tab_bar(*screens)
tab_bar = tab_bar_controller(*screens)
a = self.respond_to?(:load_root_screen) ? self : UIApplication.sharedApplication.delegate
a.load_root_screen(tab_bar)
tab_bar
end
@@ -66,12 +66,12 @@ module ProMotion
title = tab[:title] if tab[:title]
tab[:tag] ||= @current_tag ||= 0
@current_tag = tab[:tag] + 1
tab_bar_item = create_tab_bar_icon(tab[:system_icon], tab[:tag]) if tab[:system_icon]
tab_bar_item = create_tab_bar_icon_custom(title, tab[:icon], tab[:tag]) if tab[:icon]
tab_bar_item.badgeValue = tab[:badge_number].to_s unless tab[:badge_number].nil? || tab[:badge_number] <= 0
return tab_bar_item
end

View File

@@ -3,20 +3,20 @@ module ProMotion
def split_screen_controller(master, detail)
master_main = master.navigationController ? master.navigationController : master
detail_main = detail.navigationController ? detail.navigationController : detail
split = SplitViewController.alloc.init
split.viewControllers = [ master_main, detail_main ]
split.delegate = self
split
end
def create_split_screen(master, detail, args={})
master = master.new if master.respond_to?(:new)
detail = detail.new if detail.respond_to?(:new)
split = split_screen_controller(master, detail)
[master, detail].each do |s|
s.split_screen = split if s.respond_to?("split_screen=")
s.on_load if s.respond_to?(:on_load)
@@ -24,15 +24,15 @@ module ProMotion
split
end
def open_split_screen(master, detail, args={})
split = create_split_screen(master, detail, args)
open split, args
split
end
# UISplitViewControllerDelegate methods
def splitViewController(svc, willHideViewController: vc, withBarButtonItem: button, forPopoverController: pc)
button.title = vc.title
svc.detail_screen.navigationItem.leftBarButtonItem = button;

View File

@@ -12,13 +12,13 @@ module ProMotion
unless self.is_a?(UIViewController)
raise StandardError.new("ERROR: Screens must extend UIViewController or a subclass of UIViewController.")
end
args.each do |k, v|
self.send("#{k}=", v) if self.respond_to?("#{k}=")
end
self.add_nav_bar if args[:nav_bar]
self.table_setup if self.respond_to?(:table_setup)
self.table_setup if self.respond_to?(:table_setup)
self.on_init if self.respond_to?(:on_init)
self
end
@@ -60,23 +60,26 @@ module ProMotion
end
def set_nav_bar_right_button(title, args={})
args[:style] ||= UIBarButtonItemStyleBordered
args[:target] ||= self
args[:action] ||= nil
right_button = UIBarButtonItem.alloc.initWithTitle(title, style: args[:style], target: args[:target], action: args[:action])
self.navigationItem.rightBarButtonItem = right_button
right_button
args[:title] = title
set_nav_bar_button :right, args
end
def set_nav_bar_left_button(title, args={})
args[:title] = title
set_nav_bar_button :left, args
end
def set_nav_bar_button(side, args={})
args[:style] ||= UIBarButtonItemStyleBordered
args[:target] ||= self
args[:action] ||= nil
left_button = UIBarButtonItem.alloc.initWithTitle(title, style: args[:style], target: args[:target], action: args[:action])
self.navigationItem.leftBarButtonItem = left_button
left_button
button = UIBarButtonItem.alloc.initWithTitle(args[:title], style: args[:style], target: args[:target], action: args[:action])
self.navigationItem.leftBarButtonItem = button if side == :left
self.navigationItem.rightBarButtonItem = button if side == :right
button
end
# [DEPRECATED]
@@ -132,8 +135,7 @@ module ProMotion
end
def main_controller
return self.navigation_controller if self.navigation_controller
self
self.navigation_controller || self
end
def view_controller
@@ -186,7 +188,7 @@ module ProMotion
end
orientations
end
def supported_device_families
NSBundle.mainBundle.infoDictionary["UIDeviceFamily"].map do |m|
case m
@@ -197,7 +199,7 @@ module ProMotion
end
end
end
def supported_device_family?(family)
supported_device_families.include?(family)
end

View File

@@ -2,13 +2,15 @@ module ProMotion
module TableScreenModule
include MotionTable::PlainTable
include MotionTable::SearchableTable
include MotionTable::RefreshableTable
include ProMotion::ScreenModule
def update_table_data
self.update_table_view_data(table_data)
end
module TableClassMethods
# Searchable
def searchable(params={})
@searchable_params = params
@searchable = true
@@ -21,6 +23,21 @@ module ProMotion
def get_searchable
@searchable ||= false
end
# Refreshable
def refreshable(params = {})
@refreshable_params = params
@refreshable = true
end
def get_refreshable
@refreshable ||= false
end
def get_refreshable_params
@refreshable_params ||= nil
end
end
def self.included(base)
base.extend(ClassMethods)

View File

View File

@@ -0,0 +1,48 @@
class TableScreen < ProMotion::SectionedTableScreen
def on_load
@tap_counter ||= 0
end
def table_data
[{
title: "Your Account",
cells: [
{ title: "Increment", action: :increment_counter_by, arguments: { number: 3 } },
{ title: "Add New Row", action: :add_tableview_row, accessibilityLabel: "Add New Row" },
{ title: "Just another blank row" }
]
}, {
title: "App Stuff",
cells: [
{ title: "Increment One", action: :increment_counter },
{ title: "Feedback", remote_image: { url: "http://placekitten.com/100/100", placeholder: "some-local-image", size: 50, radius: 15 } }
]
}]
end
def edit_profile(args={})
args[:id]
end
def add_tableview_row
@data[0][:cells] << {
title: "Dynamically Added"
}
update_table_data
end
def increment_counter
@tap_counter += 1
end
def increment_counter_by(args)
@tap_counter = @tap_counter + args[:number]
end
def tap_counter
@tap_counter
end
end

View File

@@ -0,0 +1,11 @@
class TableScreenRefreshable < TableScreen
attr_accessor :on_refresh_called
refreshable
def on_refresh
self.on_refresh_called = true
end_refreshing
end
end

View File

@@ -0,0 +1,5 @@
class TableScreenSearchable < TableScreen
searchable
end

View File

@@ -6,23 +6,23 @@ describe "ios version" do
end
it "#ios_version_is?" do
@dummy.ios_version_is?(@dummy.ios_version).should == true
@dummy.ios_version_is?(@dummy.ios_version).should.be.true
end
it "#ios_version_greater?" do
@dummy.ios_version_greater?('1.0').should == true
@dummy.ios_version_greater?('1.0').should.be.true
end
it "#ios_version_greater_eq?" do
@dummy.ios_version_greater_eq?(@dummy.ios_version).should == true
@dummy.ios_version_greater_eq?(@dummy.ios_version).should.be.true
end
it "#ios_version_less?" do
@dummy.ios_version_less?('9.0').should == true
@dummy.ios_version_less?('9.0').should.be.true
end
it "#ios_version_less_eq?" do
@dummy.ios_version_less_eq?(@dummy.ios_version).should == true
@dummy.ios_version_less_eq?(@dummy.ios_version).should.be.true
end
end

View File

@@ -3,7 +3,7 @@ describe "pro motion module" do
it "should have 'PM' module" do
should.not.raise(NameError) { PM }
end
it "should not allow screen inclusion into just any class" do
dummy = DummyClass.new
dummy.extend ProMotion::ScreenModule

View File

@@ -24,8 +24,37 @@ describe "screen helpers" do
@screen.view.subviews.count.should == 0
end
it "should add a subview to another element" do
sub_subview = UIView.alloc.initWithFrame CGRectZero
@screen.add_to @subview, sub_subview
@subview.subviews.include?(sub_subview).should == true
end
it "should add a subview to another element with attributes" do
sub_subview = UIView.alloc.initWithFrame CGRectZero
@screen.add_to @subview, sub_subview, { backgroundColor: UIColor.redColor }
@subview.subviews.last.backgroundColor.should == UIColor.redColor
end
end
describe "nav bar buttons" do
before do
@screen = HomeScreen.new(nav_bar: true)
end
it "should add a left nav bar button" do
@screen.set_nav_bar_left_button "Save", action: :save_something, type: UIBarButtonItemStyleDone
@screen.navigationItem.leftBarButtonItem.class.should == UIBarButtonItem
end
it "should add a right nav bar button" do
@screen.set_nav_bar_right_button "Cancel", action: :return_to_some_other_screen, type: UIBarButtonItemStylePlain
@screen.navigationItem.rightBarButtonItem.class.should == UIBarButtonItem
end
end
describe "screen navigation" do

View File

@@ -2,48 +2,48 @@ describe "split screen in tab bar functionality" do
before do
@app = TestDelegate.new
@master_screen = HomeScreen.new nav_bar: true
@detail_screen = BasicScreen.new nav_bar: true
@split_screen = @app.create_split_screen @master_screen, @detail_screen
@tab = @app.open_tab_bar @split_screen, HomeScreen, BasicScreen
end
it "should create a UISplitViewController" do
@split_screen.is_a?(UISplitViewController).should == true
end
it "should have two viewControllers" do
@split_screen.viewControllers.length.should == 2
end
it "should set the root view to the tab bar" do
@app.window.rootViewController.should == @tab
end
it "should return screens for the master_screen and detail_screen methods" do
it "should return screens for the master_screen and detail_screen methods" do
@split_screen.master_screen.is_a?(PM::Screen).should == true
@split_screen.detail_screen.is_a?(PM::Screen).should == true
end
it "should return navigationControllers" do
it "should return navigationControllers" do
@split_screen.viewControllers.first.is_a?(UINavigationController).should == true
@split_screen.viewControllers.last.is_a?(UINavigationController).should == true
end
it "should set the first viewController to HomeScreen's main controller" do
@split_screen.master_screen.should == @master_screen
@split_screen.viewControllers.first.should == @master_screen.main_controller
end
it "should set the second viewController to BasicScreen's main controller" do
@split_screen.detail_screen.should == @detail_screen
@split_screen.viewControllers.last.should == @detail_screen.main_controller
end
it "should set the tab bar first viewController to the split screen" do
it "should set the tab bar first viewController to the split screen" do
@tab.viewControllers.first.should == @split_screen
end
end

View File

@@ -2,45 +2,45 @@ describe "split screen `open` functionality" do
before do
@app = TestDelegate.new
@master_screen = HomeScreen.new nav_bar: true
@detail_screen_1 = BasicScreen.new # no nav_bar on this one
@detail_screen_2 = BasicScreen.new(nav_bar: true)
@split_screen = @app.open_split_screen @master_screen, @detail_screen_1
end
it "should open a new screen in the detail view" do
@master_screen.open @detail_screen_2, in_detail: true
@split_screen.detail_screen.should == @detail_screen_2
@split_screen.viewControllers.first.should == @master_screen.main_controller
@split_screen.viewControllers.last.should == @detail_screen_2.main_controller
end
it "should open a new screen in the master view" do
@detail_screen_1.open @detail_screen_2, in_master: true
@split_screen.master_screen.should == @detail_screen_2
@split_screen.viewControllers.first.should == @detail_screen_2.main_controller
@split_screen.viewControllers.last.should == @detail_screen_1.main_controller
end
it "should open a new screen in the master view's navigation controller" do
@master_screen.open @detail_screen_2
@split_screen.detail_screen.should == @detail_screen_1 # no change
@master_screen.navigationController.topViewController.should == @detail_screen_2
end
it "should open a new modal screen in the detail view" do
@detail_screen_1.open @detail_screen_2, modal: true
@split_screen.detail_screen.should == @detail_screen_1
@detail_screen_1.presentedViewController.should == @detail_screen_2.main_controller
end
it "should not interfere with normal non-split screen navigation" do
it "should not interfere with normal non-split screen navigation" do
home = HomeScreen.new(nav_bar: true)
child = BasicScreen.new
home.open child, in_detail: true, in_master: true
home.navigation_controller.topViewController.should == child
end
end

View File

@@ -2,34 +2,34 @@ describe "split screen functionality" do
before do
@app = TestDelegate.new
@master_screen = HomeScreen.new nav_bar: true
@detail_screen = BasicScreen.new # no nav_bar on this one
@split_screen = @app.open_split_screen @master_screen, @detail_screen
end
it "should have created a split screen" do
@split_screen.should != nil
@split_screen.is_a?(UISplitViewController).should == true
end
it "should have two viewControllers" do
@split_screen.viewControllers.length.should == 2
end
it "should set the root view to the UISplitScreenViewController" do
@app.window.rootViewController.should == @split_screen
end
it "should set the first viewController to HomeScreen" do
@split_screen.master_screen.should == @master_screen
@split_screen.viewControllers.first.should == @master_screen.main_controller
end
it "should set the second viewController to BasicScreen" do
@split_screen.detail_screen.should == @detail_screen
@split_screen.viewControllers.last.should == @detail_screen.main_controller
end
end

72
spec/table_screen_spec.rb Normal file
View File

@@ -0,0 +1,72 @@
describe "table screens" do
describe "basic functionality" do
before do
UIView.setAnimationsEnabled false # avoid animation issues
@screen = TableScreen.new
@screen.on_load
end
it "should display 2 sections" do
@screen.tableView.numberOfSections.should == 2
end
it "should have proper cell numbers" do
@screen.tableView.numberOfRowsInSection(0).should == 3
@screen.tableView.numberOfRowsInSection(1).should == 2
end
it "should have a placeholder image in the last cell" do
index_path = NSIndexPath.indexPathForRow(1, inSection: 1)
@screen.tableView(@screen.tableView, cellForRowAtIndexPath: index_path).imageView.class.should == UIImageView
end
end
describe "search functionality" do
before do
@screen = TableScreenSearchable.new
@screen.on_load
end
it "should be searchable" do
@screen.class.get_searchable.should == true
end
it "should create a search header" do
@screen.table_view.tableHeaderView.class.should == UISearchBar
end
end
describe "refresh functionality" do
# Note this test only works if on iOS 6+ or when using CKRefreshControl.
before do
@screen = TableScreenRefreshable.new
@screen.on_load
end
it "should be refreshable" do
@screen.class.get_refreshable.should == true
end
it "should create a refresh object" do
@screen.instance_variable_get("@refresh_control").class.should == UIRefreshControl
end
it "should respond to start_refreshing and end_refreshing" do
@screen.respond_to?(:start_refreshing).should == true
@screen.respond_to?(:end_refreshing).should == true
end
# Animations cause the refresh object to fail when tested. Test manually.
end
end

View File

@@ -3,7 +3,7 @@ describe "view helpers" do
def equal_rect(rect)
->(obj) { CGRectEqualToRect obj, rect }
end
before do
@dummy = UIView.alloc.initWithFrame CGRectZero
@dummy.extend ProMotion::ViewHelper
@@ -14,7 +14,7 @@ describe "view helpers" do
end
it "#frame_from_array should return a valid CGRect" do
@dummy.frame_from_array([0,0,320,480]).should equal_rect(CGRectMake(0,0,320,480))
@dummy.frame_from_array([0,0,320,480]).should equal_rect(CGRectMake(0,0,320,480))
end
it "should allow you to set attributes" do
@@ -24,16 +24,16 @@ describe "view helpers" do
it "should allow you to set nested attributes" do
layered_view = UIView.alloc.initWithFrame(CGRectMake(0, 0, 10, 10))
@dummy.set_attributes layered_view, {
layer: {
backgroundColor: UIColor.redColor.CGColor
}
}
layered_view.layer.backgroundColor.should == UIColor.redColor.CGColor
end
it "should allow you to set multiple nested attributes" do
mask_layer = CAShapeLayer.layer
layered_view = UIView.alloc.initWithFrame(CGRectMake(0, 0, 10, 10))
@@ -45,10 +45,25 @@ describe "view helpers" do
}
}
}
layered_view.layer.mask.backgroundColor.should == UIColor.redColor.CGColor
end
it "should allow you to set snake_case attributes" do
layered_view = UIView.alloc.initWithFrame(CGRectMake(0, 0, 10, 10))
@dummy.set_attributes layered_view, {
layer: {
background_color: UIColor.redColor.CGColor
},
content_mode: UIViewContentModeBottom
}
layered_view.contentMode.should == UIViewContentModeBottom
layered_view.layer.backgroundColor.should == UIColor.redColor.CGColor
end
describe "content height" do
before do
@@ -67,4 +82,74 @@ describe "view helpers" do
end
describe "set_easy_attributes" do
before do
@dummy = UIView.alloc.initWithFrame CGRectZero
@dummy.extend ProMotion::ViewHelper
@parent = UIView.alloc.initWithFrame(CGRectMake(0, 0, 320, 480))
@child = UIView.alloc.initWithFrame(CGRectZero)
end
it "Should set the autoresizingMask for all" do
@dummy.set_easy_attributes @parent, @child, {
resize: [:left, :right, :top, :bottom, :width, :height]
}
mask = UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin |
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight
@child.autoresizingMask.should == mask
end
it "Should set the autoresizingMask for half" do
@dummy.set_easy_attributes @parent, @child, {
resize: [:left, :right, :top]
}
mask = UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin
@child.autoresizingMask.should == mask
end
it "Should set the autoresizingMask for the second half" do
@dummy.set_easy_attributes @parent, @child, {
resize: [:bottom, :width, :height]
}
mask = UIViewAutoresizingFlexibleBottomMargin |
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight
@child.autoresizingMask.should == mask
end
it "Should not set the autoresizingMask" do
@dummy.set_easy_attributes @parent, @child, {}
mask = UIViewAutoresizingNone
@child.autoresizingMask.should == mask
end
it "Should create a frame" do
@dummy.set_easy_attributes @parent, @child, {
left: 10,
top: 20,
width: 100,
height: 50
}
@child.frame.should == CGRectMake(10, 20, 100, 50)
end
end
end