From f79d5a6b593533ac824dc6cb55bc198c496406df Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 31 Dec 2019 23:04:48 -0500 Subject: [PATCH] Add support for scroll views inside of navigation controllers --- .../SheetContainerViewController.swift | 55 +++++++++++++------ .../Base.lproj/Main.storyboard | 15 +++-- .../ContentTableViewController.swift | 17 ++++++ SheetImagePickerTest/ViewController.swift | 50 +++++++++++++++++ 4 files changed, 115 insertions(+), 22 deletions(-) diff --git a/SheetImagePicker/SheetContainerViewController.swift b/SheetImagePicker/SheetContainerViewController.swift index a6fd8c1..99f0669 100644 --- a/SheetImagePicker/SheetContainerViewController.swift +++ b/SheetImagePicker/SheetContainerViewController.swift @@ -11,6 +11,8 @@ import UIKit public protocol SheetContainerViewControllerDelegate { func sheetContainer(_ sheetContainer: SheetContainerViewController, willSnapToDetent detent: Detent) -> Bool func sheetContainer(_ sheetContainer: SheetContainerViewController, didSnapToDetent detent: Detent) + func sheetContainerContentScrollView(_ sheetContainer: SheetContainerViewController) -> UIScrollView? + func sheetContainer(_ sheetContainer: SheetContainerViewController, topContentOffsetForScrollView scrollView: UIScrollView) -> CGFloat } // default no-op implementation @@ -19,13 +21,19 @@ public extension SheetContainerViewControllerDelegate { return true } func sheetContainer(_ sheetContainer: SheetContainerViewController, didSnapToDetent detent: Detent) {} + func sheetContainerContentScrollView(_ sheetContainer: SheetContainerViewController) -> UIScrollView? { + return nil + } + func sheetContainer(_ sheetContainer: SheetContainerViewController, topContentOffsetForScrollView scrollView: UIScrollView) -> CGFloat { + return 0 + } } public class SheetContainerViewController: UIViewController { public var delegate: SheetContainerViewControllerDelegate? - let content: UIViewController + public let content: UIViewController public var detents: [Detent] = [.bottom, .middle, .top] var topDetent: (detent: Detent, offset: CGFloat) { @@ -44,7 +52,10 @@ public class SheetContainerViewController: UIViewController { var dimmingView: UIView! public var minimumDimmingAlpha: CGFloat = 0 public var maximumDimmingAlpha: CGFloat = 0.75 - var initialScrollViewContentOffset = CGPoint.zero + + var contentScrollView: UIScrollView? { + delegate?.sheetContainerContentScrollView(self) ?? content.view as? UIScrollView + } public init(content: UIViewController) { self.content = content @@ -85,11 +96,12 @@ public class SheetContainerViewController: UIViewController { dimmingView.bottomAnchor.constraint(equalTo: content.view.topAnchor, constant: content.view.layer.cornerRadius) ]) - if let scrollView = content.view as? UIScrollView { + let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:))) + panGesture.delegate = self + content.view.addGestureRecognizer(panGesture) + + if let scrollView = contentScrollView { scrollView.panGestureRecognizer.addTarget(self, action: #selector(scrollViewPanGestureRecognized)) - } else { - let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:))) - content.view.addGestureRecognizer(panGesture) } } @@ -114,29 +126,27 @@ public class SheetContainerViewController: UIViewController { @objc func scrollViewPanGestureRecognized(_ recognizer: UIPanGestureRecognizer) { guard let scrollView = recognizer.view as? UIScrollView else { return } - - let translation = recognizer.translation(in: scrollView) + let velocity = recognizer.velocity(in: scrollView) - - let shouldMoveSheetDown = scrollView.contentOffset.y <= 0 && velocity.y > 0 // scrolled to top and dragging down + + let topContentOffset: CGFloat = delegate?.sheetContainer(self, topContentOffsetForScrollView: scrollView) ?? 0 + let shouldMoveSheetDown = scrollView.contentOffset.y <= topContentOffset && velocity.y > 0 // scrolled to top and dragging down let shouldMoveSheetUp = topConstraint.constant > topDetent.offset && velocity.y < 0 // not fully expanded and dragging up - + let shouldMoveSheet = shouldMoveSheetDown || shouldMoveSheetUp if shouldMoveSheet { scrollView.bounces = false - scrollView.setContentOffset(.zero, animated: false) + scrollView.setContentOffset(CGPoint(x: 0, y: topContentOffset), animated: false) } - + switch recognizer.state { - case .began: - initialScrollViewContentOffset = scrollView.contentOffset - case .changed: if shouldMoveSheet { + let translation = recognizer.translation(in: scrollView) setTopOffset(topConstraint.constant + translation.y) - recognizer.setTranslation(initialScrollViewContentOffset, in: scrollView) + recognizer.setTranslation(.zero, in: scrollView) } - + case .ended: scrollView.bounces = true if shouldMoveSheet { @@ -205,6 +215,15 @@ public class SheetContainerViewController: UIViewController { } +extension SheetContainerViewController: UIGestureRecognizerDelegate { + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if topConstraint.constant <= topDetent.offset { + return (gestureRecognizer as! UIPanGestureRecognizer).translation(in: gestureRecognizer.view!).y > 0 + } + return true + } +} + extension SheetContainerViewController: UIViewControllerTransitioningDelegate { public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return SheetContainerPresentationAnimationController() diff --git a/SheetImagePickerTest/Base.lproj/Main.storyboard b/SheetImagePickerTest/Base.lproj/Main.storyboard index 78619f3..450ea98 100644 --- a/SheetImagePickerTest/Base.lproj/Main.storyboard +++ b/SheetImagePickerTest/Base.lproj/Main.storyboard @@ -16,29 +16,36 @@ - + + diff --git a/SheetImagePickerTest/ContentTableViewController.swift b/SheetImagePickerTest/ContentTableViewController.swift index e1c60cb..75a5654 100644 --- a/SheetImagePickerTest/ContentTableViewController.swift +++ b/SheetImagePickerTest/ContentTableViewController.swift @@ -40,4 +40,21 @@ class ContentTableViewController: UITableViewController { return cell } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let navController = navigationController else { return } + + let vc = UIViewController() + vc.view.backgroundColor = .systemBackground + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = "\(indexPath.row)" + vc.view.addSubview(label) + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor) + ]) + + navController.pushViewController(vc, animated: true) + } } diff --git a/SheetImagePickerTest/ViewController.swift b/SheetImagePickerTest/ViewController.swift index 69e8044..5adba45 100644 --- a/SheetImagePickerTest/ViewController.swift +++ b/SheetImagePickerTest/ViewController.swift @@ -58,6 +58,42 @@ class ViewController: UIViewController { } @IBAction func navPressed(_ sender: Any) { + let root = UIViewController() + root.view.backgroundColor = .systemBackground + let label = UILabel() + label.text = "Root VC" + label.translatesAutoresizingMaskIntoConstraints = false + root.view.addSubview(label) + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: root.view.centerXAnchor), + label.centerYAnchor.constraint(equalTo: root.view.centerYAnchor) + ]) + + let nav = UINavigationController(rootViewController: root) + nav.view.translatesAutoresizingMaskIntoConstraints = false + nav.view.layer.masksToBounds = true + nav.view.layer.cornerRadius = view.bounds.width * 0.02 + + let sheet = SheetContainerViewController(content: nav) + sheet.delegate = self + sheet.detents = [.bottom, .middle, .top] + + present(sheet, animated: true) + } + + @IBAction func navTablePressed(_ sender: Any) { + let table = ContentTableViewController() + + let nav = UINavigationController(rootViewController: table) + nav.view.translatesAutoresizingMaskIntoConstraints = false + nav.view.layer.masksToBounds = true + nav.view.layer.cornerRadius = view.bounds.width * 0.02 + + let sheet = SheetContainerViewController(content: nav) + sheet.delegate = self + sheet.detents = [.bottom, .middle, .top] + + present(sheet, animated: true) } } @@ -69,4 +105,18 @@ extension ViewController: SheetContainerViewControllerDelegate { } return true } + func sheetContainerContentScrollView(_ sheetContainer: SheetContainerViewController) -> UIScrollView? { + if let navController = sheetContainer.content as? UINavigationController, let scrollView = navController.visibleViewController?.view as? UIScrollView { + return scrollView + } else { + return nil + } + } + func sheetContainer(_ sheetContainer: SheetContainerViewController, topContentOffsetForScrollView scrollView: UIScrollView) -> CGFloat { + if let navController = sheetContainer.content as? UINavigationController, navController.visibleViewController?.view is UIScrollView { + return -navController.navigationBar.bounds.height + } else { + return 0 + } + } }