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. `UIPageViewController` …? • For a static list of pages, it’s

    totally OK. ✅ • It’s fast. " • …Still supports skeuomorphic page turns! $
  2. …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 ?? ⚠
  3. `UIPageViewController` …? ⚠ Data ends here? …What if the user

    swipes to (page count + 1) during a slow async download? swiped from, strong
  4. `UIPageViewController` …? ⚠ Data ends here? OK, but you’d need

    a quick favor from your designer. ✅ End of results! Workaround: Swap in a screen.
  5. …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?
  6. `UICollectionView` …? Has pagination built in! ✅ … … But

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

    it breaks as soon as you add margins between pages. ❌ (… So you have to get creative again. )
  8. 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 ) }
  9. 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!)
  10. 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.
  11. WHAT’S UP? • App Store in iOS 11 has this.

    • … But, implementing this can be quirky. — Even for Apple!
  12. …THE CORRECT WAY? • NOT DIRECTLY with view/window manipulation. •

    Instead, use: "
 `UIViewControllerAnimatedTransitioning`
  13. 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) } ) } ) }
  14. SCROLLING VS. BOUNCE • Content is usually scrollable. • Scrollable

    content bounces on iOS. • … Top-edge needs to not bounce. $

  15. SCROLLING VS. BOUNCE • Content is usually scrollable. • Scrollable

    content bounces on iOS. • … Top-edge needs to not bounce. $
 … but when?
  16. 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?
  17. FAIRLY COMPLEX LOGIC • Gesture monitoring • Event racing •

    … I’ve open-sourced this to try and perfect it collectively.
  18. `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.
  19. CHECK OUT THE BETA CODE! Thanks for listening. ☺ 3

    https://benguild.com https://github.com/benguild