From a5a993ab84b807c64c2950b380864e5815e4d37a Mon Sep 17 00:00:00 2001 From: mordaroso Date: Wed, 27 Jun 2012 15:26:42 +0200 Subject: [PATCH 01/10] use bundler for dependencies --- .gitignore | 3 ++- Gemfile | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Gemfile diff --git a/.gitignore b/.gitignore index fdf1ba9..b79da83 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ resources/*.storyboardc *.gem .bundle Gemfile.lock -pkg/* \ No newline at end of file +pkg/* +.rvmrc \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..8f33266 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in motion-settings.gemspec +gemspec \ No newline at end of file From 4495c2c59a41d9b93efe30141eb7bddb0c92a707 Mon Sep 17 00:00:00 2001 From: mordaroso Date: Wed, 27 Jun 2012 15:30:42 +0200 Subject: [PATCH 02/10] refactor row types and move them into separate classes --- lib/formotion/form_delegate.rb | 16 +-- lib/formotion/row.rb | 42 ++++---- lib/formotion/row_cell_builder.rb | 144 +-------------------------- lib/formotion/row_type.rb | 24 ++--- lib/formotion/row_type/base.rb | 28 ++++++ lib/formotion/row_type/character.rb | 105 +++++++++++++++++++ lib/formotion/row_type/check_row.rb | 28 ++++++ lib/formotion/row_type/email_row.rb | 11 ++ lib/formotion/row_type/number_row.rb | 11 ++ lib/formotion/row_type/phone_row.rb | 11 ++ lib/formotion/row_type/static_row.rb | 6 ++ lib/formotion/row_type/string_row.rb | 6 ++ lib/formotion/row_type/submit_row.rb | 35 +++++++ lib/formotion/row_type/switch_row.rb | 22 ++++ spec/form_spec.rb | 9 +- 15 files changed, 294 insertions(+), 204 deletions(-) create mode 100644 lib/formotion/row_type/base.rb create mode 100644 lib/formotion/row_type/character.rb create mode 100644 lib/formotion/row_type/check_row.rb create mode 100644 lib/formotion/row_type/email_row.rb create mode 100644 lib/formotion/row_type/number_row.rb create mode 100644 lib/formotion/row_type/phone_row.rb create mode 100644 lib/formotion/row_type/static_row.rb create mode 100644 lib/formotion/row_type/string_row.rb create mode 100644 lib/formotion/row_type/submit_row.rb create mode 100644 lib/formotion/row_type/switch_row.rb diff --git a/lib/formotion/form_delegate.rb b/lib/formotion/form_delegate.rb index 380f90b..cd88529 100644 --- a/lib/formotion/form_delegate.rb +++ b/lib/formotion/form_delegate.rb @@ -67,21 +67,7 @@ def tableView(tableView, cellForRowAtIndexPath:indexPath) def tableView(tableView, didSelectRowAtIndexPath:indexPath) tableView.deselectRowAtIndexPath(indexPath, animated:true) row = row_for_index_path(indexPath) - if row.submit_button? - self.submit - elsif row.checkable? - if row.section.select_one and !row.value - row.section.rows.each {|other_row| - other_row.value = (other_row == row) - Formotion::RowCellBuilder.make_check_cell(other_row, tableView.cellForRowAtIndexPath(other_row.index_path)) - } - elsif !row.section.select_one - row.value = !row.value - Formotion::RowCellBuilder.make_check_cell(row, tableView.cellForRowAtIndexPath(row.index_path)) - end - elsif row.editable? - row.text_field.becomeFirstResponder - end + row.object.on_select(tableView, self) end end end \ No newline at end of file diff --git a/lib/formotion/row.rb b/lib/formotion/row.rb index fa431ae..65a5233 100644 --- a/lib/formotion/row.rb +++ b/lib/formotion/row.rb @@ -6,7 +6,7 @@ class Row < Formotion::Base # the user's (or configured) value for this row. :value, # set as cell.titleLabel.text - :title, + :title, # set as cell.detailLabel.text :subtitle, # configures the type of input this is (string, phone, switch, etc) @@ -19,7 +19,7 @@ class Row < Formotion::Base # placeholder text :placeholder, # whether or not the entry field is secure (like a password) - :secure, + :secure, # given by a UIReturnKey___ integer, string, or symbol # EX :default, :google :return_key, @@ -62,6 +62,9 @@ class Row < Formotion::Base # starts editing #text_field. attr_accessor :on_begin_callback + # row type object + attr_accessor :object + def initialize(params = {}) super @@ -74,7 +77,7 @@ def initialize(params = {}) # these should be done with alias_method but there's currently a bug # in RM which messes up attr_accessors with alias_method # EX - # row.editable? + # row.secure? # => true # row.checkable? # => nil @@ -103,7 +106,7 @@ def reuse_identifier def next_row # if there are more rows in this section, use that. - return self.section.rows[self.index + 1] if self.index < (self.section.rows.count - 1) + return self.section.rows[self.index + 1] if self.index < (self.section.rows.count - 1) # if there are more sections, then use the first row of that section. return self.section.next_section.rows[0] if self.section.next_section @@ -112,7 +115,7 @@ def next_row end def previous_row - return self.section.rows[self.index - 1] if self.index > 0 + return self.section.rows[self.index - 1] if self.index > 0 # if there are more sections, then use the first row of that section. return self.section.previous_section.rows[-1] if self.section.previous_section @@ -120,41 +123,30 @@ def previous_row nil end - def editable? - Formotion::RowType::TEXT_FIELD_TYPES.member? self.type - end - def submit_button? - self.type == Formotion::RowType::SUBMIT - end - - def switchable? - self.type == Formotion::RowType::SWITCH - end - - def checkable? - self.type == Formotion::RowType::CHECK + object.submit_button? end ######################### # setter overrides def type=(type) - @type = Formotion::RowType.for(type) + @object = Formotion::RowType.for(type).new(self) + @type = type end def return_key=(value) @return_key = const_int_get("UIReturnKey", value) end - def auto_correction=(value) + def auto_correction=(value) @auto_correction = const_int_get("UITextAutocorrectionType", value) end - def auto_capitalization=(value) + def auto_capitalization=(value) @auto_capitalization = const_int_get("UITextAutocapitalizationType", value) end - def clear_button=(value) + def clear_button=(value) @clear_button = const_int_get("UITextFieldViewMode", value) end @@ -197,12 +189,12 @@ def const_int_get(base, value) # directly in your code, they don't get added # to Kernel and const_int_get crashes. def load_constants_hack - [UITextAutocapitalizationTypeNone, UITextAutocapitalizationTypeWords, + [UITextAutocapitalizationTypeNone, UITextAutocapitalizationTypeWords, UITextAutocapitalizationTypeSentences,UITextAutocapitalizationTypeAllCharacters, UITextAutocorrectionTypeNo, UITextAutocorrectionTypeYes, UITextAutocorrectionTypeDefault, - UIReturnKeyDefault, UIReturnKeyGo, UIReturnKeyGoogle, UIReturnKeyJoin, + UIReturnKeyDefault, UIReturnKeyGo, UIReturnKeyGoogle, UIReturnKeyJoin, UIReturnKeyNext, UIReturnKeyRoute, UIReturnKeySearch, UIReturnKeySend, - UIReturnKeyYahoo, UIReturnKeyDone, UIReturnKeyEmergencyCall, + UIReturnKeyYahoo, UIReturnKeyDone, UIReturnKeyEmergencyCall, UITextFieldViewModeNever, UITextFieldViewModeAlways, UITextFieldViewModeWhileEditing, UITextFieldViewModeUnlessEditing ] diff --git a/lib/formotion/row_cell_builder.rb b/lib/formotion/row_cell_builder.rb index cf33158..debd2b0 100644 --- a/lib/formotion/row_cell_builder.rb +++ b/lib/formotion/row_cell_builder.rb @@ -7,9 +7,6 @@ ################# module Formotion class RowCellBuilder - # The new UITextField in a UITableViewCell - # will be assigned this tag, if applicable. - TEXT_FIELD_TAG=1000 # PARAMS row.is_a? Formotion::Row # RETURNS [cell configured to that row, a UITextField for that row if applicable or nil] @@ -22,147 +19,10 @@ def self.make_cell(row) cell.textLabel.text = row.title cell.detailTextLabel.text = row.subtitle - if row.submit_button? - make_submit_cell(row, cell) - elsif row.switchable? - make_switch_cell(row, cell) - elsif row.checkable? - make_check_cell(row, cell) - elsif row.editable? - text_field = make_text_field(row, cell) - end - [cell, text_field] - end - - # This is actually called whenever again cell is checked/unchecked - # in the UITableViewDelegate callbacks. So (for now) don't - # instantiate long-lived objects in them. - # Maybe that logic should be moved elsewhere? - def self.make_check_cell(row, cell) - cell.accessoryType = row.value ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone - end - - def self.make_switch_cell(row, cell) - cell.selectionStyle = UITableViewCellSelectionStyleNone - switchView = UISwitch.alloc.initWithFrame(CGRectZero) - cell.accessoryView = switchView - switchView.setOn(row.value || false, animated:false) - switchView.when(UIControlEventValueChanged) do - row.value = switchView.isOn - end - end + edit_field = row.object.build_cell(cell) - # Does a clever little trick to override #layoutSubviews - # for just this one UITableViewCell object, in order to - # center it's labels horizontally. - def self.make_submit_cell(row, cell) - cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) - cell.instance_eval do - def layoutSubviews - old_layoutSubviews - - center = lambda {|frame, dimen| - ((self.frame.size.send(dimen) - frame.size.send(dimen)) / 2.0) - } - - self.textLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.textLabel.center.y) - self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.detailTextLabel.center.y) - end - end + [cell, edit_field] end - # Configures the cell to have a new UITextField - # which is used to enter data. Consists of - # 1) setting up that field with the appropriate properties - # specified by `row` 2) configures the callbacks on the field - # to call any callbacks `row` listens for. - # Also does the layoutSubviews swizzle trick - # to size the UITextField so it won't bump into the titleLabel. - def self.make_text_field(row, cell) - field = UITextField.alloc.initWithFrame(CGRectZero) - field.tag = TEXT_FIELD_TAG - - field.placeholder = row.placeholder - field.text = row.value - - field.clearButtonMode = UITextFieldViewModeWhileEditing - field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter - field.textAlignment = UITextAlignmentRight - - case row.type - when RowType::EMAIL - field.keyboardType = UIKeyboardTypeEmailAddress - when RowType::PHONE - field.keyboardType = UIKeyboardTypePhonePad - when RowType::NUMBER - field.keyboardType = UIKeyboardTypeDecimalPad - else - field.keyboardType = UIKeyboardTypeDefault - end - - field.secureTextEntry = true if row.secure? - field.returnKeyType = row.return_key || UIReturnKeyNext - field.autocapitalizationType = row.auto_capitalization if row.auto_capitalization - field.autocorrectionType = row.auto_correction if row.auto_correction - field.clearButtonMode = row.clear_button || UITextFieldViewModeWhileEditing - - if row.on_enter_callback - field.should_return? do |text_field| - if row.on_enter_callback.arity == 0 - row.on_enter_callback.call - elsif row.on_enter_callback.arity == 1 - row.on_enter_callback.call(row) - end - false - end - elsif field.returnKeyType == UIReturnKeyDone - field.should_return? do |text_field| - text_field.resignFirstResponder - false - end - else - field.should_return? do |text_field| - if row.next_row && row.next_row.text_field - row.next_row.text_field.becomeFirstResponder - else - text_field.resignFirstResponder - end - true - end - end - - field.on_begin do |text_field| - row.on_begin_callback && row.on_begin_callback.call - end - - field.should_begin? do |text_field| - row.section.form.active_row = row - true - end - - field.on_change do |text_field| - row.value = text_field.text - end - - cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) - cell.instance_eval do - def layoutSubviews - old_layoutSubviews - - # viewWithTag is terrible, but I think it's ok to use here... - formotion_field = self.viewWithTag(Formotion::RowCellBuilder::TEXT_FIELD_TAG) - formotion_field.sizeToFit - - field_frame = formotion_field.frame - field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20 - field_frame.origin.y = ((self.frame.size.height - field_frame.size.height) / 2.0).round - field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20 - formotion_field.frame = field_frame - end - end - - cell.addSubview(field) - field - end end end \ No newline at end of file diff --git a/lib/formotion/row_type.rb b/lib/formotion/row_type.rb index fa81696..b40cb0d 100644 --- a/lib/formotion/row_type.rb +++ b/lib/formotion/row_type.rb @@ -1,29 +1,17 @@ module Formotion - class RowType - STRING=0 - EMAIL=1 - PHONE=2 - NUMBER=3 - SUBMIT=4 - SWITCH=5 - CHECK=6 - STATIC=100 - - TYPES = [STRING, EMAIL, PHONE, NUMBER, SUBMIT, SWITCH, CHECK, STATIC] - TEXT_FIELD_TYPES=[STRING, EMAIL, PHONE, NUMBER] + module RowType + ROW_TYPES = Formotion::RowType.constants(false).select { |constant_name| constant_name =~ /Row$/ } class << self - def for(string_or_sym_or_int) - type = string_or_sym_or_int + def for(string_or_sym) + type = string_or_sym if type.is_a?(Symbol) or type.is_a? String - string = type.to_s.upcase + string = "#{type.to_s.downcase}_row".camelize if not const_defined? string - raise Formotion::InvalidClassError, "Invalid RowType value #{string_or_sym}" + raise Formotion::InvalidClassError, "Invalid RowType value #{string_or_sym}. Create a class called #{string}" end Formotion::RowType.const_get(string) - elsif type.is_a? Integer and TYPES.member? type - TYPES[type] else raise Formotion::InvalidClassError, "Attempted row type #{type.inspect} is not a valid RowType." end diff --git a/lib/formotion/row_type/base.rb b/lib/formotion/row_type/base.rb new file mode 100644 index 0000000..613388c --- /dev/null +++ b/lib/formotion/row_type/base.rb @@ -0,0 +1,28 @@ +module Formotion + module RowType + class Base + attr_accessor :row + + def initialize(row) + @row = row + end + + def submit_button? + false + end + + # builder method for row cell specific implementation + def build_cell(cell) + # implement in row class + nil + end + + # method gets triggered when tableView(tableView, didSelectRowAtIndexPath:indexPath) + # in UITableViewDelegate is executed + def on_select(tableView, tableViewDelegate) + # implement in row class + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/character.rb b/lib/formotion/row_type/character.rb new file mode 100644 index 0000000..a2d84a6 --- /dev/null +++ b/lib/formotion/row_type/character.rb @@ -0,0 +1,105 @@ +module Formotion + module RowType + class Character < Base + + # The new UITextField in a UITableViewCell + # will be assigned this tag, if applicable. + TEXT_FIELD_TAG=1000 + + def keyboardType + UIKeyboardTypeDefault + end + + # Configures the cell to have a new UITextField + # which is used to enter data. Consists of + # 1) setting up that field with the appropriate properties + # specified by `row` 2) configures the callbacks on the field + # to call any callbacks `row` listens for. + # Also does the layoutSubviews swizzle trick + # to size the UITextField so it won't bump into the titleLabel. + def build_cell(cell) + field = UITextField.alloc.initWithFrame(CGRectZero) + field.tag = TEXT_FIELD_TAG + + field.placeholder = row.placeholder + field.text = row.value + + field.clearButtonMode = UITextFieldViewModeWhileEditing + field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter + field.textAlignment = UITextAlignmentRight + + field.keyboardType = keyboardType + + field.secureTextEntry = true if row.secure? + field.returnKeyType = row.return_key || UIReturnKeyNext + field.autocapitalizationType = row.auto_capitalization if row.auto_capitalization + field.autocorrectionType = row.auto_correction if row.auto_correction + field.clearButtonMode = row.clear_button || UITextFieldViewModeWhileEditing + + if row.on_enter_callback + field.should_return? do |text_field| + if row.on_enter_callback.arity == 0 + row.on_enter_callback.call + elsif row.on_enter_callback.arity == 1 + row.on_enter_callback.call(row) + end + false + end + elsif field.returnKeyType == UIReturnKeyDone + field.should_return? do |text_field| + text_field.resignFirstResponder + false + end + else + field.should_return? do |text_field| + if row.next_row && row.next_row.text_field + row.next_row.text_field.becomeFirstResponder + else + text_field.resignFirstResponder + end + true + end + end + + field.on_begin do |text_field| + row.on_begin_callback && row.on_begin_callback.call + end + + field.should_begin? do |text_field| + row.section.form.active_row = row + true + end + + field.on_change do |text_field| + row.value = text_field.text + end + + cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) + cell.instance_eval do + def layoutSubviews + old_layoutSubviews + + # viewWithTag is terrible, but I think it's ok to use here... + formotion_field = self.viewWithTag(TEXT_FIELD_TAG) + formotion_field.sizeToFit + + field_frame = formotion_field.frame + field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20 + field_frame.origin.y = ((self.frame.size.height - field_frame.size.height) / 2.0).round + field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20 + formotion_field.frame = field_frame + end + end + + cell.addSubview(field) + field + + end + + def on_select(tableView, tableViewDelegate) + row.text_field.becomeFirstResponder + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/check_row.rb b/lib/formotion/row_type/check_row.rb new file mode 100644 index 0000000..f3a1349 --- /dev/null +++ b/lib/formotion/row_type/check_row.rb @@ -0,0 +1,28 @@ +module Formotion + module RowType + class CheckRow < Base + + # This is actually called whenever again cell is checked/unchecked + # in the UITableViewDelegate callbacks. So (for now) don't + # instantiate long-lived objects in them. + # Maybe that logic should be moved elsewhere? + def build_cell(cell) + cell.accessoryType = row.value ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone + nil + end + + def on_select(tableView, tableViewDelegate) + if row.section.select_one and !row.value + row.section.rows.each {|other_row| + other_row.value = (other_row == row) + other_row.object.build_cell(tableView.cellForRowAtIndexPath(other_row.index_path)) + } + elsif !row.section.select_one + row.value = !row.value + build_cell(tableView.cellForRowAtIndexPath(row.index_path)) + end + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/email_row.rb b/lib/formotion/row_type/email_row.rb new file mode 100644 index 0000000..7d72f9d --- /dev/null +++ b/lib/formotion/row_type/email_row.rb @@ -0,0 +1,11 @@ +module Formotion + module RowType + class EmailRow < Character + + def keyboardType + UIKeyboardTypeEmailAddress + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/number_row.rb b/lib/formotion/row_type/number_row.rb new file mode 100644 index 0000000..4ac6206 --- /dev/null +++ b/lib/formotion/row_type/number_row.rb @@ -0,0 +1,11 @@ +module Formotion + module RowType + class NumberRow < Character + + def keyboardType + UIKeyboardTypeDecimalPad + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/phone_row.rb b/lib/formotion/row_type/phone_row.rb new file mode 100644 index 0000000..1785f65 --- /dev/null +++ b/lib/formotion/row_type/phone_row.rb @@ -0,0 +1,11 @@ +module Formotion + module RowType + class PhoneRow < Character + + def keyboardType + UIKeyboardTypePhonePad + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/static_row.rb b/lib/formotion/row_type/static_row.rb new file mode 100644 index 0000000..5a01856 --- /dev/null +++ b/lib/formotion/row_type/static_row.rb @@ -0,0 +1,6 @@ +module Formotion + module RowType + class StaticRow < Base + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/string_row.rb b/lib/formotion/row_type/string_row.rb new file mode 100644 index 0000000..0dced7f --- /dev/null +++ b/lib/formotion/row_type/string_row.rb @@ -0,0 +1,6 @@ +module Formotion + module RowType + class StringRow < Character + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/submit_row.rb b/lib/formotion/row_type/submit_row.rb new file mode 100644 index 0000000..839fe55 --- /dev/null +++ b/lib/formotion/row_type/submit_row.rb @@ -0,0 +1,35 @@ +module Formotion + module RowType + class SubmitRow < Base + + def submit_button? + true + end + + # Does a clever little trick to override #layoutSubviews + # for just this one UITableViewCell object, in order to + # center it's labels horizontally. + def build_cell(cell) + cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) + cell.instance_eval do + def layoutSubviews + old_layoutSubviews + + center = lambda {|frame, dimen| + ((self.frame.size.send(dimen) - frame.size.send(dimen)) / 2.0) + } + + self.textLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.textLabel.center.y) + self.detailTextLabel.center = CGPointMake(self.frame.size.width / 2 - 10, self.detailTextLabel.center.y) + end + end + nil + end + + def on_select(tableView, tableViewDelegate) + tableViewDelegate.submit + end + + end + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/switch_row.rb b/lib/formotion/row_type/switch_row.rb new file mode 100644 index 0000000..d11222d --- /dev/null +++ b/lib/formotion/row_type/switch_row.rb @@ -0,0 +1,22 @@ +module Formotion + module RowType + class SwitchRow < Base + + def switchable + true + end + + def build_cell(cell) + cell.selectionStyle = UITableViewCellSelectionStyleNone + switchView = UISwitch.alloc.initWithFrame(CGRectZero) + cell.accessoryView = switchView + switchView.setOn(row.value || false, animated:false) + switchView.when(UIControlEventValueChanged) do + row.value = switchView.isOn + end + nil + end + + end + end +end \ No newline at end of file diff --git a/spec/form_spec.rb b/spec/form_spec.rb index cf76882..b2f2a97 100644 --- a/spec/form_spec.rb +++ b/spec/form_spec.rb @@ -56,8 +56,9 @@ it "render works correctly" do @form = Formotion::Form.new(sections: [{ rows: [{ - key: :email, - editable: true, + key: :email, + type: :email, + editable: true, title: 'Email' }]}]) @@ -74,13 +75,13 @@ rows: [{ title: "Email", placeholder: "me@mail.com", - type: Formotion::RowType::EMAIL, + type: :email, auto_correction: UITextAutocorrectionTypeNo, auto_capitalization: UITextAutocapitalizationTypeNone }, { title: "Password", placeholder: "required", - type: Formotion::RowType::STRING, + type: :string, secure: true }, { title: "Remember me?", From 3e2631d3b0d1c91f8b80e0fe8b05dfa601d957b8 Mon Sep 17 00:00:00 2001 From: mordaroso Date: Thu, 28 Jun 2012 11:08:18 +0200 Subject: [PATCH 03/10] add text view as row type and add rowHeight as a row parameter --- app/app_delegate.rb | 6 ++ lib/formotion/form_delegate.rb | 5 ++ lib/formotion/patch/ui_text_view.rb | 129 ++++++++++++++++++++++++++++ lib/formotion/row.rb | 7 +- lib/formotion/row_type/base.rb | 2 +- lib/formotion/row_type/check_row.rb | 8 +- lib/formotion/row_type/text_row.rb | 69 +++++++++++++++ 7 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 lib/formotion/patch/ui_text_view.rb create mode 100644 lib/formotion/row_type/text_row.rb diff --git a/app/app_delegate.rb b/app/app_delegate.rb index 19f4c79..459e8c2 100644 --- a/app/app_delegate.rb +++ b/app/app_delegate.rb @@ -29,6 +29,12 @@ def application(application, didFinishLaunchingWithOptions:launchOptions) title: "Switch", key: :switch, type: :switch, + }, { + title: "Bio", + key: :bio, + type: :text, + placeholder: "Enter your Bio here...", + rowHeight: 100 }] }, { title: "Account Type", diff --git a/lib/formotion/form_delegate.rb b/lib/formotion/form_delegate.rb index cd88529..b2e9da7 100644 --- a/lib/formotion/form_delegate.rb +++ b/lib/formotion/form_delegate.rb @@ -63,6 +63,11 @@ def tableView(tableView, cellForRowAtIndexPath:indexPath) cell end + def tableView(tableView, heightForRowAtIndexPath: indexPath) + row = row_for_index_path(indexPath) + row.rowHeight || tableView.rowHeight + end + # UITableViewDelegate Methods def tableView(tableView, didSelectRowAtIndexPath:indexPath) tableView.deselectRowAtIndexPath(indexPath, animated:true) diff --git a/lib/formotion/patch/ui_text_view.rb b/lib/formotion/patch/ui_text_view.rb new file mode 100644 index 0000000..05474a9 --- /dev/null +++ b/lib/formotion/patch/ui_text_view.rb @@ -0,0 +1,129 @@ +# Methods which use blocks for UITextViewDelegate methods. +# EX +# field.should_end? do |text_field| +# if text_field.text != "secret" +# return false +# end +# true +# end +# +# Also includes an on_change method, which calls after the text +# has changed (there is no UITextViewDelegate equivalent.) +# EX +# field.on_change do |text_field| +# p text_field.text +# end + +class UITextView + attr_accessor :menu_options_enabled + + def canPerformAction(action, withSender:sender) + self.menu_options_enabled + end + + # block takes argument textView; should return true/false + def should_begin?(&block) + add_delegate_method do + @delegate.textViewShouldBeginEditing_callback = block + end + end + + # block takes argument textView + def on_begin(&block) + add_delegate_method do + @delegate.textViewDidBeginEditing_callback = block + end + end + + # block takes argument textView; should return true/false + def should_end?(&block) + add_delegate_method do + @delegate.textViewShouldEndEditing_callback = block + end + end + + # block takes argument textView + def on_end(&block) + add_delegate_method do + @delegate.textViewDidBeginEditing_callback = block + end + end + + # block takes argument textView + def on_change(&block) + add_delegate_method do + @delegate.textViewDidChange_callback = block + end + end + + # block takes argument textView, range [NSRange], and string; should return true/false + def should_change?(&block) + add_delegate_method do + @delegate.shouldChangeCharactersInRange_callback = block + end + end + + private + def add_delegate_method + # create strong reference to the delegate + # (.delegate= only creates a weak reference) + @delegate ||= UITextView_Delegate.new + yield + self.delegate = @delegate + end +end + +class UITextView_Delegate + [:textViewShouldBeginEditing, :textViewDidBeginEditing, + :textViewShouldEndEditing, :textViewDidEndEditing, + :textViewDidChange, :shouldChangeCharactersInRange].each {|method| + attr_accessor (method.to_s + "_callback").to_sym + } + + # Called from + # [textView addTarget:block + # action:'call' + # forControlEvents:UIControlEventEditingChanged], + # NOT a UItextViewDelegate method. + attr_accessor :on_change_callback + + def textViewShouldBeginEditing(theTextView) + if self.textViewShouldBeginEditing_callback + return self.textViewShouldBeginEditing_callback.call(theTextView) + end + true + end + + def textViewDidBeginEditing(theTextView) + if self.textViewDidBeginEditing_callback + return self.textViewDidBeginEditing_callback.call(theTextView) + end + end + + def textViewShouldEndEditing(theTextView) + if self.textViewShouldEndEditing_callback + return self.textViewShouldEndEditing_callback.call(theTextView) + end + true + end + + def textView(textView, shouldChangeTextInRange:range, replacementText:text) + if self.shouldChangeCharactersInRange_callback + return self.shouldChangeCharactersInRange_callback.call(textView, range, text) + end + true + end + + def textViewDidEndEditing(theTextView) + if self.textViewDidEndEditing_callback + return self.textViewDidEndEditing_callback.call(theTextView) + end + end + + def textViewDidChange(theTextView) + if self.on_change_callback + self.on_change_callback.call(theTextView) + end + end + +end \ No newline at end of file diff --git a/lib/formotion/row.rb b/lib/formotion/row.rb index 65a5233..5bb6b11 100644 --- a/lib/formotion/row.rb +++ b/lib/formotion/row.rb @@ -32,7 +32,12 @@ class Row < Formotion::Base # field.clearButtonMode; given by a UITextFieldViewMode__ integer, string, symbol # EX :never, :while_editing # DEFAULT is nil, which is used as :while_editing - :clear_button] + :clear_button, + # row height as integer; used for heightForRowAtIndexPath + # EX 200 + # DEFAULT is nil, which is used as the tableView.rowHeight + :rowHeight, + ] PROPERTIES.each {|prop| attr_accessor prop } diff --git a/lib/formotion/row_type/base.rb b/lib/formotion/row_type/base.rb index 613388c..594fb8a 100644 --- a/lib/formotion/row_type/base.rb +++ b/lib/formotion/row_type/base.rb @@ -1,7 +1,7 @@ module Formotion module RowType class Base - attr_accessor :row + attr_accessor :row, :tableView def initialize(row) @row = row diff --git a/lib/formotion/row_type/check_row.rb b/lib/formotion/row_type/check_row.rb index f3a1349..d21ffee 100644 --- a/lib/formotion/row_type/check_row.rb +++ b/lib/formotion/row_type/check_row.rb @@ -13,10 +13,12 @@ def build_cell(cell) def on_select(tableView, tableViewDelegate) if row.section.select_one and !row.value - row.section.rows.each {|other_row| + row.section.rows.each do |other_row| other_row.value = (other_row == row) - other_row.object.build_cell(tableView.cellForRowAtIndexPath(other_row.index_path)) - } + + cell = tableView.cellForRowAtIndexPath(other_row.index_path) + other_row.object.build_cell(cell) if cell + end elsif !row.section.select_one row.value = !row.value build_cell(tableView.cellForRowAtIndexPath(row.index_path)) diff --git a/lib/formotion/row_type/text_row.rb b/lib/formotion/row_type/text_row.rb new file mode 100644 index 0000000..5ed9ca3 --- /dev/null +++ b/lib/formotion/row_type/text_row.rb @@ -0,0 +1,69 @@ +module Formotion + module RowType + class TextRow < Base + + TEXT_VIEW_TAG=1000 + + attr_accessor :field + + def build_cell(cell) + + @field = UITextView.alloc.initWithFrame(CGRectZero) + field.editable = true + field.tag = TEXT_VIEW_TAG + + field.text = row.value + + field.returnKeyType = row.return_key || UIReturnKeyDefault + field.autocapitalizationType = row.auto_capitalization if row.auto_capitalization + field.autocorrectionType = row.auto_correction if row.auto_correction + + field.on_begin do |text_field| + row.on_begin_callback && row.on_begin_callback.call + end + + field.should_begin? do |text_field| + row.section.form.active_row = row + true + end + + field.on_change do |text_field| + row.value = text_field.text + end + + tap = UITapGestureRecognizer.alloc.initWithTarget self, action:'dismissKeyboard' + cell.addGestureRecognizer tap + + cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) + cell.instance_eval do + def layoutSubviews + old_layoutSubviews + + # viewWithTag is terrible, but I think it's ok to use here... + formotion_field = self.viewWithTag(TEXT_VIEW_TAG) + formotion_field.sizeToFit + + field_frame = formotion_field.frame + field_frame.origin.y = 10 + field_frame.origin.x = self.textLabel.frame.origin.x + self.textLabel.frame.size.width + 20 + field_frame.size.width = self.frame.size.width - field_frame.origin.x - 20 + field_frame.size.height = self.frame.size.height - 20 + formotion_field.frame = field_frame + end + end + + cell.addSubview(field) + field + end + + def on_select(tableView, tableViewDelegate) + row.text_field.becomeFirstResponder + end + + def dismissKeyboard + field.resignFirstResponder + end + + end + end +end \ No newline at end of file From f774e6232d67f4f3a438ab5eb026c11022455d25 Mon Sep 17 00:00:00 2001 From: mordaroso Date: Thu, 28 Jun 2012 11:58:16 +0200 Subject: [PATCH 04/10] fix on change event in UITextView Delegator --- lib/formotion/patch/ui_text_view.rb | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/formotion/patch/ui_text_view.rb b/lib/formotion/patch/ui_text_view.rb index 05474a9..dd34498 100644 --- a/lib/formotion/patch/ui_text_view.rb +++ b/lib/formotion/patch/ui_text_view.rb @@ -80,13 +80,6 @@ class UITextView_Delegate attr_accessor (method.to_s + "_callback").to_sym } - # Called from - # [textView addTarget:block - # action:'call' - # forControlEvents:UIControlEventEditingChanged], - # NOT a UItextViewDelegate method. - attr_accessor :on_change_callback - def textViewShouldBeginEditing(theTextView) if self.textViewShouldBeginEditing_callback return self.textViewShouldBeginEditing_callback.call(theTextView) @@ -121,8 +114,8 @@ def textViewDidEndEditing(theTextView) end def textViewDidChange(theTextView) - if self.on_change_callback - self.on_change_callback.call(theTextView) + if self.textViewDidChange_callback + self.textViewDidChange_callback.call(theTextView) end end From 1576e88db7b9cd7b8d2cf5630f341f4d617ae4cb Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Sat, 30 Jun 2012 08:41:53 -0700 Subject: [PATCH 05/10] create subfolders for each model of the library --- lib/formotion/{ => form}/form.rb | 0 lib/formotion/{ => form}/form_delegate.rb | 0 lib/formotion/{ => row}/row.rb | 0 lib/formotion/{ => row}/row_cell_builder.rb | 0 lib/formotion/{ => row_type}/row_type.rb | 0 lib/formotion/{ => section}/section.rb | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename lib/formotion/{ => form}/form.rb (100%) rename lib/formotion/{ => form}/form_delegate.rb (100%) rename lib/formotion/{ => row}/row.rb (100%) rename lib/formotion/{ => row}/row_cell_builder.rb (100%) rename lib/formotion/{ => row_type}/row_type.rb (100%) rename lib/formotion/{ => section}/section.rb (100%) diff --git a/lib/formotion/form.rb b/lib/formotion/form/form.rb similarity index 100% rename from lib/formotion/form.rb rename to lib/formotion/form/form.rb diff --git a/lib/formotion/form_delegate.rb b/lib/formotion/form/form_delegate.rb similarity index 100% rename from lib/formotion/form_delegate.rb rename to lib/formotion/form/form_delegate.rb diff --git a/lib/formotion/row.rb b/lib/formotion/row/row.rb similarity index 100% rename from lib/formotion/row.rb rename to lib/formotion/row/row.rb diff --git a/lib/formotion/row_cell_builder.rb b/lib/formotion/row/row_cell_builder.rb similarity index 100% rename from lib/formotion/row_cell_builder.rb rename to lib/formotion/row/row_cell_builder.rb diff --git a/lib/formotion/row_type.rb b/lib/formotion/row_type/row_type.rb similarity index 100% rename from lib/formotion/row_type.rb rename to lib/formotion/row_type/row_type.rb diff --git a/lib/formotion/section.rb b/lib/formotion/section/section.rb similarity index 100% rename from lib/formotion/section.rb rename to lib/formotion/section/section.rb From f770137fc483abdd8cc1cfa6221fdd03e6d5e3d8 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Sat, 30 Jun 2012 08:45:48 -0700 Subject: [PATCH 06/10] create object#swizzle doesn't do much but makes those methods a bit cleaner. --- lib/formotion/patch/object.rb | 9 +++++++++ lib/formotion/row_type/character.rb | 3 +-- lib/formotion/row_type/submit_row.rb | 3 +-- lib/formotion/row_type/text_row.rb | 3 +-- 4 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 lib/formotion/patch/object.rb diff --git a/lib/formotion/patch/object.rb b/lib/formotion/patch/object.rb new file mode 100644 index 0000000..fae2918 --- /dev/null +++ b/lib/formotion/patch/object.rb @@ -0,0 +1,9 @@ +class Object + # Creates an alias for :method with the form + # old_#{method} + # Instance evals the block. + def swizzle(method, &block) + self.class.send(:alias_method, "old_#{method.to_s}".to_sym, method) + self.instance_eval &block + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/character.rb b/lib/formotion/row_type/character.rb index a2d84a6..87c27cc 100644 --- a/lib/formotion/row_type/character.rb +++ b/lib/formotion/row_type/character.rb @@ -74,8 +74,7 @@ def build_cell(cell) row.value = text_field.text end - cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) - cell.instance_eval do + cell.swizzle(:layoutSubviews) do def layoutSubviews old_layoutSubviews diff --git a/lib/formotion/row_type/submit_row.rb b/lib/formotion/row_type/submit_row.rb index 839fe55..be9c587 100644 --- a/lib/formotion/row_type/submit_row.rb +++ b/lib/formotion/row_type/submit_row.rb @@ -10,8 +10,7 @@ def submit_button? # for just this one UITableViewCell object, in order to # center it's labels horizontally. def build_cell(cell) - cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) - cell.instance_eval do + cell.swizzle(:layoutSubviews) do def layoutSubviews old_layoutSubviews diff --git a/lib/formotion/row_type/text_row.rb b/lib/formotion/row_type/text_row.rb index 5ed9ca3..b1e5b79 100644 --- a/lib/formotion/row_type/text_row.rb +++ b/lib/formotion/row_type/text_row.rb @@ -34,8 +34,7 @@ def build_cell(cell) tap = UITapGestureRecognizer.alloc.initWithTarget self, action:'dismissKeyboard' cell.addGestureRecognizer tap - cell.class.send(:alias_method, :old_layoutSubviews, :layoutSubviews) - cell.instance_eval do + cell.swizzle(:layoutSubviews) do def layoutSubviews old_layoutSubviews From 1f7529b9f81f8c1549f9a956748043e7b73660c1 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Sat, 30 Jun 2012 09:03:47 -0700 Subject: [PATCH 07/10] properly support placeholder in UITextView --- .../patch/ui_text_view_placeholder.rb | 65 +++++++++++++++++++ lib/formotion/row_type/text_row.rb | 1 + 2 files changed, 66 insertions(+) create mode 100644 lib/formotion/patch/ui_text_view_placeholder.rb diff --git a/lib/formotion/patch/ui_text_view_placeholder.rb b/lib/formotion/patch/ui_text_view_placeholder.rb new file mode 100644 index 0000000..43f0b76 --- /dev/null +++ b/lib/formotion/patch/ui_text_view_placeholder.rb @@ -0,0 +1,65 @@ +class UITextView + attr_reader :placeholder + attr_accessor :placeholder_color + + alias_method :old_initWithCoder, :initWithCoder + def initWithCoder(decoder) + old_initWithCoder(decoder) + setup + self + end + + alias_method :old_initWithFrame, :initWithFrame + def initWithFrame(frame) + old_initWithFrame(frame) + setup + self + end + + def setup + @foreground_observer = NSNotificationCenter.defaultCenter.observe UITextViewTextDidChangeNotification do |notification| + updateShouldDrawPlaceholder + end + end + + alias_method :old_drawRect, :drawRect + def drawRect(rect) + old_drawRect(rect) + + if (@shouldDrawPlaceholder) + self.placeholder_color.set + self.placeholder.drawInRect(placeholder_rect, withFont:self.font) + end + end + + + alias_method :old_setText, :setText + def setText(text) + old_setText(text) + + updateShouldDrawPlaceholder + end + + def placeholder=(placeholder) + return if @placeholder == placeholder + + @placeholder = placeholder + + updateShouldDrawPlaceholder + end + + def placeholder_rect + CGRectMake(self.contentInset.left + 10.0, self.contentInset.top, self.frame.size.width - self.contentInset.left - self.contentInset.right - 16.0, self.frame.size.height - self.contentInset.top - self.contentInset.bottom - 16.0) + end + + def placeholder_color + @placeholder_color ||= UIColor.lightGrayColor + end + + def updateShouldDrawPlaceholder + prev = @shouldDrawPlaceholder; + @shouldDrawPlaceholder = self.placeholder && self.text.length == 0 + + self.setNeedsDisplay if (prev != @shouldDrawPlaceholder) + end +end \ No newline at end of file diff --git a/lib/formotion/row_type/text_row.rb b/lib/formotion/row_type/text_row.rb index b1e5b79..2e1f19f 100644 --- a/lib/formotion/row_type/text_row.rb +++ b/lib/formotion/row_type/text_row.rb @@ -17,6 +17,7 @@ def build_cell(cell) field.returnKeyType = row.return_key || UIReturnKeyDefault field.autocapitalizationType = row.auto_capitalization if row.auto_capitalization field.autocorrectionType = row.auto_correction if row.auto_correction + field.placeholder = row.placeholder field.on_begin do |text_field| row.on_begin_callback && row.on_begin_callback.call From b6ff2dd8c73b214c9a676d403ee2b042d122c118 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Sat, 30 Jun 2012 12:47:21 -0700 Subject: [PATCH 08/10] clear background color for TextRow --- lib/formotion/row_type/text_row.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/formotion/row_type/text_row.rb b/lib/formotion/row_type/text_row.rb index 2e1f19f..8a7f847 100644 --- a/lib/formotion/row_type/text_row.rb +++ b/lib/formotion/row_type/text_row.rb @@ -9,6 +9,7 @@ class TextRow < Base def build_cell(cell) @field = UITextView.alloc.initWithFrame(CGRectZero) + field.backgroundColor = UIColor.clearColor field.editable = true field.tag = TEXT_VIEW_TAG From 0574fc99a98a5293b98efd01b912a1adb8089486 Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Tue, 3 Jul 2012 12:40:02 -0700 Subject: [PATCH 09/10] fix paths --- lib/formotion.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/formotion.rb b/lib/formotion.rb index bb724ae..c872341 100644 --- a/lib/formotion.rb +++ b/lib/formotion.rb @@ -2,7 +2,7 @@ require 'bubble-wrap/core' BW.require File.expand_path('../formotion/**/*.rb', __FILE__) do - ['form.rb', 'row.rb', 'section.rb'].each {|file| + ['form/form.rb', 'row/row.rb', 'section/section.rb'].each {|file| file("lib/formotion/#{file}").depends_on 'lib/formotion/base.rb' } file("lib/formotion/form_controller.rb").depends_on 'lib/formotion/patch/ui_text_field.rb' From a69388b05d182e5fc686517898f7624c335b8f6e Mon Sep 17 00:00:00 2001 From: Clay Allsopp Date: Tue, 3 Jul 2012 12:43:24 -0700 Subject: [PATCH 10/10] update kitchen sink for new properties --- examples/KitchenSink/app/app_delegate.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/examples/KitchenSink/app/app_delegate.rb b/examples/KitchenSink/app/app_delegate.rb index d6de6ae..31867ff 100644 --- a/examples/KitchenSink/app/app_delegate.rb +++ b/examples/KitchenSink/app/app_delegate.rb @@ -43,6 +43,23 @@ def application(application, didFinishLaunchingWithOptions:launchOptions) placeholder: "required", type: :string, secure: true + }, { + title: "Row Height", + key: :row_height, + placeholder: "60px", + type: :string, + rowHeight: 60 + }, { + title: "Text", + key: :text, + type: :text, + placeholder: "Enter your text here", + rowHeight: 100 + }, { + title: "Check", + key: :check, + type: :check, + value: true }, { title: "Remember?", key: :remember,