formotion 0.0.3 → 0.5

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