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