app/lib/table_screen_patches/table_patch.rb
module ProMotion
module Table
include ProMotion::Styling
include ProMotion::Table::Searchable
include ProMotion::Table::Refreshable
include ProMotion::Table::Indexable
include ProMotion::Table::Longpressable
include ProMotion::Table::Utils
include ProMotion::TableBuilder
attr_reader :promotion_table_data
def table_view
self.view
end
def screen_setup
check_table_data
set_up_header_footer_views
set_up_searchable
set_up_refreshable
set_up_longpressable
set_up_row_height
end
def on_live_reload
update_table_data
end
def check_table_data
mp("Missing #table_data method in TableScreen #{self.class.to_s}.", force_color: :red) unless self.respond_to?(:table_data)
end
def promotion_table_data
@promotion_table_data ||= TableData.new(table_data, table_view, setup_search_method)
end
def set_up_header_footer_views
[:header, :footer].each do |hf_view|
if self.respond_to?("table_#{hf_view}_view".to_sym)
view = self.send("table_#{hf_view}_view")
if view.is_a? UIView
self.tableView.send(camelize("set_table_#{hf_view}_view:"), view)
else
mp "Table #{hf_view} view must be a UIView.", force_color: :yellow
end
end
end
end
def set_up_searchable
if self.class.respond_to?(:get_searchable) && self.class.get_searchable
self.make_searchable(self.class.get_searchable_params)
end
end
def setup_search_method
params = self.class.get_searchable_params
if params.nil?
return nil
else
@search_method || begin
params = self.class.get_searchable_params
@search_action = params[:with] || params[:find_by] || params[:search_by] || params[:filter_by]
@search_action = method(@search_action) if @search_action.is_a?(Symbol) || @search_action.is_a?(String)
@search_action
end
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
mp "To use the refresh control on < iOS 6, you need to include the CocoaPod 'CKRefreshControl'.", force_color: :yellow
end
end
end
def set_up_longpressable
if self.class.respond_to?(:get_longpressable) && self.class.get_longpressable
self.make_longpressable(self.class.get_longpressable_params)
end
end
def set_up_row_height
if self.class.respond_to?(:get_row_height) && params = self.class.get_row_height
self.view.rowHeight = params[:height]
self.view.estimatedRowHeight = params[:estimated]
end
end
def searching?
self.promotion_table_data.filtered?
end
def search_string
self.promotion_table_data.search_string
end
def update_table_view_data(data, args = {})
self.promotion_table_data.data = data
if args[:index_paths]
args[:animation] ||= UITableViewRowAnimationNone
table_view.beginUpdates
table_view.reloadRowsAtIndexPaths(Array(args[:index_paths]), withRowAnimation: args[:animation])
table_view.endUpdates
else
table_view.reloadData
end
end
def accessory_toggled_switch(switch)
table_cell = closest_parent(UITableViewCell, switch)
index_path = closest_parent(UITableView, table_cell).indexPathForCell(table_cell)
if index_path
data_cell = cell_at(index_path: index_path)
data_cell[:accessory][:arguments][:value] = switch.isOn if data_cell[:accessory][:arguments].is_a?(Hash)
trigger_action(data_cell[:accessory][:action], data_cell[:accessory][:arguments], index_path) if data_cell[:accessory][:action]
end
end
def delete_row(index_paths, animation = nil)
deletable_index_paths = []
index_paths = [index_paths] if index_paths.kind_of?(NSIndexPath)
index_paths.each do |index_path|
delete_cell = false
delete_cell = trigger_action(:on_cell_deleted, cell_at(index_path: index_path), index_path) if respond_to?(:on_cell_deleted)
unless delete_cell == false
self.promotion_table_data.delete_cell(index_path: index_path)
deletable_index_paths << index_path
end
end
table_view.deleteRowsAtIndexPaths(deletable_index_paths, withRowAnimation: map_row_animation_symbol(animation)) if deletable_index_paths.length > 0
end
def update_table_data(args = {})
args = { index_paths: args } unless args.is_a?(Hash)
self.update_table_view_data(self.table_data, args)
end
def toggle_edit_mode(animated = true)
edit_mode({enabled: !editing?, animated: animated})
end
def edit_mode(args = {})
args[:enabled] = false if args[:enabled].nil?
args[:animated] = true if args[:animated].nil?
setEditing(args[:enabled], animated:args[:animated])
end
def edit_mode?
!!isEditing
end
# Returns the data cell
def cell_at(args = {})
self.promotion_table_data.cell(args)
end
########## Cocoa touch methods #################
def numberOfSectionsInTableView(_)
self.promotion_table_data.sections.length
end
# Number of cells
def tableView(_, numberOfRowsInSection: section)
self.promotion_table_data.section_length(section)
end
def tableView(_, titleForHeaderInSection: section)
section = promotion_table_data.section(section)
section && section[:title]
end
def tableView(_, titleForFooterInSection: section)
section = promotion_table_data.section(section)
section && section[:footer]
end
# Set table_data_index if you want the right hand index column (jumplist)
def sectionIndexTitlesForTableView(_)
return if searching?
return self.table_data_index if self.respond_to?(:table_data_index)
nil
end
def tableView(_, cellForRowAtIndexPath: index_path)
params = index_path_to_section_index(index_path: index_path)
data_cell = cell_at(index: params[:index], section: params[:section])
return UITableViewCell.alloc.init unless data_cell
create_table_cell(data_cell)
end
def tableView(_, willDisplayCell: table_cell, forRowAtIndexPath: index_path)
data_cell = cell_at(index_path: index_path)
try :will_display_cell, table_cell, index_path
table_cell.send(:will_display) if table_cell.respond_to?(:will_display)
table_cell.send(:restyle!) if table_cell.respond_to?(:restyle!) # Teacup compatibility
end
def tableView(_, heightForRowAtIndexPath: index_path)
(cell_at(index_path: index_path)[:height] || tableView.rowHeight).to_f
end
def tableView(table_view, didSelectRowAtIndexPath: index_path)
=begin
###
puts "++ Current table_view => #{table_view}" if BW2.debug?
###
=end
data_cell = cell_at(index_path: index_path)
table_view.deselectRowAtIndexPath(index_path, animated: true) unless data_cell[:keep_selection] == true
trigger_action(data_cell[:action], data_cell[:arguments], index_path) if data_cell[:action]
end
def tableView(_, canEditRowAtIndexPath:index_path)
data_cell = cell_at(index_path: index_path, unfiltered: !searching?)
[:insert,:delete].include?(data_cell[:editing_style])
end
def tableView(_, editingStyleForRowAtIndexPath: index_path)
data_cell = cell_at(index_path: index_path, unfiltered: !searching?)
map_cell_editing_style(data_cell[:editing_style])
end
def tableView(_, commitEditingStyle: editing_style, forRowAtIndexPath: index_path)
if editing_style == UITableViewCellEditingStyleDelete
delete_row(index_path)
end
end
def tableView(_, canMoveRowAtIndexPath:index_path)
data_cell = cell_at(index_path: index_path, unfiltered: !searching?)
if (!data_cell[:moveable].nil? || data_cell[:moveable].is_a?(Symbol)) && data_cell[:moveable] != false
true
else
false
end
end
def tableView(_, targetIndexPathForMoveFromRowAtIndexPath:source_index_path, toProposedIndexPath:proposed_destination_index_path)
data_cell = cell_at(index_path: source_index_path, unfiltered: !searching?)
if data_cell[:moveable] == :section && source_index_path.section != proposed_destination_index_path.section
source_index_path
else
proposed_destination_index_path
end
end
def tableView(_, moveRowAtIndexPath:from_index_path, toIndexPath:to_index_path)
self.promotion_table_data.move_cell(from_index_path, to_index_path)
if self.respond_to?("on_cell_moved:")
args = {
paths: {
from: from_index_path,
to: to_index_path
},
cell: self.promotion_table_data.section(to_index_path.section)[:cells][to_index_path.row]
}
send(:on_cell_moved, args)
else
mp "Implement the on_cell_moved method in your PM::TableScreen to be notified when a user moves a cell.", force_color: :yellow
end
end
def tableView(table_view, sectionForSectionIndexTitle: title, atIndex: index)
return index unless ["{search}", UITableViewIndexSearch].include?(self.table_data_index[0])
if index == 0
table_view.scrollRectToVisible(CGRectMake(0.0, 0.0, 1.0, 1.0), animated: false)
-1 # NOTE: returning -1 since NSNotFound has been removed in Mojave/Marzipan
else
index - 1
end
end
def deleteRowsAtIndexPaths(index_paths, withRowAnimation: animation)
mp "ProMotion expects you to use 'delete_cell(index_paths, animation)'' instead of 'deleteRowsAtIndexPaths(index_paths, withRowAnimation:animation)'.", force_color: :yellow
delete_row(index_paths, animation)
end
# Section header view methods
def tableView(_, viewForHeaderInSection: index)
section = promotion_table_data.section(index)
view = section[:title_view]
view = section[:title_view].new if section[:title_view].respond_to?(:new)
view.on_load if view.respond_to?(:on_load)
view.title = section[:title] if view.respond_to?(:title=)
view
end
def tableView(_, heightForHeaderInSection: index)
section = promotion_table_data.section(index)
if section[:title_view] || section[:title].to_s.length > 0
if section[:title_view_height]
section[:title_view_height]
elsif (section_header = tableView(_, viewForHeaderInSection: index)) && section_header.respond_to?(:height)
section_header.height
else
tableView.sectionHeaderHeight
end
else
0.0
end
end
def tableView(_, willDisplayHeaderView:view, forSection:section)
action = :will_display_header
if respond_to?(action)
case self.method(action).arity
when 0 then self.send(action)
when 2 then self.send(action, view, section)
else self.send(action, view)
end
end
end
# Section footer view methods
def tableView(_, viewForFooterInSection: index)
section = promotion_table_data.section(index)
view = section[:footer_view]
view = section[:footer_view].new if section[:footer_view].respond_to?(:new)
view.on_load if view.respond_to?(:on_load)
view.title = section[:footer] if view.respond_to?(:title=)
view
end
def tableView(_, heightForFooterInSection: index)
section = promotion_table_data.section(index)
if section[:footer_view] || section[:footer].to_s.length > 0
if section[:footer_view_height]
section[:footer_view_height]
elsif (section_footer = tableView(_, viewForFooterInSection: index)) && section_footer.respond_to?(:height)
section_footer.height
else
tableView.sectionFooterHeight
end
else
0.0
end
end
def tableView(_, willDisplayFooterView:view, forSection:section)
action = :will_display_footer
if respond_to?(action)
case self.method(action).arity
when 0 then self.send(action)
when 2 then self.send(action, view, section)
else self.send(action, view)
end
end
end
protected
def map_cell_editing_style(symbol)
{
none: UITableViewCellEditingStyleNone,
delete: UITableViewCellEditingStyleDelete,
insert: UITableViewCellEditingStyleInsert
}[symbol] || symbol || UITableViewCellEditingStyleNone
end
def map_row_animation_symbol(symbol)
symbol ||= UITableViewRowAnimationAutomatic
{
fade: UITableViewRowAnimationFade,
right: UITableViewRowAnimationRight,
left: UITableViewRowAnimationLeft,
top: UITableViewRowAnimationTop,
bottom: UITableViewRowAnimationBottom,
none: UITableViewRowAnimationNone,
middle: UITableViewRowAnimationMiddle,
automatic: UITableViewRowAnimationAutomatic
}[symbol] || symbol || UITableViewRowAnimationAutomatic
end
def self.included(base)
base.extend(TableClassMethods)
end
end
end