Last active
December 11, 2015 00:49
-
-
Save colinta/4519385 to your computer and use it in GitHub Desktop.
ExposeController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# given a target and slideView, this class will manage a slide out view. | |
# | |
# ExposeController.new(target, slideView, options) | |
# | |
# target: this is the view that the pan gesture will be added to, can be the same as the slideView. | |
# slideView: this is the view that will be moved to the right, should be a container view of some sort. | |
# delegate: the expose controller delegate | |
# | |
# It is assumed that the *exposed view* is organized below the slideView, and | |
# so it will be exposed when the slideView is moved. | |
# | |
# Options: | |
# | |
# margin: the minimum visible width of the slideView. default: 50 | |
# direction: `:left` or `:right`. The direction the slideView moves in order to expose the view beneath. | |
# | |
# Delegate methods: | |
# | |
# :didCloseSlideMenu | |
# :didOpenSlideMenu | |
# :didSlideMenu | |
# :willCloseSlideMenu | |
# :willOpenSlideMenu | |
# :willSlideMenu | |
# | |
# Example: | |
# | |
# # navbar is a UINavigationBar instance | |
# # containerView is the view you want to move out of the way | |
# @slidemenu = ExposeController.new(navbar, containerView, margin: SidemenuMargin) | |
# | |
# Memory: | |
# | |
# If you want good memory handling, I recommend you implement these two delegate methods: | |
# def willOpenSlideMenu(menu) | |
# background_view.hide | |
# end | |
# | |
# def didCloseSlideMenu(menu) | |
# background_view.show | |
# end | |
# | |
# Todo: | |
# | |
# Add support for dragging from side of the screen, rather than using a target | |
# view. This can be 'faked' by having a transparent view on the top all your | |
# other views, and passing that in as the target. | |
# | |
class ExposeController | |
# When the slideView is "exposing" the view beneath, this view covers the | |
# visible portion of slideView. The horizontal pan gesture is attached to it | |
attr :coverView | |
# The delegate gets notified before and after slide events | |
attr_accessor :delegate | |
def initialize(target, slideView, options) | |
default_options = { | |
margin: 50, | |
direction: :right, | |
delegate: nil, | |
} | |
@options = default_options.merge(options) | |
@slideView = slideView | |
@coverView = UIControl.alloc.initWithFrame(CGRect.empty) | |
@coverView.on :touch_down { | |
toggle | |
} | |
target.on_gesture(HorizontalPanGestureRecognizer) { |event| | |
case event.state | |
when :began.uigesturerecognizerstate | |
start_gesture(event) | |
when :changed.uigesturerecognizerstate | |
update_state(event) | |
when :ended.uigesturerecognizerstate | |
move_to_state(@last_direction) | |
end | |
} | |
# reset state | |
if open_direction == :right | |
@state = :left | |
@last_direction = :right | |
else | |
@state = :right | |
@last_direction = :left | |
end | |
move_to_state(closed_direction) | |
end | |
def toggle | |
delegate_send :willSlideMenu | |
if @state == closed_direction | |
delegate_send :willOpenSlideMenu | |
move_to_state open_direction | |
else | |
delegate_send :willCloseSlideMenu | |
move_to_state closed_direction | |
end | |
end | |
def slideClosed | |
return if @state == closed_direction | |
delegate_send :willSlideMenu | |
delegate_send :willOpenSlideMenu | |
move_to_state(closed_direction) | |
end | |
def slideOpen | |
return if @state == open_direction | |
delegate_send :willSlideMenu | |
delegate_send :willCloseSlideMenu | |
move_to_state(open_direction) | |
end | |
private | |
def delegate_send(method, *args) | |
# method = "#{method}:" | |
@delegate && @delegate.respond_to?(method) && @delegate.send(method, self, *args) | |
end | |
def open_direction | |
@options[:direction] | |
end | |
def closed_direction | |
if open_direction == :right | |
:left | |
else | |
:right | |
end | |
end | |
def min_x | |
case open_direction | |
when :right | |
@slideView.superview.bounds.min_x | |
when :left | |
@slideView.superview.bounds.min_x - @slideView.bounds.width + @options[:margin] | |
end | |
end | |
def max_x | |
case open_direction | |
when :right | |
@slideView.bounds.max_x - @options[:margin] | |
when :left | |
@slideView.superview.bounds.min_x | |
end | |
end | |
def start_gesture(event) | |
@start = event.locationInView(@slideView.superview).x | |
@last_direction = nil | |
# send 'will' event to delegate | |
delegate_send :willSlideMenu | |
if @state == open_direction | |
delegate_send :willCloseSlideMenu | |
else | |
delegate_send :willOpenSlideMenu | |
end | |
end | |
def update_state(event) | |
panning_location = event.locationInView(@slideView.superview).x | |
position_delta = panning_location - @start | |
if position_delta > 0 | |
movement = :right | |
elsif position_delta < 0 | |
movement = :left | |
else | |
movement = nil | |
end | |
new_x = @slideView.frame.min_x + position_delta | |
if (movement == :right and new_x <= max_x) || (movement == :left and new_x >= min_x) | |
@slideView.slide(:right, {size: position_delta, duration: 0.1, options: UIViewAnimationOptionCurveLinear }) | |
@last_direction = movement | |
end | |
@start = panning_location | |
end | |
def coverViewFrame | |
cover_size = [@options[:margin], @slideView.bounds.height] | |
if open_direction == :right | |
cover_frame = [[0, 0], cover_size] | |
else | |
cover_frame = [[@slideView.bounds.width - @options[:margin], 0], cover_size] | |
end | |
end | |
def move_to_state(direction) | |
position = @slideView.frame.origin | |
original_x = position.x | |
case direction | |
when :left | |
position.x = min_x | |
when :right | |
position.x = max_x | |
end | |
distance = (position.x - original_x).abs | |
duration = distance / (@slideView.bounds.width - @options[:margin]) * 0.25 | |
@slideView.move_to(position, duration: duration, options: UIViewAnimationOptionCurveEaseOut) { | |
# event is done, so send 'did' event to delegate | |
if @state == open_direction | |
delegate_send :didOpenSlideMenu | |
else | |
delegate_send :didCloseSlideMenu | |
end | |
} | |
@state = direction | |
# | |
# also, add the cover view if the state is 'open'. Touching the coverView | |
# will immediately close the slideView | |
delegate_send :didSlideMenu | |
if @state == open_direction | |
@coverView.frame = coverViewFrame | |
@slideView << @coverView | |
else | |
@coverView.removeFromSuperview | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment