formotion 1.6 → 1.7

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