diff --git a/README.md b/README.md index 715e150..ecb8edc 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,20 @@ You can even use the `make_button` helper to create custom buttons to add: rmq.app.alert(title: "Actions!", message: "Actions created with `make_button` helper.", actions: button_list) ``` + +If you want to present your alert in :sheet style and you are on iPad, you have to provide the `:source` for the popover (either a UIView or a UIBarButtonItem) +```ruby +rmq.append(UIButton, :my_button).on(:tap) do |sender| + rmq.app.alert(title: "Actions!", message: "Alert from a Popover.", actions: [:ok, :cancel], style:sheet, source: sender) +end +``` + +*iOS 8+ options* +These options work only on iOS 8+ +* `:modal` will prevent the popover to be close by tapping outside the popover +* `:arrow_direction` will force the direction of the popover arrow. Valid values are `:up`, `:down`, `:left`, `:right` or `:any` + + ## Available Templates Button templates are provided [HERE](https://github.com/GantMan/RedAlert/blob/master/lib/project/button_templates.rb) diff --git a/Rakefile b/Rakefile index 41e30e2..3fbfec1 100644 --- a/Rakefile +++ b/Rakefile @@ -14,4 +14,5 @@ Motion::Project::App.setup do |app| app.identifier = 'com.gantlaborde.red_alert' app.name = 'RedAlert' app.deployment_target = ENV["DEPLOYMENT_TARGET"] if ENV["DEPLOYMENT_TARGET"] + app.device_family = [:iphone, :ipad] end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 15061a3..4fefaa5 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -39,15 +39,15 @@ def viewDidLoad # Simple action sheet example. # OK button that doesn't care when pressed. - acs.append(UIButton, :alert_controller_four).on(:tap) do - rmq.app.alert(title: "Hey there!", message: "My style is :sheet", style: :sheet) do |action_type| + acs.append(UIButton, :alert_controller_four).on(:tap) do |sender| + rmq.app.alert(title: "Hey there!", message: "My style is :sheet", style: :sheet, source: sender) do |action_type| puts "you clicked #{action_type}" end end # Sheet with no message - acs.append(UIButton, :alert_controller_no_message).on(:tap) do - rmq.app.alert(message: nil, style: :sheet, actions: :yes_no_cancel ) do |action_type| + acs.append(UIButton, :alert_controller_no_message).on(:tap) do |sender| + rmq.app.alert(message: nil, style: :sheet, actions: :yes_no_cancel, source: sender) do |action_type| puts "you clicked #{action_type}" end end @@ -59,14 +59,14 @@ def viewDidLoad # Text field example with :input style. acs.append(UIButton, :alert_controller_fields_one).on(:tap) do rmq.app.alert(title: "Text Field", message: "My style is :input", style: :input) do |action_type, fields| - puts "you entered '#{fields[:text].text}" + puts "you entered '#{fields[:text].text}'" end end # Text field example with :input style and placeholder. acs.append(UIButton, :alert_controller_fields_two).on(:tap) do rmq.app.alert(title: "Text Field", message: "My style is :input", style: :input, placeholder: "Some Placeholder") do |action_type, fields| - puts "you entered '#{fields[:text].text}" + puts "you entered '#{fields[:text].text}'" end end @@ -129,7 +129,7 @@ def viewDidLoad end # Alert example with 4 buttons, each made with `make_button` helper. - acs.append(UIButton, :custom_actions_helper_sheet).on(:tap) do + acs.append(UIButton, :custom_actions_helper_sheet).on(:tap) do |sender| ok = rmq.app.make_button { puts "OK pressed" } @@ -148,7 +148,14 @@ def viewDidLoad button_list = [ok, yes, cancel, destructive] - rmq.app.alert(title: "Actions!", message: "Actions created with `make_button` helper.", actions: button_list, style: :sheet) + rmq.app.alert(title: "Actions!", message: "Actions created with `make_button` helper.", actions: button_list, style: :sheet, source: sender) + end + + # Alert from popover + acs.append(UIButton, :alert_from_popover).on(:tap) do |sender| + label = rmq.find(:template_tour).get + label.sizeToFit + rmq.app.alert(title: "Popover", message: "Presented from popover (if iPad)", actions: [:ok], style: :sheet, source: label, arrow_direction: [:left,:right]) end acs.append(UILabel, :template_tour) @@ -166,8 +173,8 @@ def viewDidLoad end end - acs.append(UIButton, :alert_controller_yesnocancel).on(:tap) do - rmq.app.alert(message: "Would you like a sandwich?", actions: :yes_no_cancel, style: :sheet) do |title| + acs.append(UIButton, :alert_controller_yesnocancel).on(:tap) do |sender| + rmq.app.alert(message: "Would you like a sandwich?", actions: :yes_no_cancel, style: :sheet, source: sender) do |title| case title when :yes puts "Here's your Sandwich!" diff --git a/app/stylesheets/main_stylesheet.rb b/app/stylesheets/main_stylesheet.rb index 322886a..4a188b2 100644 --- a/app/stylesheets/main_stylesheet.rb +++ b/app/stylesheets/main_stylesheet.rb @@ -113,6 +113,11 @@ def custom_actions_helper_sheet st st.text = "Custom Sheet Actions" end + def alert_from_popover st + basic_button(st) + st.text = "Alert From Popover on iPad" + end + def usage_tour st st.frame = {bp: 10, w: screen_width - 20, l: 10, h:20} st.clips_to_bounds = false diff --git a/lib/project/action_sheet_provider.rb b/lib/project/action_sheet_provider.rb index 305520d..55095f6 100644 --- a/lib/project/action_sheet_provider.rb +++ b/lib/project/action_sheet_provider.rb @@ -10,6 +10,8 @@ def build(actions, fieldset=nil, opts={}) @actions = actions @opts = opts + raise ArgumentError.new "Please provide a :source view to use :sheet on iPad" if rmq.device.ipad? and !@opts[:source] + # grab the first cancel action cancel_action = actions.find { |action| action.cancel? } @@ -43,7 +45,17 @@ def build(actions, fieldset=nil, opts={}) def show # when we show, the view controller will disappear because a different _UIAlertOverlayWindow window will take its place @view_controller = rmq.view_controller - @action_sheet.showInView(@view_controller.view) + + if rmq.device.ipad? + source = @opts[:source] + if source.is_a?(UIBarButtonItem) + @action_sheet.showFromBarButtonItem(source, animated: true) + else + @action_sheet.showFromRect(source.frame, inView: @view_controller.view, animated: true) + end + else + @action_sheet.showInView(@view_controller.view) + end end private diff --git a/lib/project/alert_constants.rb b/lib/project/alert_constants.rb index 1858832..0b1afc7 100644 --- a/lib/project/alert_constants.rb +++ b/lib/project/alert_constants.rb @@ -12,5 +12,13 @@ module AlertConstants destructive: UIAlertActionStyleDestructive } + ALERT_POPOVER_ARROW_DIRECTION = { + up: UIPopoverArrowDirectionUp, + down: UIPopoverArrowDirectionDown, + left: UIPopoverArrowDirectionLeft, + right: UIPopoverArrowDirectionRight, + any: UIPopoverArrowDirectionAny + } + end -end \ No newline at end of file +end diff --git a/lib/project/alert_controller_provider.rb b/lib/project/alert_controller_provider.rb index f5c66c4..d11cd58 100644 --- a/lib/project/alert_controller_provider.rb +++ b/lib/project/alert_controller_provider.rb @@ -47,6 +47,34 @@ def build(actions, fieldset = nil, opts={}) @alert_controller.addAction action end + # popover + if @opts[:style] == :sheet and rmq.device.ipad? + raise ArgumentError.new "Please provide a :source view to use :sheet on iPad" unless @opts[:source] + source = @opts[:source] + @alert_controller.setModalPresentationStyle(UIModalPresentationPopover) + if @opts[:modal] + @alert_controller.setModalInPopover(true) + end + if source.is_a?(UIBarButtonItem) + @alert_controller.popoverPresentationController.barButtonItem = source + else + @alert_controller.popoverPresentationController.sourceView = source + end + @alert_controller.popoverPresentationController.sourceRect = source.bounds + + if @opts[:arrow_direction] + directions = @opts[:arrow_direction] + unless directions.is_a?(Array) + directions = [directions] + end + arrow_direction = 0 + directions.each do |direction| + arrow_direction |= RubyMotionQuery::AlertConstants::ALERT_POPOVER_ARROW_DIRECTION[direction] + end + @alert_controller.popoverPresentationController.permittedArrowDirections = arrow_direction + end + end + self end diff --git a/lib/project/red_alert.rb b/lib/project/red_alert.rb index 9bd69a7..f93963f 100644 --- a/lib/project/red_alert.rb +++ b/lib/project/red_alert.rb @@ -19,7 +19,7 @@ def alert(opts={}, &block) # ------------------------------------ opts = {message: opts} if opts.is_a? String opts = {style: :alert, animated: true, show_now: true}.merge(opts) - + # Ability to make no message opts[:message] = if opts.has_key?(:message) opts[:message].nil? ? nil : opts[:message].to_s else @@ -27,6 +27,9 @@ def alert(opts={}, &block) end opts[:style] = VALID_STYLES.include?(opts[:style]) ? opts[:style] : :alert + if opts[:style] == :sheet and rmq.device.ipad? and opts[:source].nil? + raise ArgumentError.new "Please provide a :source view to use :sheet on iPad" + end opts[:fields] = opts[:style] == :custom && opts[:fields] ? opts[:fields] : {text: {placeholder: ''}} api = rmq.device.ios_at_least?(8) ? :modern : :deprecated api = :deprecated if rmq.device.ios_at_least?(8) && opts[:api] == :deprecated diff --git a/resources/fr.lproj/Localizable.strings b/resources/fr.lproj/Localizable.strings index 96b7bb6..619fa79 100644 --- a/resources/fr.lproj/Localizable.strings +++ b/resources/fr.lproj/Localizable.strings @@ -1,10 +1,10 @@ "Yes" = "Oui"; -"No" = "Pas"; +"No" = "Non"; "Cancel" = "Annuler"; "OK" = "OK"; "Delete" = "Effacer"; "Alert!" = "Alerte!"; -"Login" = "S'identifier"; +"Login" = "Identifiant"; "Password" = "Mot De Passe"; "Current Password" = "Mot De Passe Actuel"; "New Password" = "Nouveau Mot De Passe"; diff --git a/spec/action_sheet_provider_spec.rb b/spec/action_sheet_provider_spec.rb index 30ffc05..5f41915 100644 --- a/spec/action_sheet_provider_spec.rb +++ b/spec/action_sheet_provider_spec.rb @@ -6,6 +6,7 @@ @ok = RubyMotionQuery::AlertAction.new(title: "OK", tag: :ok, style: :default) @cancel = RubyMotionQuery::AlertAction.new(title: "Cancel", tag: :cancel, style: :cancel) @boom = RubyMotionQuery::AlertAction.new(title: "Boom!", tag: :boom, style: :destructive) + @view = UIView.new end it "should prevent nil actions" do @@ -16,10 +17,15 @@ Proc.new { @p.build([]) }.should.raise(ArgumentError) end + it "should raise an error on iPad but not on iPhone" do + Proc.new { @p.build([@ok], nil, style: :sheet) }.should.raise(ArgumentError) if rmq.device.ipad? + Proc.new { @p.build([@ok], nil, style: :sheet) }.should.not.raise(ArgumentError) if rmq.device.iphone? + end + describe "action sheet with ok button" do before do - @p.build [@ok], nil, title: "title" + @p.build [@ok], nil, title: "title", source: @view end it "should have the right title" do @@ -51,7 +57,7 @@ describe "action sheet with a cancel button" do before do - @p.build [@cancel], nil, title: "title" + @p.build [@cancel], nil, title: "title", source: @view end it "should have 1 button" do @@ -75,7 +81,7 @@ describe "action sheet with a destructive button" do before do - @p.build [@boom], nil, title: "title" + @p.build [@boom], nil, title: "title", source: @view end it "should have 1 button" do diff --git a/spec/alert_controller_provider_spec.rb b/spec/alert_controller_provider_spec.rb index dc3ca6f..e99ab30 100644 --- a/spec/alert_controller_provider_spec.rb +++ b/spec/alert_controller_provider_spec.rb @@ -104,6 +104,11 @@ Proc.new { @p.build([]) }.should.raise(ArgumentError) end + it "should raise an error on iPad but not on iPhone" do + Proc.new { @p.build([@ok], nil, style: :sheet) }.should.raise(ArgumentError) if rmq.device.ipad? + Proc.new { @p.build([@ok], nil, style: :sheet) }.should.not.raise(ArgumentError) if rmq.device.iphone? + end + context 'without fields' do describe "alert controller with ok button" do @@ -119,7 +124,7 @@ describe "alert controller with a cancel button" do before do - @p.build [@cancel], nil, title: "title", style: :sheet + @p.build [@cancel], nil, title: "title", style: :sheet, source: UIView.new end behaves_like "an alert controller with a cancel button" diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index 82c0ff3..fd7bdd2 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -87,7 +87,7 @@ def NSLocalizedString(key, comment) end it "should have an english No button" do - @ac.actions[1].title.should == "Pas" + @ac.actions[1].title.should == "Non" end it "should have an english Cancel button" do @@ -95,7 +95,7 @@ def NSLocalizedString(key, comment) end it 'should have French placeholder text for the first field' do - @ac.textFields[0].placeholder.should == "S'identifier" + @ac.textFields[0].placeholder.should == "Identifiant" end it 'should have French placeholder text for the second field' do diff --git a/spec/red_alert_spec.rb b/spec/red_alert_spec.rb index 73e081e..97030ff 100644 --- a/spec/red_alert_spec.rb +++ b/spec/red_alert_spec.rb @@ -57,8 +57,9 @@ before do wait TEST_DELAY do UIView.setAnimationsEnabled false + @view = UIView.new @vc = rmq.view_controller - @provider = rmq.app.alert(message: "hello", show_now: false, animated: false, style: :sheet, api: :deprecated) + @provider = rmq.app.alert(message: "hello", show_now: false, animated: false, style: :sheet, api: :deprecated, source: @view) end end @@ -70,20 +71,25 @@ @provider.action_sheet.class.should == UIActionSheet end + it "should raise an error on iPad but not on iPhone" do + Proc.new { rmq.app.alert(style: :sheet) }.should.raise(ArgumentError) if rmq.device.ipad? + Proc.new { rmq.app.alert(style: :sheet) }.should.not.raise(ArgumentError) if rmq.device.iphone? + end + it "has a valid blank title" do - rmq.app.alert(show_now: false, animated: false, style: :sheet, api: :deprecated).action_sheet.title.should == "Alert!" + rmq.app.alert(show_now: false, animated: false, style: :sheet, api: :deprecated, source: @view).action_sheet.title.should == NSLocalizedString("Alert!", nil) end it "has a valid title when given" do - rmq.app.alert(title: "hi", show_now: false, animated: false, style: :sheet, api: :deprecated).action_sheet.title.should == "hi" + rmq.app.alert(title: "hi", show_now: false, animated: false, style: :sheet, api: :deprecated, source: @view).action_sheet.title.should == "hi" end it "should transfer message over to title when there is no title" do - rmq.app.alert(message: "hi", show_now: false, animated: false, style: :sheet, api: :deprecated).action_sheet.title.should == "hi" + rmq.app.alert(message: "hi", show_now: false, animated: false, style: :sheet, api: :deprecated, source: @view).action_sheet.title.should == "hi" end it "should never overwrite title with message" do - rmq.app.alert(title: "1", message: "2", show_now: false, animated: false, style: :sheet, api: :deprecated).action_sheet.title.should == "1" + rmq.app.alert(title: "1", message: "2", show_now: false, animated: false, style: :sheet, api: :deprecated, source: @view).action_sheet.title.should == "1" end it "should be visible at the right time" do @@ -167,8 +173,8 @@ end it 'has the correct placeholder text for the change password template fields' do - @provider.alert_view.textFieldAtIndex(0).placeholder.should == "Current Password" - @provider.alert_view.textFieldAtIndex(1).placeholder.should == "New Password" + @provider.alert_view.textFieldAtIndex(0).placeholder.should == NSLocalizedString("Current Password", nil) + @provider.alert_view.textFieldAtIndex(1).placeholder.should == NSLocalizedString("New Password", nil) end end