formotion 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +8 -8
  2. data/.travis.yml +1 -0
  3. data/Formotion.gemspec +1 -1
  4. data/LIST_OF_ROW_TYPES.md +115 -3
  5. data/README.md +1 -0
  6. data/examples/KitchenSink/app/app_delegate.rb +16 -2
  7. data/lib/formotion.rb +9 -1
  8. data/lib/formotion/form/form.rb +7 -0
  9. data/lib/formotion/form/form_delegate.rb +2 -2
  10. data/lib/formotion/patch/ui_image.rb +45 -0
  11. data/lib/formotion/patch/ui_text_view_placeholder.rb +4 -1
  12. data/lib/formotion/row/row.rb +13 -1
  13. data/lib/formotion/row_type/base.rb +13 -4
  14. data/lib/formotion/row_type/button.rb +1 -0
  15. data/lib/formotion/row_type/check_row.rb +2 -1
  16. data/lib/formotion/row_type/date_row.rb +3 -0
  17. data/lib/formotion/row_type/image_row.rb +6 -1
  18. data/lib/formotion/row_type/map_row.rb +79 -0
  19. data/lib/formotion/row_type/multi_choice_row.rb +25 -0
  20. data/lib/formotion/row_type/object_row.rb +30 -0
  21. data/lib/formotion/row_type/options_row.rb +2 -2
  22. data/lib/formotion/row_type/paged_image_row.rb +257 -0
  23. data/lib/formotion/row_type/picker_row.rb +2 -0
  24. data/lib/formotion/row_type/slider_row.rb +2 -2
  25. data/lib/formotion/row_type/static_row.rb +1 -1
  26. data/lib/formotion/row_type/string_row.rb +3 -7
  27. data/lib/formotion/row_type/subform_row.rb +3 -1
  28. data/lib/formotion/row_type/switch_row.rb +2 -2
  29. data/lib/formotion/row_type/tags_row.rb +172 -0
  30. data/lib/formotion/row_type/template_row.rb +3 -3
  31. data/lib/formotion/row_type/text_row.rb +5 -1
  32. data/lib/formotion/row_type/web_view_row.rb +44 -0
  33. data/lib/formotion/version.rb +1 -1
  34. data/resources/tags_row-selected.png +0 -0
  35. data/resources/tags_row-selected@2x.png +0 -0
  36. data/resources/tags_row.png +0 -0
  37. data/resources/tags_row@2x.png +0 -0
  38. data/spec/form_spec.rb +26 -0
  39. data/spec/functional/date_row_spec.rb +6 -0
  40. data/spec/functional/picker_row_spec.rb +5 -0
  41. data/spec/row_type/back_spec.rb +1 -1
  42. data/spec/row_type/base_spec.rb +3 -3
  43. data/spec/row_type/string_spec.rb +14 -3
  44. data/spec/row_type/subform_spec.rb +1 -1
  45. data/spec/row_type/submit_spec.rb +1 -1
  46. data/spec/row_type/text_spec.rb +7 -0
  47. metadata +17 -6
@@ -13,6 +13,7 @@ module Formotion
13
13
  IMAGE_VIEW_TAG=1100
14
14
 
15
15
  def build_cell(cell)
16
+ cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleBlue
16
17
  # only show the "plus" when editable
17
18
  add_plus_accessory(cell) if row.editable?
18
19
 
@@ -90,6 +91,10 @@ module Formotion
90
91
  @camera = BW::Device.camera.send((source == :camera) ? :rear : :any)
91
92
  @camera.picture(source_type: source, media_types: [:image]) do |result|
92
93
  if result[:original_image]
94
+ #-Resize image when requested (no image upscale)
95
+ if result[:original_image].respond_to?(:resize_image_to_size) and row.max_image_size
96
+ result[:original_image]=result[:original_image].resize_image_to_size(row.max_image_size, false)
97
+ end
93
98
  row.value = result[:original_image]
94
99
  end
95
100
  end
@@ -100,7 +105,7 @@ module Formotion
100
105
  @add_button ||= begin
101
106
  button = UIButton.buttonWithType(UIButtonTypeContactAdd)
102
107
  button.when(UIControlEventTouchUpInside) do
103
- self.on_select(nil, nil)
108
+ self._on_select(nil, nil)
104
109
  end
105
110
  button
106
111
  end
@@ -0,0 +1,79 @@
1
+ motion_require 'base'
2
+
3
+ module Formotion
4
+ module RowType
5
+ class MapRowData
6
+
7
+ attr_accessor :pin, :options
8
+ #attr_accessor :title, :subtitle, :coordinate
9
+
10
+ def initialize(title, subtitle, coordinate, options={})
11
+ @title=title
12
+ @subtitle=subtitle
13
+ @coordinate=coordinate
14
+ @options=options
15
+ end
16
+
17
+ def title
18
+ @title
19
+ end
20
+
21
+ def subtitle
22
+ @subtitle
23
+ end
24
+
25
+ def coordinate
26
+ @coordinate
27
+ end
28
+
29
+ end
30
+
31
+ class MapRow < Base
32
+
33
+ MAP_VIEW_TAG=1100
34
+
35
+ def build_cell(cell)
36
+ cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleBlue
37
+
38
+ @map_view = MKMapView.alloc.init
39
+ @map_view.delegate = self
40
+ if row.value
41
+ coord = (row.value.is_a?(Array) and row.value.size==2) ? CLLocationCoordinate2D.new(row.value[0], row.value[1]) : row.value
42
+ if coord.is_a?(CLLocationCoordinate2D)
43
+ region = MKCoordinateRegionMakeWithDistance(coord, 400.0, 480.0)
44
+ @map_view.setRegion(region, animated:true)
45
+ m=MapRowData.new(nil, nil, coord)
46
+ @map_view.addAnnotation(m)
47
+ end
48
+ end
49
+ @map_view.tag = MAP_VIEW_TAG
50
+ @map_view.contentMode = UIViewContentModeScaleAspectFit
51
+ @map_view.backgroundColor = UIColor.clearColor
52
+ cell.addSubview(@map_view)
53
+
54
+ cell.swizzle(:layoutSubviews) do
55
+ def layoutSubviews
56
+ old_layoutSubviews
57
+
58
+ # viewWithTag is terrible, but I think it's ok to use here...
59
+ formotion_field = self.viewWithTag(MAP_VIEW_TAG)
60
+
61
+ field_frame = formotion_field.frame
62
+ field_frame.origin.y = 10
63
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + Formotion::RowType::Base.field_buffer
64
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - Formotion::RowType::Base.field_buffer
65
+ field_frame.size.height = self.frame.size.height - Formotion::RowType::Base.field_buffer
66
+ formotion_field.frame = field_frame
67
+ end
68
+ end
69
+ end
70
+
71
+ def on_select(tableView, tableViewDelegate)
72
+ if !row.editable?
73
+ return
74
+ end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,25 @@
1
+ module Formotion
2
+ module RowType
3
+ module MultiChoiceRow
4
+
5
+ def build_cell(cell)
6
+ field = super(cell)
7
+ field.clearButtonMode = UITextFieldViewModeNever
8
+ field.swizzle(:caretRectForPosition) do
9
+ def caretRectForPosition(position)
10
+ CGRectZero
11
+ end
12
+ end
13
+ field
14
+ end
15
+
16
+ def add_callbacks(field)
17
+ super
18
+ field.should_change? do |text_field|
19
+ false
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ motion_require 'string_row'
2
+
3
+ module Formotion
4
+ module RowType
5
+ class ObjectRow < StringRow
6
+
7
+ def build_cell(cell)
8
+ super.tap do |field|
9
+
10
+ # "remove" the setText swizzle
11
+ if UIDevice.currentDevice.systemVersion >= "6.0" and field.respond_to?("old_setText")
12
+ unswizzle = Proc.new do
13
+ def setText(text)
14
+ old_setText(text)
15
+ end
16
+ end
17
+ field.instance_eval unswizzle
18
+ end
19
+
20
+ end
21
+ end
22
+
23
+ # overriden in subclasses
24
+ def row_value
25
+ row.value
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -8,7 +8,7 @@ module Formotion
8
8
  include RowType::ItemsMapper
9
9
 
10
10
  def build_cell(cell)
11
- cell.selectionStyle = UITableViewCellSelectionStyleNone
11
+ cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleNone
12
12
 
13
13
  segmentedControl = UISegmentedControl.alloc.initWithItems(item_names || [])
14
14
  segmentedControl.selectedSegmentIndex = name_index_of_value(row.value) if row.value
@@ -35,4 +35,4 @@ module Formotion
35
35
  end
36
36
  end
37
37
  end
38
- end
38
+ end
@@ -0,0 +1,257 @@
1
+ motion_require 'base'
2
+
3
+ # ideas taken from:
4
+ # http://www.raywenderlich.com/10518/how-to-use-uiscrollview-to-scroll-and-zoom-content
5
+
6
+ module Formotion
7
+ module RowType
8
+ class PagedImageRow < Base
9
+ TAKE = BW.localized_string("Take", nil)
10
+ DELETE = BW.localized_string("Delete", nil)
11
+ CHOOSE = BW.localized_string("Choose", nil)
12
+ CANCEL = BW.localized_string("Cancel", nil)
13
+
14
+ include BW::KVO
15
+
16
+ PAGE_VIEW_TAG=1110
17
+ SCROLL_VIEW_TAG=1111
18
+
19
+ def build_cell(cell)
20
+ # only show the "plus" when editable
21
+ add_plus_accessory(cell) if row.editable?
22
+
23
+ self.row.value = [] unless self.row.value.is_a?(Array)
24
+
25
+ @page_view = UIPageControl.alloc.init
26
+ @page_view.tag = PAGE_VIEW_TAG
27
+ @page_view.pageIndicatorTintColor = '#d0d0d0'.to_color
28
+ @page_view.currentPageIndicatorTintColor = '#505050'.to_color
29
+ @page_view.currentPage = 0
30
+ @page_view.numberOfPages = self.row.value.size
31
+ @page_view.when(UIControlEventValueChanged) do
32
+ page_width = @scroll_view.frame.size.width
33
+ page=@page_view.currentPage
34
+ offset=((page * page_width * 2.0 - page_width) / 2.0) + (page_width/2.0)
35
+ point=@scroll_view.contentOffset
36
+ point.x=offset
37
+ @scroll_view.contentOffset=point
38
+ end
39
+ cell.addSubview(@page_view)
40
+
41
+ @scroll_view = UIScrollView.alloc.init
42
+ @scroll_view.tag = SCROLL_VIEW_TAG
43
+ @scroll_view.delegate = self
44
+ @scroll_view.pagingEnabled = true
45
+ @scroll_view.delaysContentTouches = true
46
+ @scroll_view.showsHorizontalScrollIndicator=false
47
+ @scroll_view.showsVerticalScrollIndicator=false
48
+
49
+ pages_size = @scroll_view.frame.size
50
+ @scroll_view.contentSize = CGSizeMake(pages_size.width * self.row.value.size, pages_size.height)
51
+ @page_views = [nil]*self.row.value.size
52
+
53
+ cell.addSubview(@scroll_view)
54
+
55
+ cell.swizzle(:layoutSubviews) do
56
+ def layoutSubviews
57
+ old_layoutSubviews
58
+
59
+ # viewWithTag is terrible, but I think it's ok to use here...
60
+
61
+ formotion_field = self.viewWithTag(SCROLL_VIEW_TAG)
62
+ field_frame = formotion_field.frame
63
+ field_frame.origin.y = 10
64
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + Formotion::RowType::Base.field_buffer
65
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - Formotion::RowType::Base.field_buffer
66
+ f_height = self.frame.size.height - 20 - Formotion::RowType::Base.field_buffer
67
+ field_frame.size.height = f_height
68
+ formotion_field.frame = field_frame
69
+ scroll_view=formotion_field
70
+
71
+ formotion_field = self.viewWithTag(PAGE_VIEW_TAG)
72
+ field_frame = formotion_field.frame
73
+ field_frame.origin.y = 20 + f_height
74
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + Formotion::RowType::Base.field_buffer
75
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - Formotion::RowType::Base.field_buffer
76
+ field_frame.size.height = 10
77
+ formotion_field.frame = field_frame
78
+
79
+ scroll_view.delegate.resizePages
80
+ scroll_view.delegate.clearPages
81
+ end
82
+ end
83
+ end
84
+
85
+ def on_select(tableView, tableViewDelegate)
86
+ if !row.editable?
87
+ return
88
+ end
89
+ end
90
+
91
+ def actionSheet(actionSheet, clickedButtonAtIndex: index)
92
+ if index == actionSheet.destructiveButtonIndex and !@photo_page.nil?
93
+ self.row.value[@photo_page]=nil
94
+ self.row.value.delete(nil)
95
+ self.resizePages
96
+ self.clearPages
97
+ return
98
+ end
99
+
100
+ source = nil
101
+ case actionSheet.buttonTitleAtIndex(index)
102
+ when TAKE
103
+ source = :camera
104
+ when CHOOSE
105
+ source = :photo_library
106
+ when CANCEL
107
+ end
108
+
109
+ if source
110
+ @camera = BW::Device.camera.send((source == :camera) ? :rear : :any)
111
+ @camera.picture(source_type: source, media_types: [:image]) do |result|
112
+ if result[:original_image]
113
+ #-Resize image when requested (no image upscale)
114
+ if result[:original_image].respond_to?(:resize_image_to_size) and row.max_image_size
115
+ result[:original_image]=result[:original_image].resize_image_to_size(row.max_image_size, false)
116
+ end
117
+ # new photo
118
+ if @photo_page.nil?
119
+ self.row.value<<result[:original_image]
120
+ else # or overwrite photo
121
+ self.row.value[@photo_page]=result[:original_image]
122
+ end
123
+ self.resizePages
124
+ self.clearPages
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def add_plus_accessory(cell)
131
+ @add_button ||= begin
132
+ button = UIButton.buttonWithType(UIButtonTypeContactAdd)
133
+ button.when(UIControlEventTouchUpInside) do
134
+ @page_view.becomeFirstResponder
135
+ take_photo
136
+ end
137
+ button
138
+ end
139
+ cell.accessoryView = cell.editingAccessoryView = @add_button
140
+ end
141
+
142
+ #{{{Paged
143
+ def loadVisiblePages
144
+ # First, determine which page is currently visible
145
+ page_width = @scroll_view.frame.size.width
146
+ page = ((@scroll_view.contentOffset.x * 2.0 + page_width) / (page_width * 2.0)).floor
147
+ # Update the page control
148
+ @page_view.currentPage = page
149
+ # Work out which pages we want to load
150
+ first_page = page - 10
151
+ last_page = page + 1
152
+ # Purge anything before the first page
153
+ 0.upto(first_page-1) do |i|
154
+ self.purgePage(i)
155
+ end
156
+ first_page.upto(last_page) do |i|
157
+ self.loadPage(i)
158
+ end
159
+ (last_page+1).upto(self.row.value.size-1) do |i|
160
+ self.purgePage(i)
161
+ end
162
+ end
163
+
164
+ def get_active_page
165
+ page_width = @scroll_view.frame.size.width
166
+ ((@scroll_view.contentOffset.x * 2.0 + page_width) / (page_width * 2.0)).floor
167
+ end
168
+
169
+ def pages_single_tap
170
+ page = get_active_page
171
+ if row.editable?
172
+ take_photo(page)
173
+ else
174
+ _on_select(nil, nil)
175
+ end
176
+ end
177
+
178
+ def take_photo(_page=nil)
179
+ @photo_page=_page
180
+ @action_sheet = UIActionSheet.alloc.init
181
+ @action_sheet.delegate = self
182
+ @action_sheet.destructiveButtonIndex = (@action_sheet.addButtonWithTitle DELETE) unless _page.nil?
183
+ @action_sheet.addButtonWithTitle TAKE if BW::Device.camera.front? or BW::Device.camera.rear?
184
+ @action_sheet.addButtonWithTitle CHOOSE
185
+ @action_sheet.cancelButtonIndex = (@action_sheet.addButtonWithTitle CANCEL)
186
+ @action_sheet.showInView @scroll_view
187
+ end
188
+
189
+ def clearPages
190
+ 0.upto(@page_views.size-1) do |i|
191
+ self.purgePage(i) unless @page_views[i].nil?
192
+ end
193
+ self.loadVisiblePages
194
+ end
195
+
196
+ def resizePages
197
+ pages_size = @scroll_view.frame.size
198
+ @scroll_view.contentSize = CGSizeMake(pages_size.width * self.row.value.size, pages_size.height)
199
+ #lf = @page_view.frame
200
+ #cf = @page_view.superview.frame
201
+ #old_width = lf.size.width
202
+ #lf.size.width = cf.size.width-(lf.origin.x*2+10*2)
203
+ #@page_view.frame=lf
204
+ #lf.size.width!=old_width
205
+ @page_view.currentPage = 0
206
+ @page_view.numberOfPages = self.row.value.size
207
+ end
208
+
209
+ def loadPage(_page)
210
+ if _page < 0 || _page >= self.row.value.size
211
+ # If it's outside the range of what we have to display, then do nothing
212
+ return
213
+ end
214
+ # Load an individual page, first seeing if we've already loaded it
215
+ page_view = @page_views[_page]
216
+ if page_view.nil?
217
+ frame=@scroll_view.bounds
218
+ frame.origin.x = frame.size.width * _page
219
+ frame.origin.y = 0.0
220
+ thumb = self.row.value[_page].resize_image_to_size([frame.size.height,frame.size.height], false)
221
+ new_page_view = UIImageView.alloc.initWithImage(thumb)
222
+ new_page_view.userInteractionEnabled = true
223
+ new_page_view.contentMode = UIViewContentModeScaleAspectFit
224
+ new_page_view.frame = frame
225
+ single_tap = UITapGestureRecognizer.alloc.initWithTarget(self, action:"pages_single_tap")
226
+ single_tap.numberOfTapsRequired = 1
227
+ new_page_view.addGestureRecognizer(single_tap)
228
+ @scroll_view.addSubview(new_page_view)
229
+ @page_views[_page]=new_page_view
230
+ end
231
+ end
232
+
233
+ def purgePage(_page)
234
+ if _page < 0 || _page >= self.row.value.size
235
+ # If it's outside the range of what we have to display, then do nothing
236
+ return
237
+ end
238
+ # Remove a page from the scroll view and reset the container array
239
+ page_view = @page_views[_page]
240
+ unless page_view.nil?
241
+ page_view.gestureRecognizers.each do |gr|
242
+ page_view.removeGestureRecognizer(gr)
243
+ end
244
+ page_view.removeFromSuperview
245
+ @page_views[_page]=nil
246
+ end
247
+ end
248
+
249
+ def scrollViewDidScroll(_scroll_view)
250
+ # Load the pages which are now on screen
251
+ self.loadVisiblePages
252
+ end
253
+ #}}}
254
+
255
+ end
256
+ end
257
+ end
@@ -1,10 +1,12 @@
1
1
  # currently supports only one component
2
2
  motion_require 'string_row'
3
+ motion_require 'multi_choice_row'
3
4
 
4
5
  module Formotion
5
6
  module RowType
6
7
  class PickerRow < StringRow
7
8
  include RowType::ItemsMapper
9
+ include RowType::MultiChoiceRow
8
10
 
9
11
  def after_build(cell)
10
12
  self.row.text_field.inputView = self.picker