No commit activity in last 3 years
No release in over 3 years
== Description Use +on+ and +trigger+ to send generic events from the layout to the controller. Sounds simple, but this enables a powerful method of keeping your UI logic contained in your Layout files.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Runtime

 Project Readme

MotionKit::Events

In an effort to encourage even MORE separation of concerns in your Controllers and Layouts, the motion-kit-events gem provides a way to listen for custom events (usually triggered by buttons or other UI events) and respond in your controller. This keeps your views from being cluttered with business logic and your controllers from being cluttered with view code.

LoginController

An example of a UIViewController using MotionKit::Events:

class LoginController < UIViewController

  def viewDidLoad
    @layout = LoginLayout.new(root: self.view).build

    @layout.on :login { |username, password| initiate_login(username, password) }
    @layout.on :forgot_password { show_forgot_password }
    @layout.on :help { show_help }
  end

  def initiate_login(username, password)
    @layout.pause_ui
    # send login info to the API
    API::Client.login(username, password) do |user, errors|
      handle_login_response(user, errors)
    end
  end

  def handle_login_response(user, errors)
    # ...
    @layout.resume_ui
  end

  # ...

end

Now we can test just the behavior of the controller. When it receives a :login event, it should send a login request to its API and handle user or errors.

LoginLayout

class LoginLayout < MK::Layout

  def layout
    add UITextField, :username_field do
      delegate self
    end

    add UITextField, :password_field do
      delegate self
    end

    add UIButton, :login_button do
      on :touch do # This is Sugarcube
        trigger_login
      end
    end
  end

  def trigger_login
    # send the username and password to our controller
    trigger :login, get(:username_field).text.to_s, get(:password_field).text.to_s
  end

  def textFieldShouldReturn(field)
    if field == get(:password_field)
      trigger_login
    else
      get(:password_field).becomeFirstResponder
    end
  end

end

Testing

The layout can be tested independently of the controller.

describe LoginLayout do
  before do
    @subject = LoginLayout.new(root: UIView.new).build
  end

  it "triggers :login with username/password when the login button is tapped" do
    @subject.on :login do |user, password|
      user.should == "example"
      password.should == "testing123"
    end
    @subject.get(:username_field).text = "example"
    @subject.get(:password_field).text = "testing123"
    # Simulate tap on button
    @subject.get(:login_button).target.send(@subject.get(:login_button).action)
  end
end

An explanation: State vs. UI/events

The Controller focuses on the movement of the user and the state; the Layout handles displaying the UI state and responding to events.

  1. The user starts out in a "logging in" state. The controller doesn't care how this is represented -- it just cares about when the user is done, and then it pauses the UI.
  2. When the login attempt is complete, the controller tells the UI what state to go in next, either resuming the UI if an error occurred, or just dimissing the controller and passing along the successful login info.

MotionKit::Events is a very lightweight gem. But using it to decouple your UI from your controller can provide a huge long term benefit in terms of keeping your code maintainable!

Sample app

The sample app (most of the code is in app/ios/login/) includes a working version of this example.

Note

The example and specs all require SugarCube to run; this is just because I wanted to have the specs make sure that the on method (used in so many gems) behaves the way you would expect it to.