Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS — Cautions with sliding and swiping, pull-to-dismiss

Ben Guild
September 15, 2018

iOS — Cautions with sliding and swiping, pull-to-dismiss

How to avoid pitfalls and bad Stack Overflow code when handling UI gestures on iOS! — With the iPhone X out now, more and more navigation is by swiping rather than using buttons.

I presented this on 2018/09/15 at the Tokyo iOS Meetup in Shibuya: https://www.meetup.com/TokyoiOSMeetup/

This second half of this presentation is an extension of a blog post I wrote, which has links to the code: https://benguild.com/2018/09/03/simple-pull-to-dismiss-uiscrollview-swift/

Ben Guild

September 15, 2018
Tweet

Other Decks in Technology

Transcript

  1. CAUTIONS WITH SLIDING
    AND SWIPING, PULL-TO-
    DISMISS
    Ben Guild, 2018/09/15

    View Slide

  2. Swiping between view controllers horizontally.
    … And, swiping downward to dismiss
    modals. (pull-to-dismiss)

    View Slide

  3. Swiping between view controllers horizontally.
    … And, swiping downward to dismiss
    modals. (pull-to-dismiss)

    View Slide

  4. “Compass” app has two pages, but others can have “∞.”

    View Slide

  5. `UIPageViewController` …?

    View Slide

  6. `UIPageViewController` …?
    • For a static list of
    pages, it’s totally OK. ✅
    • It’s fast. "
    • …Still supports
    skeuomorphic page
    turns! $

    View Slide

  7. `UIPageViewController` …?

    View Slide

  8. …Neighboring pages are retained and cannot be safely
    evicted.
    `UIPageViewController` …?
    swiped from,
    strong
    swiped from,
    dealloc
    current
    peeked at?
    strong
    otherwise,
    unalloc
    ???
    unalloc
    1 2 3 4 ??

    View Slide

  9. `UIPageViewController` …?
    ⚠ Data ends here?
    …What if the user swipes
    to (page count + 1)
    during a slow async
    download?
    swiped from,
    strong

    View Slide

  10. `UIPageViewController` …?
    ⚠ Data ends here?
    OK, but you’d need a
    quick favor from your
    designer. ✅

    End of results!
    Workaround: Swap in a screen.

    View Slide

  11. …Can’t evict last page if
    swipe was cancelled!

    End of results!
    peeked at?
    strong
    Workaround: Quickly replace current and preceding retained screens with the
    “End of results” and content in reverse order until the max. …
    swiped from,
    strong
    `UIPageViewController` …?
    ⚠ User is here?

    View Slide


  12. … User’s mental page-count is then wrong.

    View Slide

  13. View Slide

  14. `UICollectionView` …?

    View Slide

  15. `UICollectionView` …?
    Has pagination built in! ✅ …
    collectionView.isPagingEnabled = true

    View Slide

  16. `UICollectionView` …?
    Has pagination built in! ✅ …
    … But it breaks as soon as you add margins between
    pages. ❌

    View Slide

  17. `UICollectionView` …?
    Has pagination built in! ✅ …
    … But it breaks as soon as you add margins between
    pages. ❌
    (… So you have to get creative again. )

    View Slide

  18. ROLL-YOUR-OWN PAGING
    collectionView.isPagingEnabled = false
    override func targetContentOffset(
    forProposedContentOffset proposedContentOffset: CGPoint,
    withScrollingVelocity velocity: CGPoint
    ) -> CGPoint {
    let widthForEachPage = itemSize.width + minimumLineSpacing
    let maximumAllowedPageIndex = Int(ceil(
    (collectionView?.contentSize.width ?? 0)
    / widthForEachPage)) - 1
    return CGPoint(
    x: widthForEachPage * CGFloat(min(
    maximumAllowedPageIndex,
    max(0, Int(round(proposedContentOffset.x
    / widthForEachPage)))
    )),
    y: proposedContentOffset.y
    )
    }

    View Slide

  19. FINALLY…
    Just add your child view controllers to cells in the
    containing view controller’s collection view. "
    (Be sure to load their contents only when necessary!)

    View Slide

  20. COMPARISON
    `UIPageViewController`
    • Easy to use, but slightly funky
    data source and view
    controller lifecycle.
    • Very optimized.
    • Should probably know the
    total number of pages in
    advance.
    `UICollectionView`
    • Very flexible, with a familiar
    data source.
    • Roll-your-own
    optimization.
    • Perhaps better for
    asynchronous load flexibility.

    View Slide

  21. Swiping between view controllers horizontally.
    … And, swiping downward to dismiss
    modals. (pull-to-dismiss)

    View Slide

  22. WHAT’S UP?
    • App Store in iOS 11 has this.
    • … But, implementing this can be
    quirky. — Even for Apple!

    View Slide

  23. …THE CORRECT WAY?
    • NOT DIRECTLY with view/window manipulation.
    • Instead, use: "

    `UIViewControllerAnimatedTransitioning`

    View Slide

  24. ACTUALLY FAIRLY EASY
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard let fromVc = transitionContext.viewController(
    forKey: UITransitionContextViewControllerKey.from
    ) else { return }
    setupTransitionViewsIfNecessary(using: transitionContext, in: fromVc)
    UIView.animate(
    withDuration: transitionDuration(using: transitionContext),
    delay: 0,
    options: .curveEaseOut,
    animations: { [weak self] in
    guard let strongSelf = self, let slideView = fromVc.view else { return }
    slideView.frame = slideView.frame.offsetBy(
    dx: 0,
    dy: slideView.window?.bounds.height ?? 0
    )
    strongSelf.dimmingView?.alpha = 0
    },
    completion: { [weak self] _ in
    self?.tearDownTransitionViewsAsNecessary(
    using: transitionContext,
    for: fromVc,
    completionHandler: {
    transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    }
    )
    }
    )
    }

    View Slide

  25. THAT’S THE BASICS.
    … But what about “bounce?”

    View Slide

  26. SCROLLING VS. BOUNCE
    • Content is usually scrollable.
    • Scrollable content bounces on iOS.
    • … Top-edge needs to not bounce. $


    View Slide

  27. SCROLLING VS. BOUNCE
    • Content is usually scrollable.
    • Scrollable content bounces on iOS.
    • … Top-edge needs to not bounce. $

    … but when?

    View Slide

  28. CONSIDERATIONS
    • When you reach the top, should it bounce, or should it always
    begin to dismiss?
    • How does the velocity of the gesture affect this logic?
    • What if there are competing gestures that could trigger a
    bounce in one instance, but a dismissal in another? …How can this
    still feel natural?
    • … Should we even just disable bounce on the “top-edge,” all
    together?

    View Slide

  29. View Slide

  30. FAIRLY COMPLEX LOGIC
    • Gesture monitoring
    • Event racing
    • … I’ve open-sourced
    this to try and perfect it
    collectively.

    View Slide

  31. View Slide

  32. `PullToDismissable` protocol
    var viewController = MyAwesomeViewController()
    viewController.isPullToDismissEnabled = true
    viewController.modalPresentationCapturesStatusBarAppearance = true
    viewController.modalPresentationStyle = .overFullScreen
    self.present(viewController)
    import PullToDismissTransition
    class MyAwesomeViewController: UIViewController, PullToDismissable {
    private lazy var pullToDismissTransition: PullToDismissTransition = {
    let pullToDismissTransition = PullToDismissTransition(
    viewController: self,
    transitionType: .slideStatic
    )
    return pullToDismissTransition
    }()
    // … etc.

    View Slide

  33. CHECK OUT THE BETA CODE!
    Thanks for listening. ☺
    3 https://benguild.com
    https://github.com/benguild

    View Slide