Merge pull request #101 from clearsightstudio/feature/table_cleanup

TableScreen Refactor
This commit is contained in:
Jamon Holmgren
2013-05-27 20:49:14 -07:00
24 changed files with 736 additions and 365 deletions

View File

@@ -1,7 +1,10 @@
module ProMotion
class TableViewCell < UITableViewCell
include TableViewCellModule
attr_accessor :image_size
# TODO: Is this necessary?
def layoutSubviews
super
@@ -12,5 +15,6 @@ module ProMotion
self.imageView.frame = CGRectInset(f, size_inset_x, size_inset_y)
end
end
end
end

View File

@@ -11,10 +11,10 @@ module ProMotion
if v.is_a?(Hash) && element.respond_to?(k)
sub_element = element.send(k)
set_attributes(sub_element, v) if sub_element
elsif v.is_a?(Array) && element.respond_to?("#{k}")
element.send("#{k}", *v)
elsif element.respond_to?("#{k}=")
element.send("#{k}=", v)
elsif v.is_a?(Array) && element.respond_to?("#{k}") && element.method("#{k}").arity == v.length
element.send("#{k}", *v)
else
# Doesn't respond. Check if snake case.
if k.to_s.include?("_")

View File

@@ -1,279 +0,0 @@
module ProMotion::MotionTable
module SectionedTable
include ProMotion::ViewHelper
def table_setup
PM.logger.error "Missing #table_data method in TableScreen #{self.class.to_s}." unless self.respond_to?(:table_data)
self.view = self.create_table_view_from_data(self.table_data)
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
PM.logger.warn "To use the refresh control on < iOS 6, you need to include the CocoaPod 'CKRefreshControl'."
end
end
end
# @param [Array] Array of table data
# @returns [UITableView] delegated to self
def create_table_view_from_data(data)
set_table_view_data data
return table_view
end
alias :createTableViewFromData :create_table_view_from_data
def update_table_view_data(data)
set_table_view_data data
self.table_view.reloadData
end
alias :updateTableViewData :update_table_view_data
def set_table_view_data(data)
@mt_table_view_groups = data
end
alias :setTableViewData :set_table_view_data
def section_at_index(index)
if @mt_filtered
@mt_filtered_data.at(index)
else
@mt_table_view_groups.at(index)
end
end
def cell_at_section_and_index(section, index)
if section_at_index(section) && section_at_index(section)[:cells]
return section_at_index(section)[:cells].at(index)
end
end
alias :cellAtSectionAndIndex :cell_at_section_and_index
def trigger_action(action, arguments)
if self.respond_to?(action)
expected_arguments = self.method(action).arity
if expected_arguments == 0
self.send(action)
elsif expected_arguments == 1 || expected_arguments == -1
self.send(action, arguments)
else
PM.logger.warn "#{action} expects #{expected_arguments} arguments. Maximum number of required arguments for an action is 1."
end
else
PM.logger.info "Action not implemented: #{action.to_s}"
end
end
def accessory_toggled_switch(switch)
table_cell = switch.superview
index_path = table_cell.superview.indexPathForCell(table_cell)
data_cell = cell_at_section_and_index(index_path.section, index_path.row)
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
########## Cocoa touch methods, leave as-is #################
def numberOfSectionsInTableView(table_view)
if @mt_filtered
return @mt_filtered_data.length if @mt_filtered_data
else
return @mt_table_view_groups.length if @mt_table_view_groups
end
0
end
# Number of cells
def tableView(table_view, numberOfRowsInSection:section)
return section_at_index(section)[:cells].length if section_at_index(section) && section_at_index(section)[:cells]
0
end
def tableView(table_view, titleForHeaderInSection:section)
return section_at_index(section)[:title] if section_at_index(section) && section_at_index(section)[:title]
end
# 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
end
end
def remap_data_cell(data_cell)
# Re-maps legacy data cell calls
mappings = {
cell_style: :cellStyle,
cell_identifier: :cellIdentifier,
cell_class: :cellClass,
masks_to_bounds: :masksToBounds,
background_color: :backgroundColor,
selection_style: :selectionStyle,
cell_class_attributes: :cellClassAttributes,
accessory_view: :accessoryView,
accessory_type: :accessoryType,
accessory_checked: :accessoryDefault,
remote_image: :remoteImage,
subviews: :subViews
}
mappings.each_pair do |n, old|
if data_cell[old]
warn "[DEPRECATION] `:#{old}` is deprecated in TableScreens. Use `:#{n}`"
data_cell[n] = data_cell[old]
end
end
if data_cell[:styles] && data_cell[:styles][:textLabel]
warn "[DEPRECATION] `:textLabel` is deprecated in TableScreens. Use `:label`"
data_cell[:styles][:label] = data_cell[:styles][:textLabel]
end
data_cell
end
def tableView(table_view, cellForRowAtIndexPath:index_path)
data_cell = cell_at_section_and_index(index_path.section, index_path.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]
data_cell[:cell_class] ||= ProMotion::TableViewCell
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)
data_cell[:layer] ||= {}
data_cell[:layer][:masksToBounds] = data_cell[:masks_to_bounds] # TODO: Deprecate and then remove this.
table_cell.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin
set_attributes table_cell, data_cell
end
### Catch any custom class labels ###
if data_cell[:cell_class]
data_cell.select{|k| k =~ /_label$/}.each_pair do |k, v|
cell = table_cell.send(k)
cell.setText v
cell.autoresizingMask = UIViewAutoresizingFlexibleWidth
end
end
if data_cell[:cell_class_attributes]
set_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
end
if data_cell[:accessory_type]
table_cell.accessoryType = data_cell[:accessory_type]
end
if data_cell[:accessory] && data_cell[:accessory] == :switch
switch_view = UISwitch.alloc.initWithFrame(CGRectZero)
switch_view.addTarget(self, action: "accessory_toggled_switch:", forControlEvents:UIControlEventValueChanged)
switch_view.on = true if data_cell[:accessory_checked]
table_cell.accessoryView = switch_view
end
if data_cell[:subtitle] and table_cell.detailTextLabel
table_cell.detailTextLabel.text = data_cell[:subtitle]
table_cell.detailTextLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth
end
table_cell.selectionStyle = UITableViewCellSelectionStyleNone if data_cell[:no_select]
if data_cell[:remote_image]
if table_cell.imageView.respond_to?("setImageWithURL:placeholderImage:")
url = data_cell[:remote_image][:url]
url = NSURL.URLWithString(url) unless url.is_a?(NSURL)
placeholder = data_cell[:remote_image][:placeholder]
placeholder = UIImage.imageNamed(placeholder) if placeholder.is_a?(String)
table_cell.image_size = data_cell[:remote_image][:size] if data_cell[:remote_image][:size] && table_cell.respond_to?("image_size=")
table_cell.imageView.setImageWithURL(url, placeholderImage: placeholder)
table_cell.imageView.layer.masksToBounds = true
table_cell.imageView.layer.cornerRadius = data_cell[:remote_image][:radius] if data_cell[:remote_image].has_key?(:radius)
else
PM.logger.error "ProMotion Warning: to use remote_image with TableScreen you need to include the CocoaPod 'SDWebImage'."
end
elsif data_cell[:image]
table_cell.imageView.layer.masksToBounds = true
table_cell.imageView.image = data_cell[:image][:image]
table_cell.imageView.layer.cornerRadius = data_cell[:image][:radius] if data_cell[:image][:radius]
end
if data_cell[:subviews]
tag_number = 0
data_cell[:subviews].each do |view|
# Remove an existing view at that tag number
tag_number += 1
existing_view = table_cell.viewWithTag(tag_number)
existing_view.removeFromSuperview if existing_view
# Add the subview if it exists
if view
view.tag = tag_number
table_cell.addSubview view
end
end
end
if data_cell[:details]
table_cell.addSubview data_cell[:details][:image]
end
if data_cell[:styles] && data_cell[:styles][:label] && data_cell[:styles][:label][:frame]
ui_label = false
table_cell.contentView.subviews.each do |view|
if view.is_a? UILabel
ui_label = true
view.text = data_cell[:styles][:label][:text]
end
end
unless ui_label == true
label ||= UILabel.alloc.initWithFrame(CGRectZero)
set_attributes label, data_cell[:styles][:label]
table_cell.contentView.addSubview label
end
# hackery
table_cell.textLabel.textColor = UIColor.clearColor
else
cell_title = data_cell[:title]
cell_title ||= ""
table_cell.textLabel.text = cell_title
end
return table_cell
end
def tableView(tableView, heightForRowAtIndexPath:index_path)
cell = cell_at_section_and_index(index_path.section, index_path.row)
if cell[:height]
cell[:height].to_f
else
tableView.rowHeight
end
end
def tableView(table_view, didSelectRowAtIndexPath:index_path)
cell = cell_at_section_and_index(index_path.section, index_path.row)
table_view.deselectRowAtIndexPath(index_path, animated: true)
cell[:arguments] ||= {}
cell[:arguments][:cell] = cell if cell[:arguments].is_a?(Hash)
trigger_action(cell[:action], cell[:arguments]) if cell[:action]
end
end
end

View File

@@ -1,14 +0,0 @@
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;
@table_view.delegate = self;
return @table_view
end
alias :tableView :table_view
end
end

View File

@@ -1,15 +0,0 @@
module ProMotion::MotionTable
module PlainTable
include SectionedTable
include SearchableTable
include RefreshableTable
def table_view
@table_view ||= UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStylePlain)
@table_view.dataSource = self;
@table_view.delegate = self;
return @table_view
end
alias :tableView :table_view
end
end

View File

@@ -13,7 +13,6 @@ module ProMotion
raise StandardError.new("ERROR: Screens must extend UIViewController or a subclass of UIViewController.")
end
self.title = self.class.send(:get_title)
args.each do |k, v|

View File

@@ -1,9 +1,9 @@
module ProMotion
module TableScreenModule
include MotionTable::PlainTable
include MotionTable::SearchableTable
include MotionTable::RefreshableTable
include ProMotion::ScreenModule
include PlainTable
include SearchableTable
include RefreshableTable
include ScreenModule
def update_table_data
self.update_table_view_data(table_data)

View File

@@ -1,4 +1,4 @@
module ProMotion::MotionTable
module ProMotion
module RefreshableTable
def make_refreshable(params={})
pull_message = params[:pull_message] || "Pull to refresh"
@@ -39,4 +39,4 @@ module ProMotion::MotionTable
@refresh_control.endRefreshing
end
end
end
end

View File

@@ -1,18 +1,10 @@
module ProMotion::MotionTable
module ProMotion
module SearchableTable
def make_searchable(params={})
params[:content_controller] ||= params[:contentController]
params[:data_source] ||= params[:searchResultsDataSource]
params[:search_results_delegate] ||= params[:searchResultsDelegate]
params = set_searchable_param_defaults(params)
params[:frame] ||= CGRectMake(0, 0, 320, 44) # TODO: Don't hardcode this...
params[:content_controller] ||= self
params[:delegate] ||= self
params[:data_source] ||= self
params[:search_results_delegate] ||= self
search_bar = UISearchBar.alloc.initWithFrame(params[:frame])
search_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
search_bar = create_search_bar(params)
if params[:search_bar] && params[:search_bar][:placeholder]
search_bar.placeholder = params[:search_bar][:placeholder]
@@ -26,41 +18,44 @@ module ProMotion::MotionTable
self.table_view.tableHeaderView = search_bar
end
alias :makeSearchable :make_searchable
def set_searchable_param_defaults(params)
params[:content_controller] ||= params[:contentController]
params[:data_source] ||= params[:searchResultsDataSource]
params[:search_results_delegate] ||= params[:searchResultsDelegate]
params[:frame] ||= CGRectMake(0, 0, 320, 44) # TODO: Don't hardcode this...
params[:content_controller] ||= self
params[:delegate] ||= self
params[:data_source] ||= self
params[:search_results_delegate] ||= self
params
end
def create_search_bar(params)
search_bar = UISearchBar.alloc.initWithFrame(params[:frame])
search_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
search_bar
end
######### iOS methods, headless camel case #######
def searchDisplayController(controller, shouldReloadTableForSearchString:search_string)
@mt_filtered_data = nil
@mt_filtered_data = []
@mt_table_view_groups.each do |section|
new_section = {}
new_section[:cells] = []
section[:cells].each do |cell|
if cell[:title].to_s.downcase.strip.include?(search_string.downcase.strip)
new_section[:cells] << cell
end
end
if new_section[:cells] && new_section[:cells].length > 0
new_section[:title] = section[:title]
@mt_filtered_data << new_section
end
end
@promotion_table_data.search(search_string)
true
end
def searchDisplayControllerWillEndSearch(controller)
@mt_filtered = false
@mt_filtered_data = nil
@promotion_table_data.stop_searching
@promotion_table_data_data = nil
self.table_view.setScrollEnabled true
end
def searchDisplayControllerWillBeginSearch(controller)
@mt_filtered = true
@mt_filtered_data = []
@promotion_table_data = true
@motion_table_filtered_data = []
self.table_view.setScrollEnabled false
end
end
end

View File

@@ -0,0 +1,5 @@
module ProMotion
module SectionedTable
include Table
end
end

View File

@@ -0,0 +1,129 @@
module ProMotion
module Table
include ProMotion::ViewHelper
def table_setup
check_table_data
set_up_table_view
set_up_searchable
set_up_refreshable
end
def check_table_data
PM.logger.error "Missing #table_data method in TableScreen #{self.class.to_s}." unless self.respond_to?(:table_data)
end
def set_up_table_view
self.view = self.create_table_view_from_data(self.table_data)
end
def set_up_searchable
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
end
def set_up_refreshable
if self.class.respond_to?(:get_refreshable) && self.class.get_refreshable
if defined?(UIRefreshControl)
self.make_refreshable(self.class.get_refreshable_params)
else
PM.logger.warn "To use the refresh control on < iOS 6, you need to include the CocoaPod 'CKRefreshControl'."
end
end
end
def create_table_view_from_data(data)
@promotion_table_data = TableData.new(data, table_view)
table_view
end
def update_table_view_data(data)
@promotion_table_data.data = data
table_view.reloadData
end
# Methods to retrieve data
def section_at_index(index)
@promotion_table_data.section(index)
end
def cell_at_section_and_index(section, index)
@promotion_table_data.cell(section: section, index: index)
end
def trigger_action(action, arguments)
if self.respond_to?(action)
expected_arguments = self.method(action).arity
if expected_arguments == 0
self.send(action)
elsif expected_arguments == 1 || expected_arguments == -1
self.send(action, arguments)
else
PM.logger.warn "#{action} expects #{expected_arguments} arguments. Maximum number of required arguments for an action is 1."
end
else
PM.logger.info "Action not implemented: #{action.to_s}"
end
end
def accessory_toggled_switch(switch)
table_cell = switch.superview
index_path = table_cell.superview.indexPathForCell(table_cell)
data_cell = cell_at_section_and_index(index_path.section, index_path.row)
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
########## Cocoa touch methods #################
def numberOfSectionsInTableView(table_view)
return Array(@promotion_table_data.data).length
end
# Number of cells
def tableView(table_view, numberOfRowsInSection:section)
return @promotion_table_data.section_length(section)
0
end
def tableView(table_view, titleForHeaderInSection:section)
return section_at_index(section)[:title] if section_at_index(section) && section_at_index(section)[:title]
end
# Set table_data_index if you want the right hand index column (jumplist)
def sectionIndexTitlesForTableView(table_view)
self.table_data_index if self.respond_to?(:table_data_index)
end
def tableView(table_view, cellForRowAtIndexPath:index_path)
@promotion_table_data.table_view_cell(index_path: index_path)
end
def tableView(table_view, heightForRowAtIndexPath:index_path)
(@promotion_table_data.cell(index_path: index_path)[:height] || table_view.rowHeight).to_f
end
def tableView(table_view, didSelectRowAtIndexPath:index_path)
cell = @promotion_table_data.cell(index_path: index_path)
table_view.deselectRowAtIndexPath(index_path, animated: true)
cell[:arguments] ||= {}
cell[:arguments][:cell] = cell if cell[:arguments].is_a?(Hash) # TODO: Should we really do this?
trigger_action(cell[:action], cell[:arguments]) if cell[:action]
end
# Old aliases, deprecated, will be removed
alias :createTableViewFromData :create_table_view_from_data
alias :updateTableViewData :update_table_view_data
alias :cellAtSectionAndIndex :cell_at_section_and_index
end
end

View File

@@ -0,0 +1,16 @@
module ProMotion
module GroupedTable
include Table
include RefreshableTable
def table_view
@table_view ||= begin
t = UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStyleGrouped)
t.dataSource = self
t.delegate = self
t
end
end
alias :tableView :table_view
end
end

View File

@@ -0,0 +1,17 @@
module ProMotion
module PlainTable
include Table
include SearchableTable
include RefreshableTable
def table_view
@table_view ||= begin
t = UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStylePlain)
t.dataSource = self
t.delegate = self
t
end
end
alias :tableView :table_view
end
end

View File

@@ -0,0 +1,139 @@
module ProMotion
class TableData
attr_accessor :data, :filtered_data, :filtered, :table_view
def initialize(data, table_view)
self.data = data
self.table_view = table_view
end
def section(index)
s = sections.at(index)
s || { title: nil, cells: [] }
end
def sections
self.filtered ? self.filtered_data : self.data
end
def section_length(index)
section(index)[:cells].length
end
def cell(params={})
if params[:index_path]
params[:section] = params[:index_path].section
params[:index] = params[:index_path].row
end
table_section = self.section(params[:section])
return table_section[:cells].at(params[:index].to_i)
nil
end
def search(search_string)
self.filtered_data = []
self.filtered = true
search_string = search_string.downcase.strip
self.data.each do |section|
new_section = {}
new_section[:cells] = []
new_section[:cells] = section[:cells].map do |cell|
cell[:title].to_s.downcase.strip.include?(search_string) ? cell : nil
end.compact
if new_section[:cells] && new_section[:cells].length > 0
new_section[:title] = section[:title]
self.filtered_data << new_section
end
end
self.filtered_data
end
def stop_searching
self.filtered_data = []
self.filtered = false
end
def table_view_cell(params={})
if params[:index_path]
params[:section] = params[:index_path].section
params[:index] = params[:index_path].row
end
data_cell = self.cell(section: params[:section], index: params[:index])
return UITableViewCell.alloc.init unless data_cell # No data?
data_cell = self.remap_data_cell(data_cell) # TODO: Deprecated, remove in version 1.0
data_cell = self.set_data_cell_defaults(data_cell)
table_cell = self.create_table_cell(data_cell)
table_cell
end
def set_data_cell_defaults(data_cell)
data_cell[:cell_style] ||= UITableViewCellStyleDefault
data_cell[:cell_identifier] ||= "Cell"
data_cell[:cell_class] ||= ProMotion::TableViewCell
data_cell
end
def remap_data_cell(data_cell)
# Re-maps legacy data cell calls
mappings = {
cell_style: :cellStyle,
cell_identifier: :cellIdentifier,
cell_class: :cellClass,
masks_to_bounds: :masksToBounds,
background_color: :backgroundColor,
selection_style: :selectionStyle,
cell_class_attributes: :cellClassAttributes,
accessory_view: :accessoryView,
accessory_type: :accessoryType,
accessory_checked: :accessoryDefault,
remote_image: :remoteImage,
subviews: :subViews
}
if data_cell[:masks_to_bounds]
PM.logger.deprecated "masks_to_bounds: (value) is deprecated. Use layer: { masks_to_bounds: (value) } instead."
data_cell[:layer] ||= {}
data_cell[:layer][:masks_to_bounds] = data_cell[:masks_to_bounds] # TODO: Deprecate and then remove this.
end
mappings.each_pair do |n, old|
if data_cell[old]
warn "[DEPRECATION] `:#{old}` is deprecated in TableScreens. Use `:#{n}`"
data_cell[n] = data_cell[old]
end
end
if data_cell[:styles] && data_cell[:styles][:textLabel]
warn "[DEPRECATION] `:textLabel` is deprecated in TableScreens. Use `:label`"
data_cell[:styles][:label] = data_cell[:styles][:textLabel]
end
data_cell
end
def create_table_cell(data_cell)
table_cell = table_view.dequeueReusableCellWithIdentifier(data_cell[:cell_identifier])
unless table_cell
table_cell = data_cell[:cell_class].alloc.initWithStyle(data_cell[:cell_style], reuseIdentifier:data_cell[:cell_identifier])
table_cell.extend ProMotion::TableViewCellModule unless table_cell.is_a?(ProMotion::TableViewCellModule)
table_cell.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin
end
table_cell.setup(data_cell)
table_cell
end
end
end

View File

@@ -0,0 +1,150 @@
module ProMotion
module TableViewCellModule
include ViewHelper
attr_accessor :data_cell
def setup(data_cell)
self.data_cell = data_cell
# TODO: Some of these need to go away. Unnecessary overhead.
set_cell_attributes
set_background_color
set_class_attributes
set_accessory_view
set_subtitle
set_image
set_remote_image
set_subviews
set_details
set_styles
set_selection_style
self
end
def set_cell_attributes
set_attributes self, data_cell
self
end
def set_background_color
self.backgroundView = UIView.new.tap{|v| v.backgroundColor = data_cell[:background_color]} if data_cell[:background_color]
end
def set_class_attributes
if data_cell[:cell_class_attributes]
PM.logger.deprecated "`cell_class_attributes` is deprecated. Just add the attributes you want to set right into your cell hash."
set_attributes self, data_cell[:cell_class_attributes]
end
self
end
def set_accessory_view
if data_cell[:accessory_view]
self.accessoryView = data_cell[:accessory_view]
self.accessoryView.autoresizingMask = UIViewAutoresizingFlexibleWidth
end
if data_cell[:accessory] && data_cell[:accessory] == :switch
switch_view = UISwitch.alloc.initWithFrame(CGRectZero)
switch_view.addTarget(self, action: "accessory_toggled_switch:", forControlEvents:UIControlEventValueChanged)
switch_view.on = true if data_cell[:accessory_checked]
self.accessoryView = switch_view
end
self
end
def set_subtitle
if data_cell[:subtitle] && self.detailTextLabel
self.detailTextLabel.text = data_cell[:subtitle]
self.detailTextLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth
end
self
end
def set_remote_image
if data_cell[:remote_image]
if self.imageView.respond_to?("setImageWithURL:placeholderImage:")
url = data_cell[:remote_image][:url]
url = NSURL.URLWithString(url) unless url.is_a?(NSURL)
placeholder = data_cell[:remote_image][:placeholder]
placeholder = UIImage.imageNamed(placeholder) if placeholder.is_a?(String)
self.image_size = data_cell[:remote_image][:size] if data_cell[:remote_image][:size] && self.respond_to?("image_size=")
self.imageView.setImageWithURL(url, placeholderImage: placeholder)
self.imageView.layer.masksToBounds = true
self.imageView.layer.cornerRadius = data_cell[:remote_image][:radius] if data_cell[:remote_image].has_key?(:radius)
else
PM.logger.error "ProMotion Warning: to use remote_image with TableScreen you need to include the CocoaPod 'SDWebImage'."
end
end
self
end
def set_image
if data_cell[:image]
self.imageView.layer.masksToBounds = true
self.imageView.image = data_cell[:image][:image]
self.imageView.layer.cornerRadius = data_cell[:image][:radius] if data_cell[:image][:radius]
end
self
end
def set_subviews
tag_number = 0
Array(data_cell[:subviews]).each do |view|
# Remove an existing view at that tag number
tag_number += 1
existing_view = self.viewWithTag(tag_number)
existing_view.removeFromSuperview if existing_view
# Add the subview if it exists
if view
view.tag = tag_number
self.addSubview view
end
end
self
end
def set_details
if data_cell[:details]
self.addSubview data_cell[:details][:image]
end
self
end
def set_styles
if data_cell[:styles] && data_cell[:styles][:label] && data_cell[:styles][:label][:frame]
ui_label = false
self.contentView.subviews.each do |view|
if view.is_a? UILabel
ui_label = true
view.text = data_cell[:styles][:label][:text]
end
end
unless ui_label == true
label ||= UILabel.alloc.initWithFrame(CGRectZero)
set_attributes label, data_cell[:styles][:label]
self.contentView.addSubview label
end
# TODO: What is this and why is it necessary?
self.textLabel.textColor = UIColor.clearColor
else
cell_title = data_cell[:title]
cell_title ||= ""
self.textLabel.text = cell_title
end
self
end
def set_selection_style
self.selectionStyle = UITableViewCellSelectionStyleNone if data_cell[:no_select]
end
end
end

View File

@@ -3,13 +3,14 @@ module ProMotion
# Just make sure to implement the Obj-C methods in cocoatouch/TableViewController.rb.
class TableScreen < TableViewController
include ProMotion::TableScreenModule
# Includes PM::PlainTable already
end
class GroupedTableScreen < TableScreen
include ProMotion::MotionTable::GroupedTable
include ProMotion::GroupedTable
end
class SectionedTableScreen < TableScreen
include ProMotion::MotionTable::SectionedTable
include ProMotion::SectionedTable
end
end

View File

@@ -1,10 +1,10 @@
describe "ProMotion::TableScreen functionality" do
tests PM::TableScreen
describe "ProMotion::TestTableScreen functionality" do
tests PM::TestTableScreen
# Override controller to properly instantiate
def controller
rotate_device to: :portrait, button: :bottom
@controller ||= TableScreen.new(nav_bar: true)
@controller ||= TestTableScreen.new(nav_bar: true)
@controller.on_load
@controller.main_controller
end
@@ -14,7 +14,7 @@ describe "ProMotion::TableScreen functionality" do
end
it "should have a navigation bar" do
@controller.navigationController.is_a?(UINavigationController).should == true
@controller.navigationController.should.be.kind_of(UINavigationController)
end
it "should increment the tap counter on tap" do

View File

@@ -1,4 +1,8 @@
class TableScreen < ProMotion::SectionedTableScreen
class TestTableScreen < ProMotion::SectionedTableScreen
def promotion_table_data
@promotion_table_data
end
def on_load
@tap_counter ||= 0

View File

@@ -1,4 +1,4 @@
class TableScreenRefreshable < TableScreen
class TableScreenRefreshable < TestTableScreen
attr_accessor :on_refresh_called
refreshable

View File

@@ -1,4 +1,4 @@
class TableScreenSearchable < TableScreen
class TableScreenSearchable < TestTableScreen
searchable

View File

@@ -0,0 +1,108 @@
describe "PM::Table module" do
def cell_factory(args={})
{ title: "Basic", action: :basic_cell_tapped, arguments: { id: 1 } }.merge!(args)
end
def custom_cell
cell_factory({
title: "Crazy Full Featured Cell",
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: PM::TableViewCell,
masks_to_bounds: true,
background_color: UIColor.whiteColor,
selection_style: UITableViewCellSelectionStyleGray,
cell_class_attributes: {
# any Obj-C attributes to set on the cell
backgroundColor: UIColor.whiteColor
},
accessory: :switch, # currently only :switch is supported
accessory_view: @some_accessory_view,
accessory_type: UITableViewCellAccessoryCheckmark,
accessory_checked: true, # whether it's "checked" or not
image: { image: UIImage.imageNamed("something"), radius: 15 },
remote_image: { # remote image, requires SDWebImage CocoaPod
url: "http://placekitten.com/200/300", placeholder: "some-local-image",
size: 50, radius: 15
},
subviews: [ @some_view, @some_other_view ] # arbitrary views added to the cell
})
end
before do
@subject = TestTableScreen.new
@subject.mock! :table_data do
[{
title: "Table cell group 1", cells: [ ]
},{
title: "Table cell group 2", cells: [ cell_factory ]
},{
title: "Table cell group 3", cells: [ cell_factory(title: "3-1"), cell_factory(title: "3-2") ]
},{
title: "Table cell group 4", cells: [ custom_cell, cell_factory(title: "4-2"), cell_factory(title: "4-3"), cell_factory(title: "4-4") ]
}]
end
@subject.on_load
@ip = NSIndexPath.indexPathForRow(1, inSection: 2) # Cell 3-2
@custom_ip = NSIndexPath.indexPathForRow(0, inSection: 3) # Cell "Crazy Full Featured Cell"
@subject.update_table_data
end
it "should have the right number of sections" do
@subject.numberOfSectionsInTableView(@subject.table_view).should == 4
end
it "should set the section titles" do
@subject.tableView(@subject.table_view, titleForHeaderInSection:0).should == "Table cell group 1"
@subject.tableView(@subject.table_view, titleForHeaderInSection:1).should == "Table cell group 2"
@subject.tableView(@subject.table_view, titleForHeaderInSection:2).should == "Table cell group 3"
@subject.tableView(@subject.table_view, titleForHeaderInSection:3).should == "Table cell group 4"
end
it "should create the right number of cells" do
@subject.tableView(@subject.table_view, numberOfRowsInSection:0).should == 0
@subject.tableView(@subject.table_view, numberOfRowsInSection:1).should == 1
@subject.tableView(@subject.table_view, numberOfRowsInSection:2).should == 2
@subject.tableView(@subject.table_view, numberOfRowsInSection:3).should == 4
end
it "should create the jumplist" do
@subject.mock! :table_data_index, do
Array("A".."Z")
end
@subject.sectionIndexTitlesForTableView(@subject.table_view).should == Array("A".."Z")
end
it "should return the proper cell" do
cell = @subject.tableView(@subject.table_view, cellForRowAtIndexPath: @ip)
cell.should.be.kind_of(UITableViewCell)
cell.textLabel.text.should == "3-2"
end
it "should return the table's cell height if none is given" do
@subject.tableView(@subject.table_view, heightForRowAtIndexPath:@ip).should == 44.0 # Built-in default
end
it "should allow setting a custom cell height" do
@subject.tableView(@subject.table_view, heightForRowAtIndexPath:@custom_ip).should.be > 0.0
@subject.tableView(@subject.table_view, heightForRowAtIndexPath:@custom_ip).should == custom_cell[:height].to_f
end
it "should trigger the right action on select and pass in the right arguments" do
@subject.mock! :tapped_cell_1 do |args|
args[:data].should == [ "lots", "of", "data" ]
end
@subject.tableView(@subject.table_view, didSelectRowAtIndexPath:@custom_ip)
end
end

View File

@@ -3,19 +3,25 @@ describe "table screens" do
describe "basic functionality" do
before do
@screen = TableScreen.new
@screen = TestTableScreen.new
@screen.on_load
end
it "should display 2 sections" do
@screen.tableView.numberOfSections.should == 2
@screen.promotion_table_data.sections.should.be.kind_of(Array)
end
it "should have proper cell numbers" do
@screen.tableView.numberOfRowsInSection(0).should == 3
@screen.tableView.numberOfRowsInSection(1).should == 2
@screen.tableView(@screen.tableView, numberOfRowsInSection:0).should == 3
@screen.tableView(@screen.tableView, numberOfRowsInSection:1).should == 2
end
it "should return a UITableViewCell" do
index_path = NSIndexPath.indexPathForRow(1, inSection: 1)
@screen.tableView(@screen.tableView, cellForRowAtIndexPath: index_path).should.be.kind_of UITableViewCell
end
it "should have a placeholder image in the last cell" do
index_path = NSIndexPath.indexPathForRow(1, inSection: 1)

View File

@@ -0,0 +1,106 @@
describe "PM::TableViewCellModule" do
def custom_cell
{
title: "Crazy Full Featured Cell",
subtitle: "This is way too huge...",
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: PM::TableViewCell,
layer: { masks_to_bounds: true },
background_color: UIColor.redColor,
selection_style: UITableViewCellSelectionStyleGray,
accessory: :switch, # currently only :switch is supported
accessory_checked: true, # whether it's "checked" or not
image: { image: UIImage.imageNamed("list"), radius: 15 },
subviews: [ UIView.alloc.initWithFrame(CGRectZero), UILabel.alloc.initWithFrame(CGRectZero) ] # arbitrary views added to the cell
}
end
before do
@screen = TestTableScreen.new
button = UIButton.buttonWithType(UIButtonTypeRoundedRect).tap{|b| b.titleLabel.text = "ACC" }
@screen.mock! :table_data do
[
{ title: "", cells: [] },
{ title: "", cells: [
{title: "Test 1", accessory_type: UITableViewCellStateShowingEditControlMask },
custom_cell,
{ title: "Test2", accessory_view: button } ] }
]
end
@screen.on_load
custom_ip = NSIndexPath.indexPathForRow(1, inSection: 1) # Cell "Crazy Full Featured Cell"
@screen.update_table_data
@subject = @screen.tableView(@screen.table_view, cellForRowAtIndexPath: custom_ip)
end
it "should be a PM::TableViewCell" do
@subject.should.be.kind_of(PM::TableViewCell)
end
it "should have the right title" do
@subject.textLabel.text.should == "Crazy Full Featured Cell"
end
it "should have the right subtitle" do
@subject.detailTextLabel.text.should == "This is way too huge..."
end
it "should have the right re-use identifier" do
@subject.reuseIdentifier.should == "Cell"
end
it "should set the layer.masksToBounds" do
@subject.layer.masksToBounds.should == true
end
it "should set the background color" do
@subject.backgroundView.backgroundColor.should == UIColor.redColor
end
it "should set the selection color style" do
@subject.selectionStyle.should == UITableViewCellSelectionStyleGray
end
it "should set the accessory view to a switch" do
@subject.accessoryView.should.be.kind_of(UISwitch)
end
it "should set the accessory view to a button" do
ip = NSIndexPath.indexPathForRow(2, inSection: 1)
subject = @screen.tableView(@screen.table_view, cellForRowAtIndexPath: ip)
subject.accessoryView.should.be.kind_of(UIRoundedRectButton)
end
it "should set the accessory type to edit" do
ip = NSIndexPath.indexPathForRow(0, inSection: 1)
subject = @screen.tableView(@screen.table_view, cellForRowAtIndexPath: ip)
subject.accessoryView.should.be.nil
subject.accessoryType.should == UITableViewCellStateShowingEditControlMask
end
it "should set an image with a radius" do
@subject.imageView.should.be.kind_of(UIImageView)
@subject.imageView.image.should == UIImage.imageNamed("list")
@subject.imageView.layer.cornerRadius.should == 15.0
end
it "should create two extra subviews" do
@subject.subviews.length.should == 4
@subject.subviews[2].class.should == UIView
@subject.subviews[3].class.should == UILabel
end
end