formotion 1.6 → 1.7

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +0 -3
  4. data/Gemfile +4 -1
  5. data/LIST_OF_ROW_TYPES.md +58 -6
  6. data/Rakefile +6 -2
  7. data/app/app_delegate.rb +6 -0
  8. data/examples/KitchenSink/Gemfile +2 -1
  9. data/examples/KitchenSink/Rakefile +9 -4
  10. data/examples/KitchenSink/app/app_delegate.rb +20 -5
  11. data/examples/KitchenSink/resources/arrow-up.png +0 -0
  12. data/examples/KitchenSink/resources/arrow-up@2x.png +0 -0
  13. data/examples/KitchenSink/resources/email.png +0 -0
  14. data/examples/KitchenSink/resources/email@2x.png +0 -0
  15. data/examples/Persistence/app/app_delegate.rb +2 -0
  16. data/examples/Persistence/app/controller.rb +66 -0
  17. data/lib/formotion/controller/form_controller.rb +1 -1
  18. data/lib/formotion/form/form.rb +14 -5
  19. data/lib/formotion/model/formable.rb +8 -6
  20. data/lib/formotion/patch/ui_text_field.rb +15 -1
  21. data/lib/formotion/row/row.rb +19 -3
  22. data/lib/formotion/row/row_cell_builder.rb +48 -2
  23. data/lib/formotion/row_type/button.rb +2 -2
  24. data/lib/formotion/row_type/date_row.rb +8 -1
  25. data/lib/formotion/row_type/image_row.rb +2 -2
  26. data/lib/formotion/row_type/map_row.rb +60 -23
  27. data/lib/formotion/row_type/object_row.rb +2 -2
  28. data/lib/formotion/row_type/picker_row.rb +32 -0
  29. data/lib/formotion/row_type/string_row.rb +5 -2
  30. data/lib/formotion/row_type/web_link_row.rb +48 -0
  31. data/lib/formotion/version.rb +1 -1
  32. data/resources/camera.png +0 -0
  33. data/resources/camera@2x.png +0 -0
  34. data/spec/functional/map_row_spec.rb +151 -4
  35. data/spec/functional/web_link_row_spec.rb +35 -0
  36. data/spec/row_spec.rb +109 -1
  37. data/spec/row_type/web_link_spec.rb +23 -0
  38. metadata +13 -2
@@ -39,7 +39,7 @@ class UITextField
39
39
  # block takes argument textField
40
40
  def on_end(&block)
41
41
  add_delegate_method do
42
- @delegate.textFieldDidBeginEditing_callback = block
42
+ @delegate.textFieldDidEndEditing_callback = block
43
43
  end
44
44
  end
45
45
 
@@ -114,6 +114,11 @@ class UITextField_Delegate
114
114
  if self.textFieldShouldEndEditing_callback
115
115
  return self.textFieldShouldEndEditing_callback.call(theTextField)
116
116
  end
117
+
118
+ if Device.ios_version >= "7.0"
119
+ theTextField.text = theTextField.text.gsub("\u00a0", " ").strip
120
+ end
121
+
117
122
  true
118
123
  end
119
124
 
@@ -127,6 +132,15 @@ class UITextField_Delegate
127
132
  if self.shouldChangeCharactersInRange_callback
128
133
  return self.shouldChangeCharactersInRange_callback.call(theTextField, range, string)
129
134
  end
135
+
136
+ # fix for UITextField in iOS7 http://stackoverflow.com/questions/19569688/uitextfield-spacebar-does-not-advance-cursor-in-ios-7/20129483#20129483
137
+ if Device.ios_version >= "7.0"
138
+ if range.location == theTextField.text.length && string == " "
139
+ theTextField.text = theTextField.text.stringByAppendingString("\u00a0")
140
+ return false
141
+ end
142
+ end
143
+
130
144
  true
131
145
  end
132
146
 
@@ -9,6 +9,10 @@ module Formotion
9
9
  :value,
10
10
  # set as cell.titleLabel.text
11
11
  :title,
12
+ # set as cell.imageView.image
13
+ :image,
14
+ # an image placeholder for cell.imageView.image when using remote images
15
+ :image_placeholder,
12
16
  # set as cell.detailLabel.text
13
17
  :subtitle,
14
18
  # configures the type of input this is (string, phone, switch, etc)
@@ -101,7 +105,13 @@ module Formotion
101
105
  # Cell selection style
102
106
  # OPTIONS: :blue, :gray, :none
103
107
  # DEFAULT is :blue
104
- :selection_style
108
+ :selection_style,
109
+
110
+ # The following apply only to weblink rows
111
+
112
+ # Whether or not to display a warning to the user before leaving the app.
113
+ # DEFAULT is false
114
+ :warn
105
115
  ]
106
116
  PROPERTIES.each {|prop|
107
117
  attr_accessor prop
@@ -139,6 +149,8 @@ module Formotion
139
149
  attr_accessor :on_tap_callback
140
150
  # callback for when a row is tapped
141
151
  attr_accessor :on_delete_callback
152
+ # callback for when a row is exited
153
+ attr_accessor :on_end_callback
142
154
 
143
155
  # RowType object
144
156
  attr_accessor :object
@@ -264,7 +276,7 @@ module Formotion
264
276
  end
265
277
 
266
278
  def text_alignment=(alignment)
267
- @text_alignment = const_int_get("UITextAlignment", alignment)
279
+ @text_alignment = const_int_get("NSTextAlignment", alignment)
268
280
  end
269
281
 
270
282
  def selection_style=(style)
@@ -305,6 +317,10 @@ module Formotion
305
317
  self.on_begin_callback = block
306
318
  end
307
319
 
320
+ def on_end(&block)
321
+ self.on_end_callback = block
322
+ end
323
+
308
324
  # Used in :button type rows
309
325
  def on_tap(&block)
310
326
  self.on_tap_callback = block
@@ -398,7 +414,7 @@ module Formotion
398
414
  UITextFieldViewModeNever, UITextFieldViewModeAlways, UITextFieldViewModeWhileEditing,
399
415
  UITextFieldViewModeUnlessEditing, NSDateFormatterShortStyle, NSDateFormatterMediumStyle,
400
416
  NSDateFormatterLongStyle, NSDateFormatterFullStyle,
401
- UITextAlignmentRight, UITextAlignmentCenter, UITextAlignmentLeft
417
+ NSTextAlignmentRight, NSTextAlignmentCenter, NSTextAlignmentLeft
402
418
  ]
403
419
  end
404
420
  end
@@ -23,13 +23,20 @@ module Formotion
23
23
  cell.textLabel.text = new_value
24
24
  end
25
25
 
26
+ if row.image
27
+ Formotion::RowCellBuilder.set_image(cell, row)
28
+ observe(row, "image") do |old_value, new_value|
29
+ Formotion::RowCellBuilder.set_image(cell, row)
30
+ end
31
+ end
32
+
26
33
  cell.detailTextLabel.text = row.subtitle
27
34
  observe(row, "subtitle") do |old_value, new_value|
28
35
  cell.detailTextLabel.text = new_value
29
36
  end
30
37
 
31
38
  edit_field = row.object.build_cell(cell)
32
-
39
+
33
40
  if edit_field and edit_field.respond_to?("accessibilityLabel=")
34
41
  label = row.accessibility
35
42
  label = row.title unless label
@@ -39,5 +46,44 @@ module Formotion
39
46
  [cell, edit_field]
40
47
  end
41
48
 
49
+ def self.set_image(cell, row)
50
+ if row.image.is_a?(NSURL) || (row.image.is_a?(String) && row.image.include?("http"))
51
+ # Use a remote image helper to set the image.
52
+ image_url = row.image
53
+ image_url = NSURL.URLWithString(image_url) unless image_url.is_a?(NSURL)
54
+
55
+ placeholder = row.image_placeholder
56
+ placeholder = UIImage.imageNamed(placeholder) if placeholder.is_a?(String)
57
+
58
+ if cell.imageView.respond_to?("setImageWithURLRequest:placeholderImage:success:failure:")
59
+ # Use AFNetworking
60
+ request = NSURLRequest.requestWithURL(image_url)
61
+ cell.imageView.setImageWithURLRequest(request, placeholderImage: placeholder, success: ->(request, response, image) {
62
+ cell.imageView.image = image
63
+ cell.setNeedsLayout
64
+ }, failure: ->(request, response, error) {
65
+
66
+ })
67
+ elsif cell.imageView.respond_to?("setImageWithURL:placeholderImage:completed:")
68
+ cell.imageView.setImageWithURL(image_url, placeholderImage: placeholder, completed: ->(image, error, cacheType) {
69
+ cell.imageView.image = image
70
+ cell.setNeedsLayout
71
+ })
72
+ elsif cell.imageView.respond_to?("setImageWithURL:placeholder:")
73
+ # Use JMImageCache
74
+ JMImageCache.sharedCache.imageForURL(image_url, completionBlock: ->(downloadedImage) {
75
+ cell.imageView.image = downloadedImage
76
+ cell.setNeedsLayout
77
+ })
78
+ else
79
+ raise "Please add the AFNetworking, SDWebImage, or JMImageCache pods to your project to use remote images in Formotion"
80
+ end
81
+
82
+ else
83
+ # Just set the image like normal
84
+ cell.imageView.image = (row.image.is_a? String) ? UIImage.imageNamed(row.image) : row.image
85
+ end
86
+ end
87
+
42
88
  end
43
- end
89
+ end
@@ -20,8 +20,8 @@ module Formotion
20
20
  ((self.frame.size.send(dimen) - frame.size.send(dimen)) / 2.0)
21
21
  }
22
22
 
23
- self.textLabel.center = CGPointMake(self.frame.size.width / 2 - (Formotion::RowType::Base.field_buffer / 2), self.textLabel.center.y)
24
- self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2 - (Formotion::RowType::Base.field_buffer / 2), self.detailTextLabel.center.y)
23
+ self.textLabel.center = CGPointMake(self.frame.size.width / 2, self.textLabel.center.y)
24
+ self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2, self.detailTextLabel.center.y)
25
25
  end
26
26
  end
27
27
  nil
@@ -40,6 +40,12 @@ module Formotion
40
40
 
41
41
  def after_build(cell)
42
42
  self.row.text_field.inputView = self.picker
43
+ # work around an iOS7 bug: http://bit.ly/KcwKSv
44
+ if row.picker_mode == :countdown
45
+ self.picker.setDate(self.picker.date, animated:true)
46
+ picker.countDownDuration = self.row.value
47
+ end
48
+
43
49
  update
44
50
  end
45
51
 
@@ -49,13 +55,14 @@ module Formotion
49
55
  picker.datePickerMode = self.picker_mode
50
56
  picker.hidden = false
51
57
  picker.date = self.date_value || Time.now
58
+ picker.countDownDuration = self.row.value if row.picker_mode == :countdown
52
59
  picker.minuteInterval = self.row.minute_interval if self.row.minute_interval
53
60
 
54
61
  picker.when(UIControlEventValueChanged) do
55
62
  if self.row.picker_mode == :countdown
56
63
  self.row.value = @picker.countDownDuration
57
64
  else
58
- self.row.value = Time.at(@picker.date).to_i
65
+ self.row.value = Time.at(@picker.date).to_i
59
66
  end
60
67
  update
61
68
  end
@@ -15,7 +15,7 @@ module Formotion
15
15
  def build_cell(cell)
16
16
  cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleBlue
17
17
  # only show the "plus" when editable
18
- add_plus_accessory(cell) if row.editable?
18
+ add_plus_accessory(cell) if row.editable? && (row.value == nil)
19
19
 
20
20
  observe(self.row, "value") do |old_value, new_value|
21
21
  @image_view.image = new_value
@@ -25,7 +25,7 @@ module Formotion
25
25
  else
26
26
  self.row.row_height = 44
27
27
  # only show the "plus" when editable
28
- add_plus_accessory(cell) if row.editable?
28
+ add_plus_accessory(cell) if row.editable? && (row.value == nil)
29
29
  end
30
30
  row.form.reload_data
31
31
  end
@@ -3,31 +3,35 @@ motion_require 'base'
3
3
  module Formotion
4
4
  module RowType
5
5
  class MapRowData
6
-
6
+
7
7
  attr_accessor :pin, :options
8
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
9
+
10
+ def initialize(options)
11
+ @title=options[:title]
12
+ @subtitle=options[:subtitle]
13
+ @coordinate=options[:coord]
14
+ @options=options[:options]
15
15
  end
16
-
16
+
17
17
  def title
18
18
  @title
19
19
  end
20
-
20
+
21
21
  def subtitle
22
22
  @subtitle
23
23
  end
24
-
24
+
25
25
  def coordinate
26
- @coordinate
26
+ if @coordinate.is_a? CLCircularRegion
27
+ @coordinate.center
28
+ else
29
+ @coordinate
30
+ end
27
31
  end
28
-
32
+
29
33
  end
30
-
34
+
31
35
  class MapRow < Base
32
36
  include BW::KVO
33
37
 
@@ -35,34 +39,67 @@ module Formotion
35
39
 
36
40
  def set_pin
37
41
  return unless row.value
38
- coord = (row.value.is_a?(Array) and row.value.size==2) ? CLLocationCoordinate2D.new(row.value[0], row.value[1]) : row.value
39
- if coord.is_a?(CLLocationCoordinate2D)
40
- @map_view.removeAnnotation(@map_row_data) if @map_row_data
41
- region = MKCoordinateRegionMakeWithDistance(coord, 400.0, 480.0)
42
- @map_row_data = MapRowData.new(nil, nil, coord)
43
- @map_view.setRegion(region, animated:true)
42
+
43
+ unless row.value.is_a?(Hash)
44
+ coord = (row.value.is_a?(Array) and row.value.size==2) ? CLLocationCoordinate2D.new(row.value[0], row.value[1]) : row.value
45
+ row.value = {coord: coord, pin: {coord:coord}}
46
+ end
47
+
48
+ # Set Defaults
49
+ row.value = {
50
+ animated: true,
51
+ type: MKMapTypeStandard,
52
+ enabled: true
53
+ }.merge(row.value)
54
+
55
+ if row.value[:coord].is_a?(CLLocationCoordinate2D)
56
+ region = MKCoordinateRegionMakeWithDistance(row.value[:coord], 400.0, 480.0)
57
+ elsif row.value[:coord].is_a?(CLCircularRegion)
58
+ region = MKCoordinateRegionMakeWithDistance(
59
+ row.value[:coord].center,
60
+ row.value[:coord].radius * 2,
61
+ row.value[:coord].radius * 2
62
+ )
63
+ else
64
+ return
65
+ end
66
+
67
+ if row.value[:pin]
68
+ row.value[:pin] = {title: nil, subtitle:nil}.merge(row.value[:pin]) #Defaults
69
+
70
+ @map_row_data = MapRowData.new(row.value[:pin])
71
+ @map_view.removeAnnotations(@map_view.annotations)
44
72
  @map_view.addAnnotation(@map_row_data)
73
+ @map_view.selectAnnotation(@map_row_data, animated:row.value[:animated]) if row.value[:pin][:title]
45
74
  end
75
+
76
+ @map_view.setUserInteractionEnabled(row.value[:enabled])
77
+ @map_view.setMapType(row.value[:type])
78
+ @map_view.setRegion(region, animated:row.value[:animated])
46
79
  end
47
-
80
+
48
81
  def annotations
49
82
  @map_view.annotations
50
83
  end
51
84
 
85
+ def map
86
+ @map_view
87
+ end
88
+
52
89
  def build_cell(cell)
53
90
  cell.selectionStyle = self.row.selection_style || UITableViewCellSelectionStyleBlue
54
91
 
55
92
  @map_view = MKMapView.alloc.init
56
93
  @map_view.delegate = self
57
-
94
+
58
95
  set_pin
59
-
96
+
60
97
  observe(self.row, "value") do |old_value, new_value|
61
98
  break_with_semaphore do
62
99
  set_pin
63
100
  end
64
101
  end
65
-
102
+
66
103
  @map_view.tag = MAP_VIEW_TAG
67
104
  @map_view.contentMode = UIViewContentModeScaleAspectFit
68
105
  @map_view.backgroundColor = UIColor.clearColor
@@ -17,7 +17,7 @@ module Formotion
17
17
 
18
18
  field.clearButtonMode = UITextFieldViewModeWhileEditing
19
19
  field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter
20
- field.textAlignment = row.text_alignment || UITextAlignmentRight
20
+ field.textAlignment = row.text_alignment || NSTextAlignmentRight
21
21
 
22
22
  field.keyboardType = keyboardType
23
23
 
@@ -54,7 +54,7 @@ module Formotion
54
54
  field
55
55
 
56
56
  end
57
-
57
+
58
58
  # Used when row.value changes
59
59
  def update_text_field(new_value)
60
60
  self.row.text_field.text = row_value.to_s
@@ -8,6 +8,38 @@ module Formotion
8
8
  include RowType::ItemsMapper
9
9
  include RowType::MultiChoiceRow
10
10
 
11
+ def input_accessory_view(input_accessory)
12
+ case input_accessory
13
+ when :done
14
+ @input_accessory ||= begin
15
+ tool_bar = UIToolbar.alloc.initWithFrame([[0, 0], [0, 44]])
16
+ tool_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
17
+ tool_bar.translucent = true
18
+
19
+ left_space = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
20
+ UIBarButtonSystemItemFlexibleSpace,
21
+ target: nil,
22
+ action: nil)
23
+
24
+ done_button = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
25
+ UIBarButtonSystemItemDone,
26
+ target: self,
27
+ action: :done_editing)
28
+
29
+ tool_bar.items = [left_space, done_button]
30
+
31
+ tool_bar
32
+ end
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ # Callback for "Done" button in input_accessory_view
39
+ def done_editing
40
+ self.row.text_field.endEditing(true)
41
+ end
42
+
11
43
  def after_build(cell)
12
44
  self.row.text_field.inputView = self.picker
13
45
  self.row.text_field.text = name_for_value(row.value).to_s
@@ -33,7 +33,7 @@ module Formotion
33
33
 
34
34
  field.clearButtonMode = UITextFieldViewModeWhileEditing
35
35
  field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter
36
- field.textAlignment = row.text_alignment || UITextAlignmentRight
36
+ field.textAlignment = row.text_alignment || NSTextAlignmentRight
37
37
 
38
38
  field.keyboardType = keyboardType
39
39
 
@@ -112,6 +112,10 @@ module Formotion
112
112
  end
113
113
  end
114
114
 
115
+ field.on_end do |text_field|
116
+ row.on_end_callback && row.on_end_callback.call
117
+ end
118
+
115
119
  field.on_begin do |text_field|
116
120
  row.on_begin_callback && row.on_begin_callback.call
117
121
  end
@@ -152,7 +156,6 @@ module Formotion
152
156
  @input_accessory ||= begin
153
157
  tool_bar = UIToolbar.alloc.initWithFrame([[0, 0], [0, 44]])
154
158
  tool_bar.autoresizingMask = UIViewAutoresizingFlexibleWidth
155
- tool_bar.barStyle = UIBarStyleBlack
156
159
  tool_bar.translucent = true
157
160
 
158
161
  left_space = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
@@ -0,0 +1,48 @@
1
+ module Formotion
2
+ module RowType
3
+ class WebLinkRow < ObjectRow
4
+
5
+ def after_build(cell)
6
+ super
7
+
8
+ cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator
9
+ self.row.text_field.hidden = true
10
+ end
11
+
12
+ def on_select(tableView, tableViewDelegate)
13
+ if is_url?
14
+ if row.warn.nil? || row.warn == false
15
+ App.open_url row.value
16
+ else
17
+ warn
18
+ end
19
+ else
20
+ raise StandardError, "Row value for WebLinkRow should be a URL string or instance of NSURL."
21
+ end
22
+ end
23
+
24
+ def is_url?
25
+ (row.value.is_a?(String) && row.value[0..3] == "http") || row.value.is_a?(NSURL)
26
+ end
27
+
28
+ def warn
29
+ row.warn = {} unless row.warn.is_a? Hash #Convert value from true to a hash
30
+ row.warn = {
31
+ title: "Leaving #{App.name}",
32
+ message: "This action will leave #{App.name} and open Safari.",
33
+ buttons: ["Cancel", "OK"]
34
+ }.merge(row.warn)
35
+
36
+ BW::UIAlertView.new({
37
+ title: row.warn[:title],
38
+ message: row.warn[:message],
39
+ buttons: row.warn[:buttons],
40
+ cancel_button_index: 0
41
+ }) do |alert|
42
+ App.open_url(row.value) unless alert.clicked_button.cancel?
43
+ end.show
44
+ end
45
+
46
+ end
47
+ end
48
+ end