formotion 0.0.3 → 0.5

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 (69) hide show
  1. data/.gitignore +2 -1
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +6 -0
  4. data/LIST_OF_ROW_TYPES.md +163 -0
  5. data/NEW_ROW_TYPES.md +73 -0
  6. data/README.md +11 -14
  7. data/Rakefile +6 -0
  8. data/app/app_delegate.rb +22 -0
  9. data/examples/KitchenSink/Rakefile +1 -1
  10. data/examples/KitchenSink/app/app_delegate.rb +50 -2
  11. data/examples/Login/.gitignore +5 -0
  12. data/examples/Login/Rakefile +9 -0
  13. data/examples/Login/app/app_delegate.rb +16 -0
  14. data/examples/Login/app/login_controller.rb +47 -0
  15. data/examples/Login/app/user_controller.rb +38 -0
  16. data/examples/Login/spec/main_spec.rb +9 -0
  17. data/lib/formotion.rb +7 -1
  18. data/lib/formotion/{form.rb → form/form.rb} +0 -0
  19. data/lib/formotion/{form_delegate.rb → form/form_delegate.rb} +10 -15
  20. data/lib/formotion/patch/object.rb +9 -0
  21. data/lib/formotion/patch/ui_action_sheet.rb +27 -0
  22. data/lib/formotion/patch/ui_text_view.rb +122 -0
  23. data/lib/formotion/patch/ui_text_view_placeholder.rb +65 -0
  24. data/lib/formotion/{row.rb → row/row.rb} +37 -27
  25. data/lib/formotion/row/row_cell_builder.rb +28 -0
  26. data/lib/formotion/row_type/base.rb +41 -0
  27. data/lib/formotion/row_type/check_row.rb +30 -0
  28. data/lib/formotion/row_type/date_row.rb +61 -0
  29. data/lib/formotion/row_type/email_row.rb +11 -0
  30. data/lib/formotion/row_type/image_row.rb +99 -0
  31. data/lib/formotion/row_type/number_row.rb +11 -0
  32. data/lib/formotion/row_type/options_row.rb +24 -0
  33. data/lib/formotion/row_type/phone_row.rb +11 -0
  34. data/lib/formotion/row_type/row_type.rb +21 -0
  35. data/lib/formotion/row_type/slider_row.rb +43 -0
  36. data/lib/formotion/row_type/static_row.rb +6 -0
  37. data/lib/formotion/row_type/string_row.rb +112 -0
  38. data/lib/formotion/row_type/submit_row.rb +34 -0
  39. data/lib/formotion/row_type/switch_row.rb +19 -0
  40. data/lib/formotion/row_type/text_row.rb +76 -0
  41. data/lib/formotion/{section.rb → section/section.rb} +3 -0
  42. data/lib/formotion/version.rb +1 -1
  43. data/spec/form_spec.rb +5 -4
  44. data/spec/functional/character_spec.rb +70 -0
  45. data/spec/functional/check_row_spec.rb +42 -0
  46. data/spec/functional/date_row_spec.rb +63 -0
  47. data/spec/functional/image_row_spec.rb +82 -0
  48. data/spec/functional/options_row_spec.rb +27 -0
  49. data/spec/functional/slider_row_spec.rb +27 -0
  50. data/spec/functional/submit_row_spec.rb +30 -0
  51. data/spec/functional/switch_row_spec.rb +28 -0
  52. data/spec/functional/text_row_spec.rb +60 -0
  53. data/spec/row_type/check_spec.rb +27 -0
  54. data/spec/row_type/date_spec.rb +69 -0
  55. data/spec/row_type/email_spec.rb +22 -0
  56. data/spec/row_type/image_spec.rb +43 -0
  57. data/spec/row_type/number_spec.rb +22 -0
  58. data/spec/row_type/options_spec.rb +37 -0
  59. data/spec/row_type/phone_spec.rb +22 -0
  60. data/spec/row_type/slider_spec.rb +47 -0
  61. data/spec/row_type/static_spec.rb +15 -0
  62. data/spec/row_type/string_spec.rb +52 -0
  63. data/spec/row_type/submit_spec.rb +28 -0
  64. data/spec/row_type/switch_spec.rb +27 -0
  65. data/spec/row_type/text_spec.rb +41 -0
  66. data/spec/support/ui_control_wrap_extension.rb +7 -0
  67. metadata +88 -9
  68. data/lib/formotion/row_cell_builder.rb +0 -168
  69. data/lib/formotion/row_type.rb +0 -33
@@ -0,0 +1,34 @@
1
+ module Formotion
2
+ module RowType
3
+ class SubmitRow < Base
4
+
5
+ def submit_button?
6
+ true
7
+ end
8
+
9
+ # Does a clever little trick to override #layoutSubviews
10
+ # for just this one UITableViewCell object, in order to
11
+ # center it's labels horizontally.
12
+ def build_cell(cell)
13
+ cell.swizzle(:layoutSubviews) do
14
+ def layoutSubviews
15
+ old_layoutSubviews
16
+
17
+ center = lambda {|frame, dimen|
18
+ ((self.frame.size.send(dimen) - frame.size.send(dimen)) / 2.0)
19
+ }
20
+
21
+ self.textLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.textLabel.center.y)
22
+ self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.detailTextLabel.center.y)
23
+ end
24
+ end
25
+ nil
26
+ end
27
+
28
+ def on_select(tableView, tableViewDelegate)
29
+ tableViewDelegate.submit
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ module Formotion
2
+ module RowType
3
+ class SwitchRow < Base
4
+
5
+ def build_cell(cell)
6
+ cell.selectionStyle = UITableViewCellSelectionStyleNone
7
+ switchView = UISwitch.alloc.initWithFrame(CGRectZero)
8
+ switchView.accessibilityLabel = row.title + " Switch"
9
+ cell.accessoryView = switchView
10
+ switchView.setOn(row.value || false, animated:false)
11
+ switchView.when(UIControlEventValueChanged) do
12
+ row.value = switchView.isOn
13
+ end
14
+ nil
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ module Formotion
2
+ module RowType
3
+ class TextRow < Base
4
+
5
+ TEXT_VIEW_TAG=1000
6
+
7
+ attr_accessor :field
8
+
9
+ def build_cell(cell)
10
+
11
+ @field = UITextView.alloc.initWithFrame(CGRectZero)
12
+ field.backgroundColor = UIColor.clearColor
13
+ field.editable = true
14
+ field.tag = TEXT_VIEW_TAG
15
+
16
+ field.text = row.value
17
+
18
+ field.returnKeyType = row.return_key || UIReturnKeyDefault
19
+ field.autocapitalizationType = row.auto_capitalization if row.auto_capitalization
20
+ field.autocorrectionType = row.auto_correction if row.auto_correction
21
+ field.placeholder = row.placeholder
22
+
23
+ field.on_begin do |text_field|
24
+ row.on_begin_callback && row.on_begin_callback.call
25
+ @tap_gesture.enabled = true
26
+ end
27
+
28
+ field.should_begin? do |text_field|
29
+ row.section.form.active_row = row
30
+ true
31
+ end
32
+
33
+ field.on_change do |text_field|
34
+ row.value = text_field.text
35
+ end
36
+
37
+ field.on_end do |text_field|
38
+ @tap_gesture.enabled = false
39
+ end
40
+
41
+ @tap_gesture = UITapGestureRecognizer.alloc.initWithTarget self, action:'dismissKeyboard'
42
+ @tap_gesture.enabled = false
43
+ cell.addGestureRecognizer @tap_gesture
44
+
45
+ cell.swizzle(:layoutSubviews) do
46
+ def layoutSubviews
47
+ old_layoutSubviews
48
+
49
+ # viewWithTag is terrible, but I think it's ok to use here...
50
+ formotion_field = self.viewWithTag(TEXT_VIEW_TAG)
51
+ formotion_field.sizeToFit
52
+
53
+ field_frame = formotion_field.frame
54
+ field_frame.origin.y = 10
55
+ field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20
56
+ field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20
57
+ field_frame.size.height = self.frame.size.height - 20
58
+ formotion_field.frame = field_frame
59
+ end
60
+ end
61
+
62
+ cell.addSubview(field)
63
+ field
64
+ end
65
+
66
+ def on_select(tableView, tableViewDelegate)
67
+ field.becomeFirstResponder
68
+ end
69
+
70
+ def dismissKeyboard
71
+ field.resignFirstResponder
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -3,6 +3,9 @@ module Formotion
3
3
  PROPERTIES = [
4
4
  # Displayed in the section header row
5
5
  :title,
6
+ # Displayed below the entire section; good for giving
7
+ # detailed information regarding the section.
8
+ :footer,
6
9
  # Arranges the section as a 'radio' section,
7
10
  # such that only one row can be checked at a time.
8
11
  :select_one,
@@ -1,3 +1,3 @@
1
1
  module Formotion
2
- VERSION = "0.0.3"
2
+ VERSION = "0.5"
3
3
  end
@@ -56,8 +56,9 @@ describe "Forms" do
56
56
  it "render works correctly" do
57
57
  @form = Formotion::Form.new(sections: [{
58
58
  rows: [{
59
- key: :email,
60
- editable: true,
59
+ key: :email,
60
+ type: :email,
61
+ editable: true,
61
62
  title: 'Email'
62
63
  }]}])
63
64
 
@@ -74,13 +75,13 @@ describe "Forms" do
74
75
  rows: [{
75
76
  title: "Email",
76
77
  placeholder: "me@mail.com",
77
- type: Formotion::RowType::EMAIL,
78
+ type: :email,
78
79
  auto_correction: UITextAutocorrectionTypeNo,
79
80
  auto_capitalization: UITextAutocapitalizationTypeNone
80
81
  }, {
81
82
  title: "Password",
82
83
  placeholder: "required",
83
- type: Formotion::RowType::STRING,
84
+ type: :string,
84
85
  secure: true
85
86
  }, {
86
87
  title: "Remember me?",
@@ -0,0 +1,70 @@
1
+ describe "FormController/StringRow" do
2
+ tests Formotion::FormController
3
+
4
+ def controller
5
+ make_row_settings = lambda { |index|
6
+ {
7
+ title: "String#{index == 0 ? '' : ' ' + index.to_s}",
8
+ key: :string,
9
+ type: :string
10
+ }
11
+ }
12
+ @form ||= Formotion::Form.new(
13
+ sections: [{
14
+ rows:(0..20).collect {|i|
15
+ make_row_settings.call(i)
16
+ }
17
+ }])
18
+
19
+ @controller ||= Formotion::FormController.alloc.initWithForm(@form)
20
+ end
21
+
22
+ def string_row
23
+ @form.sections.first.rows.first
24
+ end
25
+
26
+ after do
27
+ (@active_row || string_row).text_field.resignFirstResponder
28
+ wait 1 do
29
+ @active_row = nil
30
+ end
31
+ end
32
+
33
+ it "should pop open the keyboard when tapped" do
34
+ @notif = App.notification_center.observe UIKeyboardDidShowNotification do |notification|
35
+ @did_show = true
36
+ end
37
+ tap("String")
38
+ # wait for keyboard to actually pop up
39
+ wait 1 do
40
+ @did_show.should == true
41
+ App.notification_center.unobserve @notif
42
+ end
43
+ end
44
+
45
+ it "should have new value after typing" do
46
+ tap("String")
47
+ string_row.text_field.setText("Hello")
48
+ string_row.text_field.resignFirstResponder
49
+ string_row.value.should == "Hello"
50
+ end
51
+
52
+ it "should scroll to correct offset when tapping" do
53
+ @notif = App.notification_center.observe UIKeyboardDidShowNotification do |notification|
54
+ @key_rect = notification.userInfo[UIKeyboardFrameBeginUserInfoKey].CGRectValue
55
+ end
56
+
57
+ tap("String 9")
58
+ wait 1 do
59
+ table_view = self.controller.tableView
60
+ index_path = NSIndexPath.indexPathForRow(9, inSection: 0)
61
+ row_rect = table_view.convertRect(table_view.rectForRowAtIndexPath(index_path), toView: nil)
62
+
63
+ bottom_y = row_rect.origin.y + row_rect.size.height
64
+ bottom_y.should == @key_rect.origin.y - @key_rect.size.height
65
+ App.notification_center.unobserve @notif
66
+ @key_rect = nil
67
+ @active_row = @form.sections.first.rows[9]
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,42 @@
1
+ describe "FormController/CheckRow" do
2
+ tests Formotion::FormController
3
+
4
+ # By default, `tests` uses @controller.init
5
+ # this isn't ideal for our case, so override.
6
+ def controller
7
+ @form ||= Formotion::Form.new(
8
+ sections: [{
9
+ title: "Select One",
10
+ key: :account_type,
11
+ select_one: true,
12
+ rows: [{
13
+ title: "A",
14
+ key: :a,
15
+ type: :check,
16
+ value: true
17
+ }, {
18
+ title: "B",
19
+ key: :b,
20
+ type: :check,
21
+ }, {
22
+ title: "C",
23
+ key: :c,
24
+ type: :check,
25
+ }]
26
+ }])
27
+
28
+ @controller ||= Formotion::FormController.alloc.initWithForm(@form)
29
+ end
30
+
31
+ it "should leave only one row checked" do
32
+ rows = ["A", "B", "C"]
33
+ rows.each_with_index {|letter, i|
34
+ tap letter
35
+ @form.sections[0].rows.each_with_index {|row, index|
36
+ (!!(row.value)).should == (index == i)
37
+ }
38
+ wait 1 do
39
+ end
40
+ }
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ describe "FormController/DateRow" do
2
+ tests Formotion::FormController
3
+
4
+ # By default, `tests` uses @controller.init
5
+ # this isn't ideal for our case, so override.
6
+ def controller
7
+ row_settings = {
8
+ title: "Date",
9
+ key: :date,
10
+ type: :date,
11
+ value: 1341273600
12
+ }
13
+ @form ||= Formotion::Form.new(
14
+ sections: [{
15
+ rows:[row_settings]
16
+ }])
17
+
18
+ @controller ||= Formotion::FormController.alloc.initWithForm(@form)
19
+ end
20
+
21
+ def date_row
22
+ @form.sections.first.rows.first
23
+ end
24
+
25
+ def picker
26
+ date_row.object.picker
27
+ end
28
+
29
+ after do
30
+ date_row.text_field.resignFirstResponder
31
+ wait 1 do
32
+ end
33
+ end
34
+
35
+ it "should open a the picker when tapped" do
36
+ notif = App.notification_center.observe UIKeyboardDidShowNotification do |notification|
37
+ @did_show = true
38
+ end
39
+
40
+ picker.superview.should == nil
41
+ tap("Date")
42
+ picker.superview.should.not == nil
43
+
44
+ wait 1 do
45
+ @did_show.should == true
46
+ end
47
+ end
48
+
49
+ it "should change row value when picked" do
50
+ tap("Date")
51
+
52
+ wait 1 do
53
+ # RubyMotion has some memory crashes if you don't make this an ivar/retain it.
54
+ @march_26 = 701568000
55
+ new_date = NSDate.dateWithTimeIntervalSince1970(@march_26).retain
56
+ picker.setDate(new_date, animated: true)
57
+ wait 1 do
58
+ picker.sendActionsForControlEvents(UIControlEventValueChanged)
59
+ self.date_row.value.should == @march_26
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,82 @@
1
+ describe "FormController/ImageRow" do
2
+ tests Formotion::FormController
3
+
4
+ # By default, `tests` uses @controller.init
5
+ # this isn't ideal for our case, so override.
6
+ def controller
7
+ row_settings = {
8
+ title: "Photo",
9
+ key: :photo,
10
+ type: :image
11
+ }
12
+ @form ||= Formotion::Form.new(
13
+ sections: [{
14
+ rows:[row_settings]
15
+ }])
16
+
17
+ @controller ||= Formotion::FormController.alloc.initWithForm(@form)
18
+ end
19
+
20
+ def image_row
21
+ @form.sections.first.rows.first
22
+ end
23
+
24
+ def action_sheet
25
+ image_row.object.instance_variable_get("@action_sheet")
26
+ end
27
+
28
+ # Looks like view(label) doesn't scan the UIActionSheet properly, so we override it.
29
+ def tap_action_sheet(label)
30
+ choose = self.action_sheet.send(:_viewByName, label)
31
+ tap(choose)
32
+ end
33
+
34
+ # if action_sheet exists, just cancel it immediately for the next test.
35
+ after do
36
+ action_sheet && action_sheet.dismissWithClickedButtonIndex(action_sheet.cancelButtonIndex, animated: false)
37
+ end
38
+
39
+ it "should open an action sheet when tapped" do
40
+ tap("Photo")
41
+ action_sheet.nil?.should == false
42
+ end
43
+
44
+ it "should open photo library when 'choose' tapped" do
45
+ tap("Photo")
46
+
47
+ tap_action_sheet("Choose")
48
+ view("Photos").nil?.should == false
49
+ tap("Cancel")
50
+ end
51
+
52
+ it "should have value after selecting photo" do
53
+ tap("Photo")
54
+
55
+ # Looks like view(label) doesn't scan the UIActionSheet properly, so we override it.
56
+ tap_action_sheet("Choose")
57
+
58
+ image_controller = controller.modalViewController.visibleViewController
59
+
60
+ image = UIImage.alloc.init
61
+ info = { UIImagePickerControllerMediaType => KUTTypeImage,
62
+ UIImagePickerControllerOriginalImage => image,
63
+ UIImagePickerControllerMediaURL => NSURL.alloc.init}
64
+ image_row.object.instance_variable_get("@camera").imagePickerController(image_controller, didFinishPickingMediaWithInfo: info)
65
+
66
+ image_row.value.should == image
67
+ tap("Cancel")
68
+ end
69
+
70
+ it "should be able to delete value" do
71
+ image = UIImage.alloc.init
72
+ image_row.value = image
73
+ image_row.rowHeight.should > 44
74
+
75
+ tap("Photo")
76
+
77
+ # Looks like view(label) doesn't scan the UIActionSheet properly, so we override it.
78
+ tap_action_sheet("Delete")
79
+ image_row.rowHeight.should == 44
80
+ image_row.value.should == nil
81
+ end
82
+ end
@@ -0,0 +1,27 @@
1
+ describe "FormController/OptionsRow" do
2
+ tests Formotion::FormController
3
+
4
+ # By default, `tests` uses @controller.init
5
+ # this isn't ideal for our case, so override.
6
+ def controller
7
+ row_settings = {
8
+ title: "Options",
9
+ key: :options,
10
+ type: :options,
11
+ items: ['One', 'Two']
12
+ }
13
+ @form ||= Formotion::Form.new(
14
+ sections: [{
15
+ rows:[row_settings]
16
+ }])
17
+
18
+ @controller ||= Formotion::FormController.alloc.initWithForm(@form)
19
+ end
20
+
21
+ it "should change row value when selecting segment" do
22
+ tap "One"
23
+ @form.sections[0].rows[0].value.should == "One"
24
+ tap "Two"
25
+ @form.sections[0].rows[0].value.should == "Two"
26
+ end
27
+ end