ProMotion 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/.gitignore +0 -1
  2. data/Gemfile +1 -1
  3. data/README.md +336 -92
  4. data/Rakefile +1 -1
  5. data/lib/ProMotion.rb +14 -0
  6. data/lib/ProMotion/app_delegate.rb +63 -0
  7. data/lib/ProMotion/cocoatouch/NavigationController.rb +4 -0
  8. data/lib/ProMotion/cocoatouch/TableViewCell.rb +16 -0
  9. data/lib/ProMotion/cocoatouch/TableViewController.rb +50 -0
  10. data/lib/ProMotion/cocoatouch/ViewController.rb +52 -0
  11. data/lib/ProMotion/helpers/console.rb +24 -0
  12. data/lib/ProMotion/helpers/measure_helper.rb +20 -0
  13. data/lib/ProMotion/helpers/system_helper.rb +29 -0
  14. data/lib/ProMotion/helpers/tab_bar.rb +115 -0
  15. data/lib/ProMotion/helpers/view_helper.rb +39 -0
  16. data/lib/ProMotion/pro_motion.rb +3 -0
  17. data/lib/ProMotion/screen_helpers/_tables/_searchable_table.rb +66 -0
  18. data/lib/ProMotion/screen_helpers/_tables/_sectioned_table.rb +265 -0
  19. data/lib/ProMotion/screen_helpers/_tables/grouped_table.rb +13 -0
  20. data/lib/ProMotion/screen_helpers/_tables/plain_table.rb +14 -0
  21. data/lib/ProMotion/screen_helpers/screen_elements.rb +43 -0
  22. data/lib/ProMotion/screen_helpers/screen_navigation.rb +106 -0
  23. data/lib/ProMotion/screen_helpers/screen_tabs.rb +92 -0
  24. data/lib/ProMotion/screens/_screen_module.rb +222 -0
  25. data/lib/ProMotion/screens/_table_screen_module.rb +30 -0
  26. data/lib/ProMotion/screens/screen.rb +7 -0
  27. data/lib/ProMotion/screens/table_screen.rb +15 -0
  28. data/lib/ProMotion/version.rb +3 -0
  29. metadata +43 -63
  30. data/app/app_delegate.rb +0 -5
  31. data/app/screens/home_screen.rb +0 -21
  32. data/app/screens/test_screen.rb +0 -26
@@ -0,0 +1,3 @@
1
+ module ProMotion
2
+ end
3
+ ::PM = ProMotion unless defined?(::PM)
@@ -0,0 +1,66 @@
1
+ module ProMotion::MotionTable
2
+ module SearchableTable
3
+ def make_searchable(params={})
4
+ params[:content_controller] ||= params[:contentController]
5
+ params[:data_source] ||= params[:searchResultsDataSource]
6
+ params[:search_results_delegate] ||= params[:searchResultsDelegate]
7
+
8
+ params[:frame] ||= CGRectMake(0, 0, 320, 44) # TODO: Don't hardcode this...
9
+ params[:content_controller] ||= self
10
+ params[:delegate] ||= self
11
+ params[:data_source] ||= self
12
+ params[:search_results_delegate] ||= self
13
+
14
+ search_bar = UISearchBar.alloc.initWithFrame(params[:frame])
15
+ search_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
16
+
17
+ if params[:search_bar] && params[:search_bar][:placeholder]
18
+ search_bar.placeholder = params[:search_bar][:placeholder]
19
+ end
20
+
21
+ @contacts_search_display_controller = UISearchDisplayController.alloc.initWithSearchBar(search_bar, contentsController: params[:content_controller])
22
+ @contacts_search_display_controller.delegate = params[:delegate]
23
+ @contacts_search_display_controller.searchResultsDataSource = params[:data_source]
24
+ @contacts_search_display_controller.searchResultsDelegate = params[:search_results_delegate]
25
+
26
+ self.table_view.tableHeaderView = search_bar
27
+ end
28
+ alias :makeSearchable :make_searchable
29
+
30
+ ######### iOS methods, headless camel case #######
31
+
32
+ def searchDisplayController(controller, shouldReloadTableForSearchString:search_string)
33
+ @mt_filtered_data = nil
34
+ @mt_filtered_data = []
35
+
36
+ @mt_table_view_groups.each do |section|
37
+ new_section = {}
38
+ new_section[:cells] = []
39
+
40
+ section[:cells].each do |cell|
41
+ if cell[:title].to_s.downcase.strip.include?(search_string.downcase.strip)
42
+ new_section[:cells] << cell
43
+ end
44
+ end
45
+
46
+ if new_section[:cells] && new_section[:cells].length > 0
47
+ new_section[:title] = section[:title]
48
+ @mt_filtered_data << new_section
49
+ end
50
+ end
51
+ true
52
+ end
53
+
54
+ def searchDisplayControllerWillEndSearch(controller)
55
+ @mt_filtered = false
56
+ @mt_filtered_data = nil
57
+ self.table_view.setScrollEnabled true
58
+ end
59
+
60
+ def searchDisplayControllerWillBeginSearch(controller)
61
+ @mt_filtered = true
62
+ @mt_filtered_data = []
63
+ self.table_view.setScrollEnabled false
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,265 @@
1
+ module ProMotion::MotionTable
2
+ module SectionedTable
3
+ def table_setup
4
+ ProMotion::Console.log(" - #table_data method needed in TableScreen #{self.class.to_s}.", with_color: ProMotion::Console::RED_COLOR) unless self.respond_to?(:table_data)
5
+
6
+ self.view = self.create_table_view_from_data(self.table_data)
7
+ if self.class.respond_to?(:get_searchable) && self.class.get_searchable
8
+ self.make_searchable(content_controller: self, search_bar: self.class.get_searchable_params)
9
+ end
10
+ end
11
+
12
+ # @param [Array] Array of table data
13
+ # @returns [UITableView] delegated to self
14
+ def create_table_view_from_data(data)
15
+ set_table_view_data data
16
+ return table_view
17
+ end
18
+ alias :createTableViewFromData :create_table_view_from_data
19
+
20
+ def update_table_view_data(data)
21
+ set_table_view_data data
22
+ self.table_view.reloadData
23
+ end
24
+ alias :updateTableViewData :update_table_view_data
25
+
26
+ def set_table_view_data(data)
27
+ @mt_table_view_groups = data
28
+ end
29
+ alias :setTableViewData :set_table_view_data
30
+
31
+ def section_at_index(index)
32
+ if @mt_filtered
33
+ @mt_filtered_data.at(index)
34
+ else
35
+ @mt_table_view_groups.at(index)
36
+ end
37
+ end
38
+
39
+ def cell_at_section_and_index(section, index)
40
+ return section_at_index(section)[:cells].at(index) if section_at_index(section) && section_at_index(section)[:cells]
41
+ end
42
+ alias :cellAtSectionAndIndex :cell_at_section_and_index
43
+
44
+ def trigger_action(action, arguments)
45
+ if self.respond_to?(action)
46
+ expected_arguments = self.method(action).arity
47
+ if expected_arguments == 0
48
+ self.send(action)
49
+ elsif expected_arguments == 1 || expected_arguments == -1
50
+ self.send(action, arguments)
51
+ else
52
+ ProMotion::Console.log("MotionTable warning: #{action} expects #{expected_arguments} arguments. Maximum number of required arguments for an action is 1.", with_color: MotionTable::ProMotion::Console::RED_COLOR)
53
+ end
54
+ else
55
+ ProMotion::Console.log("Action not implemented: #{action.to_s}", with_color: ProMotion::Console::RED_COLOR)
56
+ end
57
+ end
58
+
59
+ def set_cell_attributes(element, args = {})
60
+ args.each do |k, v|
61
+ if v.is_a? Hash
62
+ v.each do
63
+ sub_element = element.send("#{k}")
64
+ set_cell_attributes(sub_element, v)
65
+ end
66
+ else
67
+ element.send("#{k}=", v) if element.respond_to?("#{k}=")
68
+ end
69
+ end
70
+ element
71
+ end
72
+
73
+ def accessory_toggled_switch(switch)
74
+ table_cell = switch.superview
75
+ indexPath = table_cell.superview.indexPathForCell(table_cell)
76
+
77
+ data_cell = cell_at_section_and_index(indexPath.section, indexPath.row)
78
+ data_cell[:arguments] = {} unless data_cell[:arguments]
79
+ data_cell[:arguments][:value] = switch.isOn if data_cell[:arguments].is_a? Hash
80
+ data_cell[:accessory_action] ||= data_cell[:accessoryAction] # For legacy support
81
+
82
+ trigger_action(data_cell[:accessory_action], data_cell[:arguments]) if data_cell[:accessory_action]
83
+ end
84
+
85
+ ########## Cocoa touch methods, leave as-is #################
86
+ def numberOfSectionsInTableView(table_view)
87
+ if @mt_filtered
88
+ return @mt_filtered_data.length if @mt_filtered_data
89
+ else
90
+ return @mt_table_view_groups.length if @mt_table_view_groups
91
+ end
92
+ 0
93
+ end
94
+
95
+ # Number of cells
96
+ def tableView(table_view, numberOfRowsInSection:section)
97
+ return section_at_index(section)[:cells].length if section_at_index(section) && section_at_index(section)[:cells]
98
+ 0
99
+ end
100
+
101
+ def tableView(table_view, titleForHeaderInSection:section)
102
+ return section_at_index(section)[:title] if section_at_index(section) && section_at_index(section)[:title]
103
+ end
104
+
105
+ # Set table_data_index if you want the right hand index column (jumplist)
106
+ def sectionIndexTitlesForTableView(table_view)
107
+ if self.respond_to?(:table_data_index)
108
+ self.table_data_index
109
+ end
110
+ end
111
+
112
+ def remap_data_cell(data_cell)
113
+ # Re-maps legacy data cell calls
114
+ mappings = {
115
+ cell_style: :cellStyle,
116
+ cell_identifier: :cellIdentifier,
117
+ cell_class: :cellClass,
118
+ masks_to_bounds: :masksToBounds,
119
+ background_color: :backgroundColor,
120
+ selection_style: :selectionStyle,
121
+ cell_class_attributes: :cellClassAttributes,
122
+ accessory_view: :accessoryView,
123
+ accessory_type: :accessoryType,
124
+ accessory_checked: :accessoryDefault,
125
+ remote_image: :remoteImage,
126
+ subviews: :subViews
127
+ }
128
+ mappings.each_pair do |n, old|
129
+ if data_cell[old]
130
+ warn "[DEPRECATION] `:#{old}` is deprecated in TableScreens. Use `:#{n}`"
131
+ data_cell[n] = data_cell[old]
132
+ end
133
+ end
134
+ if data_cell[:styles] && data_cell[:styles][:textLabel]
135
+ warn "[DEPRECATION] `:textLabel` is deprecated in TableScreens. Use `:label`"
136
+ data_cell[:styles][:label] = data_cell[:styles][:textLabel]
137
+ end
138
+ data_cell
139
+ end
140
+
141
+ def tableView(table_view, cellForRowAtIndexPath:indexPath)
142
+ # Aah, magic happens here...
143
+
144
+ data_cell = cell_at_section_and_index(indexPath.section, indexPath.row)
145
+ return UITableViewCell.alloc.init unless data_cell
146
+
147
+ data_cell = self.remap_data_cell(data_cell)
148
+
149
+ data_cell[:cell_style] ||= UITableViewCellStyleDefault
150
+ data_cell[:cell_identifier] ||= "Cell"
151
+ cell_identifier = data_cell[:cell_identifier]
152
+ data_cell[:cell_class] ||= ProMotion::TableViewCell
153
+
154
+ table_cell = table_view.dequeueReusableCellWithIdentifier(cell_identifier)
155
+ unless table_cell
156
+ table_cell = data_cell[:cell_class].alloc.initWithStyle(data_cell[:cell_style], reuseIdentifier:cell_identifier)
157
+
158
+ # Add optimizations here
159
+ table_cell.layer.masksToBounds = true if data_cell[:masks_to_bounds]
160
+ table_cell.backgroundColor = data_cell[:background_color] if data_cell[:background_color]
161
+ table_cell.selectionStyle = data_cell[:selection_style] if data_cell[:selection_style]
162
+ table_cell.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin
163
+ end
164
+
165
+ if data_cell[:cell_class_attributes]
166
+ set_cell_attributes table_cell, data_cell[:cell_class_attributes]
167
+ end
168
+
169
+ if data_cell[:accessory_view]
170
+ table_cell.accessoryView = data_cell[:accessory_view]
171
+ table_cell.accessoryView.autoresizingMask = UIViewAutoresizingFlexibleWidth
172
+ end
173
+
174
+ if data_cell[:accessory_type]
175
+ table_cell.accessoryType = data_cell[:accessory_type]
176
+ end
177
+
178
+ if data_cell[:accessory] && data_cell[:accessory] == :switch
179
+ switch_view = UISwitch.alloc.initWithFrame(CGRectZero)
180
+ switch_view.addTarget(self, action: "accessory_toggled_switch:", forControlEvents:UIControlEventValueChanged);
181
+ switch_view.on = true if data_cell[:accessory_checked]
182
+ table_cell.accessoryView = switch_view
183
+ end
184
+
185
+ if data_cell[:subtitle]
186
+ table_cell.detailTextLabel.text = data_cell[:subtitle]
187
+ table_cell.detailTextLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth
188
+ end
189
+
190
+ table_cell.selectionStyle = UITableViewCellSelectionStyleNone if data_cell[:no_select]
191
+
192
+ if data_cell[:remote_image]
193
+ if table_cell.imageView.respond_to?("setImageWithURL:placeholderImage:")
194
+ url = data_cell[:remote_image][:url]
195
+ url = NSURL.URLWithString(url) unless url.is_a?(NSURL)
196
+ placeholder = data_cell[:remote_image][:placeholder]
197
+ placeholder = UIImage.imageNamed(placeholder) if placeholder.is_a?(String)
198
+
199
+ table_cell.image_size = data_cell[:remote_image][:size] if data_cell[:remote_image][:size] && table_cell.respond_to?("image_size=")
200
+ table_cell.imageView.setImageWithURL(url, placeholderImage: placeholder)
201
+ table_cell.imageView.layer.masksToBounds = true
202
+ table_cell.imageView.layer.cornerRadius = data_cell[:remote_image][:radius]
203
+ else
204
+ ProMotion::Console.log("ProMotion Warning: to use remote_image with TableScreen you need to include the CocoaPod 'SDWebImage'.", with_color: ProMotion::Console::RED_COLOR)
205
+ end
206
+ elsif data_cell[:image]
207
+ table_cell.imageView.layer.masksToBounds = true
208
+ table_cell.imageView.image = data_cell[:image][:image]
209
+ table_cell.imageView.layer.cornerRadius = data_cell[:image][:radius] if data_cell[:image][:radius]
210
+ end
211
+
212
+ if data_cell[:subviews]
213
+ tag_number = 0
214
+ data_cell[:subviews].each do |view|
215
+ # Remove an existing view at that tag number
216
+ tag_number += 1
217
+ existing_view = table_cell.viewWithTag(tag_number)
218
+ existing_view.removeFromSuperview if existing_view
219
+
220
+ # Add the subview if it exists
221
+ if view
222
+ view.tag = tag_number
223
+ table_cell.addSubview view
224
+ end
225
+ end
226
+ end
227
+
228
+ if data_cell[:details]
229
+ table_cell.addSubview data_cell[:details][:image]
230
+ end
231
+
232
+ if data_cell[:styles] && data_cell[:styles][:label] && data_cell[:styles][:label][:frame]
233
+ ui_label = false
234
+ table_cell.contentView.subviews.each do |view|
235
+ if view.is_a? UILabel
236
+ ui_label = true
237
+ view.text = data_cell[:styles][:label][:text]
238
+ end
239
+ end
240
+
241
+ unless ui_label == true
242
+ label ||= UILabel.alloc.initWithFrame(CGRectZero)
243
+ set_cell_attributes label, data_cell[:styles][:label]
244
+ table_cell.contentView.addSubview label
245
+ end
246
+ # hackery
247
+ table_cell.textLabel.textColor = UIColor.clearColor
248
+ else
249
+ cell_title = data_cell[:title]
250
+ cell_title ||= ""
251
+ table_cell.textLabel.text = cell_title
252
+ end
253
+
254
+ return table_cell
255
+ end
256
+
257
+ def tableView(table_view, didSelectRowAtIndexPath:indexPath)
258
+ cell = cell_at_section_and_index(indexPath.section, indexPath.row)
259
+ table_view.deselectRowAtIndexPath(indexPath, animated: true);
260
+ cell[:arguments] ||= {}
261
+ cell[:arguments][:cell] = cell if cell[:arguments].is_a?(Hash)
262
+ trigger_action(cell[:action], cell[:arguments]) if cell[:action]
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,13 @@
1
+ module ProMotion::MotionTable
2
+ module GroupedTable
3
+ include SectionedTable
4
+
5
+ def table_view
6
+ @table_view ||= UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStyleGrouped)
7
+ @table_view.dataSource = self;
8
+ @table_view.delegate = self;
9
+ return @table_view
10
+ end
11
+ alias :tableView :table_view
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module ProMotion::MotionTable
2
+ module PlainTable
3
+ include SectionedTable
4
+ include SearchableTable
5
+
6
+ def table_view
7
+ @table_view ||= UITableView.alloc.initWithFrame(self.view.frame, style:UITableViewStylePlain)
8
+ @table_view.dataSource = self;
9
+ @table_view.delegate = self;
10
+ return @table_view
11
+ end
12
+ alias :tableView :table_view
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ module ProMotion
2
+ module ScreenElements
3
+ include ProMotion::ViewHelper
4
+
5
+ def add(v, attrs = {})
6
+ if attrs && attrs.length > 0
7
+ set_attributes(v, attrs)
8
+ end
9
+ self.view.addSubview(v)
10
+ v
11
+ end
12
+ alias :add_element :add
13
+ alias :add_view :add
14
+
15
+ def remove(v)
16
+ v.removeFromSuperview
17
+ v = nil
18
+ end
19
+ alias :remove_element :remove
20
+ alias :remove_view :remove
21
+
22
+ def bounds
23
+ return self.view.bounds
24
+ end
25
+
26
+ def frame
27
+ return self.view.frame
28
+ end
29
+
30
+ def content_height(view)
31
+ height = 0
32
+ view.subviews.each do |subview|
33
+ next if subview.isHidden
34
+ y = subview.frame.origin.y
35
+ h = subview.frame.size.height
36
+ if (y + h) > height
37
+ height = y + h
38
+ end
39
+ end
40
+ height
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,106 @@
1
+ module ProMotion
2
+ module ScreenNavigation
3
+ # TODO: De-uglify this method.
4
+ def open_screen(screen, args = {})
5
+ # Instantiate screen if given a class
6
+ screen = screen.new if screen.respond_to?(:new)
7
+
8
+ screen.parent_screen = self if screen.respond_to?("parent_screen=")
9
+
10
+ screen.title = args[:title] if args[:title] && screen.respond_to?("title=")
11
+
12
+ screen.modal = args[:modal] if args[:modal] && screen.respond_to?("modal=")
13
+
14
+ screen.hidesBottomBarWhenPushed = args[:hide_tab_bar] unless args[:hide_tab_bar].nil?
15
+
16
+ screen.add_nav_bar if args[:nav_bar] && screen.respond_to?(:add_nav_bar)
17
+
18
+ unless args[:close_all] || args[:modal]
19
+ screen.navigation_controller ||= self.navigation_controller if screen.respond_to?("navigation_controller=")
20
+ screen.tab_bar ||= self.tab_bar if screen.respond_to?("tab_bar=")
21
+ end
22
+
23
+ screen.send(:on_load) if screen.respond_to?(:on_load)
24
+
25
+ animated = args[:animated]
26
+ animated ||= true
27
+
28
+ if args[:close_all]
29
+ open_root_screen(screen)
30
+ elsif args[:modal]
31
+ vc = screen
32
+ vc = screen.main_controller if screen.respond_to?("main_controller=")
33
+ self.presentModalViewController(vc, animated:animated)
34
+ elsif args[:in_tab] && self.tab_bar
35
+ vc = open_tab(args[:in_tab])
36
+ if vc
37
+ if vc.is_a?(UINavigationController)
38
+ screen.navigation_controller = vc if screen.respond_to?("navigation_controller=")
39
+ push_view_controller(screen, vc)
40
+ else
41
+ self.tab_bar.selectedIndex = vc.tabBarItem.tag
42
+ end
43
+ else
44
+ Console.log("No tab bar item '#{args[:in_tab]}'", with_color: Console::RED_COLOR)
45
+ end
46
+ elsif self.navigation_controller
47
+ push_view_controller screen
48
+ elsif screen.respond_to?(:main_controller)
49
+ open_view_controller screen.main_controller
50
+ else
51
+ open_view_controller screen
52
+ end
53
+ end
54
+ alias :open :open_screen
55
+
56
+ def open_root_screen(screen)
57
+ app_delegate.open_root_screen(screen)
58
+ end
59
+ alias :fresh_start :open_root_screen
60
+
61
+ def app_delegate
62
+ UIApplication.sharedApplication.delegate
63
+ end
64
+
65
+ # TODO: De-uglify this method.
66
+ def close_screen(args = {})
67
+ args ||= {}
68
+ args[:animated] = true
69
+
70
+ # Pop current view, maybe with arguments, if in navigation controller
71
+ previous_screen = self.parent_screen
72
+ if self.is_modal?
73
+ self.parent_screen.dismissModalViewControllerAnimated(args[:animated])
74
+ elsif self.navigation_controller
75
+ if args[:to_screen] && args[:to_screen].is_a?(UIViewController)
76
+ self.navigation_controller.popToViewController(args[:to_screen], animated: args[:animated])
77
+ previous_screen = args[:to_screen]
78
+ else
79
+ self.navigation_controller.popViewControllerAnimated(args[:animated])
80
+ end
81
+ else
82
+ Console.log("Tried to close #{self.to_s}; however, this screen isn't modal or in a nav bar.", withColor: Console::PURPLE_COLOR)
83
+ end
84
+
85
+ if previous_screen && previous_screen.respond_to?(:on_return)
86
+ if args
87
+ previous_screen.send(:on_return, args)
88
+ else
89
+ previous_screen.send(:on_return)
90
+ end
91
+ ProMotion::Screen.current_screen = previous_screen
92
+ end
93
+ end
94
+ alias :close :close_screen
95
+
96
+ def open_view_controller(vc)
97
+ UIApplication.sharedApplication.delegate.load_root_view vc
98
+ end
99
+
100
+ def push_view_controller(vc, nav_controller=nil)
101
+ Console.log(" You need a nav_bar if you are going to push #{vc.to_s} onto it.", withColor: Console::RED_COLOR) unless self.navigation_controller
102
+ nav_controller ||= self.navigation_controller
103
+ nav_controller.pushViewController(vc, animated: true)
104
+ end
105
+ end
106
+ end