diff --git a/EMPageViewController.podspec b/EMPageViewController.podspec index d7d426a..31a62a5 100644 --- a/EMPageViewController.podspec +++ b/EMPageViewController.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "EMPageViewController" - s.version = "4.0.0" + s.version = "4.0.1" s.summary = "A better page view controller for iOS." s.homepage = "https://github.com/emalyak/EMPageViewController" s.license = { :type => "MIT", :file => "LICENSE" } diff --git a/EMPageViewController/EMPageViewController.swift b/EMPageViewController/EMPageViewController.swift index 0d1af9c..0b2cbce 100644 --- a/EMPageViewController/EMPageViewController.swift +++ b/EMPageViewController/EMPageViewController.swift @@ -97,6 +97,10 @@ import UIKit - parameter transitionSuccessful: A Boolean whether the transition to the destination view controller was successful or not. If `true`, the new selected view controller is `destinationViewController`. If `false`, the transition returned to the view controller it started from, so the selected view controller is still `startingViewController`. */ @objc optional func em_pageViewController(_ pageViewController: EMPageViewController, didFinishScrollingFrom startingViewController: UIViewController?, destinationViewController:UIViewController, transitionSuccessful: Bool) + + @objc optional func em_pageViewController(_ pageViewController: EMPageViewController, scrollViewDidEndDragging: UIScrollView, willDecelerate decelerate: Bool) + + @objc optional func em_pageViewController(_ pageViewController: EMPageViewController, scrollViewDidEndScrolling: UIScrollView) } /** @@ -120,7 +124,7 @@ import UIKit } /// Manages page navigation between view controllers. View controllers can be navigated via swiping gestures, or called programmatically. -open class EMPageViewController: UIViewController, UIScrollViewDelegate { +open class EMPageViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate { /// The object that provides view controllers on an as-needed basis throughout the navigation of the page view controller. /// @@ -139,9 +143,13 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { return self.navigationOrientation == .horizontal } + private var isChangeSize = false + + @objc open var shouldRecognizeSimultaneously = false + /// The underlying `UIScrollView` responsible for scrolling page views. /// - important: Properties should be set with caution to prevent unexpected behavior. - open private(set) lazy var scrollView: UIScrollView = { + @objc open private(set) lazy var scrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.isPagingEnabled = true scrollView.scrollsToTop = false @@ -152,17 +160,20 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { scrollView.translatesAutoresizingMaskIntoConstraints = true scrollView.showsHorizontalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false + if #available(iOS 11.0, *) { + scrollView.contentInsetAdjustmentBehavior = .never + } return scrollView }() /// The view controller before the selected view controller. - private var beforeViewController: UIViewController? + @objc open private(set) var beforeViewController: UIViewController? /// The currently selected view controller. Can be `nil` if no view controller is selected. @objc open private(set) var selectedViewController: UIViewController? /// The view controller after the selected view controller. - private var afterViewController: UIViewController? + @objc open private(set) var afterViewController: UIViewController? /// Boolean that indicates whether the page controller is currently in the process of scrolling. @objc open private(set) var scrolling = false @@ -180,7 +191,7 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { /// Initializes a newly created page view controller with the specified navigation orientation. /// - parameter navigationOrientation: The page view controller's navigation scroll direction. /// - returns: The initialized page view controller. - public convenience init(navigationOrientation: EMPageViewControllerNavigationOrientation) { + @objc public convenience init(navigationOrientation: EMPageViewControllerNavigationOrientation) { self.init() self.navigationOrientation = navigationOrientation } @@ -199,12 +210,18 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { @objc open func selectViewController(_ viewController: UIViewController, direction: EMPageViewControllerNavigationDirection, animated: Bool, completion: ((_ transitionSuccessful: Bool) -> Void)?) { if (direction == .forward) { - self.afterViewController = viewController + if afterViewController != viewController { + removeChildIfNeeded(afterViewController) + afterViewController = viewController + } self.layoutViews() self.loadNewAdjoiningViewControllersOnFinish = true self.scrollForward(animated: animated, completion: completion) } else if (direction == .reverse) { - self.beforeViewController = viewController + if beforeViewController != viewController { + removeChildIfNeeded(beforeViewController) + beforeViewController = viewController + } self.layoutViews() self.loadNewAdjoiningViewControllersOnFinish = true self.scrollReverse(animated: animated, completion: completion) @@ -212,6 +229,42 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { } + /** + remove all controllers, so that can be reloaded + */ + @objc open func removeViewControllers() { + guard !scrolling else { return } + removeChildIfNeeded(beforeViewController) + removeChildIfNeeded(afterViewController) + removeChildIfNeeded(selectedViewController) + beforeViewController = nil + afterViewController = nil + selectedViewController = nil + } + + /** + allow load after ViewController when new data added to dataSource, call this method when scrolling is false + */ + @objc open func loadAfterViewControllerIfPossible() { + guard !scrolling, nil == afterViewController, + let current = selectedViewController else { return } + loadAfterViewController(for: current) + if nil != afterViewController { + layoutViews() + } + } + + /** + allow load before ViewController when new data added to dataSource, call this method when scrolling is false + */ + @objc open func loadBeforeViewControllerIfPossible() { + guard !scrolling, nil == beforeViewController, + let current = selectedViewController else { return } + loadBeforeViewController(for: current) + if nil != beforeViewController { + layoutViews() + } + } /** Transitions to the view controller right of the currently selected view controller in a horizontal orientation, or below the currently selected view controller in a vertical orientation. Also described as going to the next page. @@ -295,7 +348,7 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { open override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() - guard !scrolling else { + guard !scrolling || isChangeSize else { return } @@ -519,11 +572,16 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { self.layoutViews() } + // MARK: - UIGestureRecognizerDelegate + + func gestureRecognizer(_ gesture: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGesture: UIGestureRecognizer) -> Bool { + return shouldRecognizeSimultaneously + } // MARK: - UIScrollView Delegate open func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !adjustingContentOffset { + if !adjustingContentOffset && !isChangeSize { let distance = self.isOrientationHorizontal ? self.view.bounds.width : self.view.bounds.height let progress = ((self.isOrientationHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y) - distance) / distance @@ -603,14 +661,41 @@ open class EMPageViewController: UIViewController, UIScrollViewDelegate { (self.afterViewController != nil && self.beforeViewController == nil && scrollView.contentOffset.x > abs(scrollView.contentInset.left)) || // If it's at the beginning of the collection, the decelleration can't be triggered by scrolling away from, than torwards the inset (self.beforeViewController != nil && self.afterViewController == nil && scrollView.contentOffset.x < abs(scrollView.contentInset.right)) { // Same as the last condition, but at the end of the collection scrollView.setContentOffset(CGPoint(x: self.view.bounds.width, y: 0), animated: true) + } else { + delegate?.em_pageViewController?(self, scrollViewDidEndScrolling: scrollView) } } else { if (self.beforeViewController != nil && self.afterViewController != nil) || // It isn't at the beginning or end of the page collection (self.afterViewController != nil && self.beforeViewController == nil && scrollView.contentOffset.y > abs(scrollView.contentInset.top)) || // If it's at the beginning of the collection, the decelleration can't be triggered by scrolling away from, than torwards the inset (self.beforeViewController != nil && self.afterViewController == nil && scrollView.contentOffset.y < abs(scrollView.contentInset.bottom)) { // Same as the last condition, but at the end of the collection scrollView.setContentOffset(CGPoint(x: 0, y: self.view.bounds.height), animated: true) + } else { + delegate?.em_pageViewController?(self, scrollViewDidEndScrolling: scrollView) } } + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + delegate?.em_pageViewController?(self, scrollViewDidEndDragging: scrollView, willDecelerate: decelerate) + if !decelerate { + delegate?.em_pageViewController?(self, scrollViewDidEndScrolling: scrollView) + } + } + + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + delegate?.em_pageViewController?(self, scrollViewDidEndScrolling: scrollView) + } + + // MARK: - size change + open override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) { + self.isChangeSize = true + + super.viewWillTransition(to: size, with: coordinator) + coordinator.animate { [weak self] _ in + self?.view.setNeedsLayout() + } completion: { [weak self] _ in + self?.isChangeSize = false + } } }