formotion 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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