module SwipeableCell ANIMATION_DURATION = 0.2 MAX_TRANSITION_ALPHA = 0.5 SWIPE_THRESHOLD = 110 LONG_SWIPE_THRESHOLD = SWIPE_THRESHOLD + 80 FADE_IN_DISTANCE = SWIPE_THRESHOLD def gestureRecognizer(recognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer: other_recognizer) return true unless recognizer.is_a? UIPanGestureRecognizer # Allow other recognizers to see vertical panning Gesture.panning_vertically?(recognizer, content_view) end def gestureRecognizerShouldBegin(recognizer) return true unless recognizer.is_a? UIPanGestureRecognizer # Only respond to events if they're panning side to side Gesture.panning_horizontally?(recognizer, content_view) end def on_pan(recognizer) current_location = Gesture.location(recognizer, content_view) case recognizer.state when UIGestureRecognizerStateBegan @pan_gesture_start = current_location when UIGestureRecognizerStateChanged handle_panning horizontal_movement(current_location) when UIGestureRecognizerStateEnded, UIGestureRecognizerStateCancelled handle_gesture_end horizontal_movement(current_location) end end # rubocop:disable Style/HashSyntax def slide_card(location, delay = 0, &completion) UIView.animateWithDuration(ANIMATION_DURATION, delay: delay, options: UIViewAnimationOptionCurveLinear && UIViewAnimationOptionAllowUserInteraction, animations: -> { move_card_horizontally(location) }, completion: -> (_) { completion.call if completion } ) end # rubocop:enable Style/HashSyntax def slide_card_to_center(&callback) slide_card(0, &callback) end def slide_card_off_screen(direction = :rtl, &callback) raise 'Invalid direction' unless [:rtl, :ltr].include? direction screen_size = content_view.frame.size.width location = direction == :rtl ? -screen_size : screen_size slide_card(location, &callback) table_screen.swiped_cell = self end def move_card_horizontally(location) move_content_to(location) reapply_layout end def hidden_icons {} end def icon_layout raise 'Cell must define icon_layout' end def move_content_to(location) raise 'Cell must define move_content_to' end def handle_panning(delta) slide_card(-delta) update_icons(delta) end private def alpha_from_delta(delta) return 1 if delta.abs >= FADE_IN_DISTANCE (delta.abs / FADE_IN_DISTANCE) * MAX_TRANSITION_ALPHA end # rubocop:disable Style/HashSyntax def connect_to_pan_recognizer pan_recognizer = UIPanGestureRecognizer.alloc.initWithTarget self, action: 'on_pan:' pan_recognizer.delegate = self layout.view.addGestureRecognizer pan_recognizer end # rubocop:enable Style/HashSyntax # Right -> Left = Positive delta # Left -> Right = Negative delta def delta_to_direction(delta) delta > 0 ? :rtl : :ltr end def handle_gesture_end(delta) end def hide_icon(icon_id) icon_layout.get(icon_id).alpha = 0 end def hide_icons(&callback) hidden_icons.values.map { |icon| icon[:object_id] }.each do |icon_id| # rubocop:disable Style/HashSyntax UIView.animateWithDuration(ANIMATION_DURATION, animations: -> { hide_icon(icon_id) } ) # rubocop:enable Style/HashSyntax end rmq.app.delay(ANIMATION_DURATION) { callback.call if callback } end def hide_inverse_icon(inverse_icon_hash) hide_icon inverse_icon_hash[:object_id] end def horizontal_movement(current_location) @pan_gesture_start.x - current_location.x end def icon_image_name(delta) icon_options = hidden_icons[delta_to_direction(delta)] return unless icon_options return icon_options[:default_image] if delta.abs < SWIPE_THRESHOLD if delta.abs < LONG_SWIPE_THRESHOLD || !icon_options[:long_swipe_image] return icon_options[:short_swipe_image] end icon_options[:long_swipe_image] end def reapply_layout content_view.layoutIfNeeded end def screen_size content_view.frame.size.width end def show_revealing_icon(icon_hash, delta) icon = icon_layout.get(icon_hash[:object_id]) icon.alpha = alpha_from_delta(delta) icon.image = rmq.image.resource("dashboard/#{icon_image_name(delta)}") end def update_icons(delta) direction = delta_to_direction(delta) icon_hash = hidden_icons[direction] inverse_icon_hash = hidden_icons[direction == :rtl ? :ltr : :rtl] show_revealing_icon(icon_hash, delta) if icon_hash return unless inverse_icon_hash hide_inverse_icon(inverse_icon_hash) end end