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