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.
- data/.gitignore +2 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -0
- data/LIST_OF_ROW_TYPES.md +163 -0
- data/NEW_ROW_TYPES.md +73 -0
- data/README.md +11 -14
- data/Rakefile +6 -0
- data/app/app_delegate.rb +22 -0
- data/examples/KitchenSink/Rakefile +1 -1
- data/examples/KitchenSink/app/app_delegate.rb +50 -2
- data/examples/Login/.gitignore +5 -0
- data/examples/Login/Rakefile +9 -0
- data/examples/Login/app/app_delegate.rb +16 -0
- data/examples/Login/app/login_controller.rb +47 -0
- data/examples/Login/app/user_controller.rb +38 -0
- data/examples/Login/spec/main_spec.rb +9 -0
- data/lib/formotion.rb +7 -1
- data/lib/formotion/{form.rb → form/form.rb} +0 -0
- data/lib/formotion/{form_delegate.rb → form/form_delegate.rb} +10 -15
- data/lib/formotion/patch/object.rb +9 -0
- data/lib/formotion/patch/ui_action_sheet.rb +27 -0
- data/lib/formotion/patch/ui_text_view.rb +122 -0
- data/lib/formotion/patch/ui_text_view_placeholder.rb +65 -0
- data/lib/formotion/{row.rb → row/row.rb} +37 -27
- data/lib/formotion/row/row_cell_builder.rb +28 -0
- data/lib/formotion/row_type/base.rb +41 -0
- data/lib/formotion/row_type/check_row.rb +30 -0
- data/lib/formotion/row_type/date_row.rb +61 -0
- data/lib/formotion/row_type/email_row.rb +11 -0
- data/lib/formotion/row_type/image_row.rb +99 -0
- data/lib/formotion/row_type/number_row.rb +11 -0
- data/lib/formotion/row_type/options_row.rb +24 -0
- data/lib/formotion/row_type/phone_row.rb +11 -0
- data/lib/formotion/row_type/row_type.rb +21 -0
- data/lib/formotion/row_type/slider_row.rb +43 -0
- data/lib/formotion/row_type/static_row.rb +6 -0
- data/lib/formotion/row_type/string_row.rb +112 -0
- data/lib/formotion/row_type/submit_row.rb +34 -0
- data/lib/formotion/row_type/switch_row.rb +19 -0
- data/lib/formotion/row_type/text_row.rb +76 -0
- data/lib/formotion/{section.rb → section/section.rb} +3 -0
- data/lib/formotion/version.rb +1 -1
- data/spec/form_spec.rb +5 -4
- data/spec/functional/character_spec.rb +70 -0
- data/spec/functional/check_row_spec.rb +42 -0
- data/spec/functional/date_row_spec.rb +63 -0
- data/spec/functional/image_row_spec.rb +82 -0
- data/spec/functional/options_row_spec.rb +27 -0
- data/spec/functional/slider_row_spec.rb +27 -0
- data/spec/functional/submit_row_spec.rb +30 -0
- data/spec/functional/switch_row_spec.rb +28 -0
- data/spec/functional/text_row_spec.rb +60 -0
- data/spec/row_type/check_spec.rb +27 -0
- data/spec/row_type/date_spec.rb +69 -0
- data/spec/row_type/email_spec.rb +22 -0
- data/spec/row_type/image_spec.rb +43 -0
- data/spec/row_type/number_spec.rb +22 -0
- data/spec/row_type/options_spec.rb +37 -0
- data/spec/row_type/phone_spec.rb +22 -0
- data/spec/row_type/slider_spec.rb +47 -0
- data/spec/row_type/static_spec.rb +15 -0
- data/spec/row_type/string_spec.rb +52 -0
- data/spec/row_type/submit_spec.rb +28 -0
- data/spec/row_type/switch_spec.rb +27 -0
- data/spec/row_type/text_spec.rb +41 -0
- data/spec/support/ui_control_wrap_extension.rb +7 -0
- metadata +88 -9
- data/lib/formotion/row_cell_builder.rb +0 -168
- 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,
|
data/lib/formotion/version.rb
CHANGED
data/spec/form_spec.rb
CHANGED
@@ -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
|
-
|
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:
|
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:
|
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
|