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.
- checksums.yaml +8 -8
- data/.travis.yml +1 -0
- data/Formotion.gemspec +1 -1
- data/LIST_OF_ROW_TYPES.md +115 -3
- data/README.md +1 -0
- data/examples/KitchenSink/app/app_delegate.rb +16 -2
- data/lib/formotion.rb +9 -1
- data/lib/formotion/form/form.rb +7 -0
- data/lib/formotion/form/form_delegate.rb +2 -2
- data/lib/formotion/patch/ui_image.rb +45 -0
- data/lib/formotion/patch/ui_text_view_placeholder.rb +4 -1
- data/lib/formotion/row/row.rb +13 -1
- data/lib/formotion/row_type/base.rb +13 -4
- data/lib/formotion/row_type/button.rb +1 -0
- data/lib/formotion/row_type/check_row.rb +2 -1
- data/lib/formotion/row_type/date_row.rb +3 -0
- data/lib/formotion/row_type/image_row.rb +6 -1
- data/lib/formotion/row_type/map_row.rb +79 -0
- data/lib/formotion/row_type/multi_choice_row.rb +25 -0
- data/lib/formotion/row_type/object_row.rb +30 -0
- data/lib/formotion/row_type/options_row.rb +2 -2
- data/lib/formotion/row_type/paged_image_row.rb +257 -0
- data/lib/formotion/row_type/picker_row.rb +2 -0
- data/lib/formotion/row_type/slider_row.rb +2 -2
- data/lib/formotion/row_type/static_row.rb +1 -1
- data/lib/formotion/row_type/string_row.rb +3 -7
- data/lib/formotion/row_type/subform_row.rb +3 -1
- data/lib/formotion/row_type/switch_row.rb +2 -2
- data/lib/formotion/row_type/tags_row.rb +172 -0
- data/lib/formotion/row_type/template_row.rb +3 -3
- data/lib/formotion/row_type/text_row.rb +5 -1
- data/lib/formotion/row_type/web_view_row.rb +44 -0
- data/lib/formotion/version.rb +1 -1
- data/resources/tags_row-selected.png +0 -0
- data/resources/tags_row-selected@2x.png +0 -0
- data/resources/tags_row.png +0 -0
- data/resources/tags_row@2x.png +0 -0
- data/spec/form_spec.rb +26 -0
- data/spec/functional/date_row_spec.rb +6 -0
- data/spec/functional/picker_row_spec.rb +5 -0
- data/spec/row_type/back_spec.rb +1 -1
- data/spec/row_type/base_spec.rb +3 -3
- data/spec/row_type/string_spec.rb +14 -3
- data/spec/row_type/subform_spec.rb +1 -1
- data/spec/row_type/submit_spec.rb +1 -1
- data/spec/row_type/text_spec.rb +7 -0
- 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.
|
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
|