我搜索了很多,但还没有找到合适的解决方案。
是否可能禁用UIPageViewController
的弹跳效果,并仍然使用UIPageViewControllerTransitionStyleScroll
?
我搜索了很多,但还没有找到合适的解决方案。
是否可能禁用UIPageViewController
的弹跳效果,并仍然使用UIPageViewControllerTransitionStyleScroll
?
将 <UIScrollViewDelegate>
代理添加到 UIPageViewController 的头文件中。
在 viewDidLoad
中将 UIPageViewController 的基础 UIScrollView 的代理设置为其父级代理:
for (UIView *view in self.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)view).delegate = self;
break;
}
}
scrollViewDidScroll 方法的实现是在用户滑出边界时将 contentOffset 重置到原点(而不是(0,0),而是 (bound.size.width,0)),代码示例如下:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
} else if (_currentPage == totalViewControllersInPageController-1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
}
}
最后,scrollViewWillEndDragging 函数是为了解决一个bug场景:当用户在第一页快速从左向右滑动时,由于上面的函数,第一页不会在左边弹跳,但会在右边(可能是)滑动的速度引起的情况下弹跳。最后,当被弹回时,UIPageViewController 将触发页面翻转到第二页(当然,这是不期望的)。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
if (_currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
*targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
} else if (_currentPage == totalViewControllersInPageController-1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
*targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
}
}
在viewDidLoad
中插入的代码:
for subview in self.view.subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
break;
}
}
scrollViewDidScroll的实现:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
} else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
}
}
scrollViewWillEndDragging的实现:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
} else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
}
}
禁用UIPageViewController的弹跳效果
Swift 2.2
1)将UIScrollViewDelegate添加到UIPageViewController中
extension PageViewController: UIScrollViewDelegate
2) 添加到viewDidLoad中
for view in self.view.subviews {
if let scrollView = view as? UIScrollView {
scrollView.delegate = self
}
}
3) 添加UIScrollViewDelegate方法
func scrollViewDidScroll(scrollView: UIScrollView) {
if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == totalViewControllers - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
}
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == totalViewControllers - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
}
UIPageViewController
中找到currentIndex:https://dev59.com/questions/32oy5IYBdhLWcg3wq_wk - bmjohns以下是一个完整的解决方案,与其他答案不同的是,它不需要您编写/测试自己的代码来跟踪当前显示页面的索引。
假设您的页面存储在不可变数组 sourcePageViewControllers
中,并且在创建了 UIPageViewController
作为 myPageViewController
之后:
let scrollView = myPageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first!
scrollView.delegate = <instance of YourScrollViewDelegateClass>
然后:
extension YourScrollViewDelegateClass: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard sourcePageViewControllers.count > 1 else {
scrollView.isScrollEnabled = false
return
}
guard let viewControllers = myPageViewController.viewControllers, viewControllers.count != 0 else { return }
let baseRecord =
viewControllers
.map { [superview = myPageViewController.view!] viewController -> (viewController: UIViewController, originX: CGFloat) in
let originX = superview.convert(viewController.view.bounds.origin, from: viewController.view).x
return (viewController: viewController, originX: originX)
}
.sorted(by: { $0.originX < $1.originX })
.first!
guard let baseIndex = sourcePageViewControllers.firstIndex(of: baseRecord.viewController) else { return }
let baseViewControllerOffsetXRatio = -baseRecord.originX/scrollView.bounds.width
let progress = (CGFloat(baseIndex) + baseViewControllerOffsetXRatio)/CGFloat(sourcePageViewControllers.count - 1)
if !(0...1 ~= progress) {
scrollView.isScrollEnabled = false
scrollView.isScrollEnabled = true
}
}
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollView.bounces = currentIndex == 0 ||
currentIndex == controllers.count - 1
? false
: true
}
for subview in view.subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
break
}
}
scrollViewDidScroll的实现:
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) || (currentPage == totalNumberOfPages - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
}
scrollViewWillEndDragging的实现:
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) || (currentPage == totalNumberOfPages - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
}
我不确定如何正确管理currentIndex
,但最终做了这样的处理
extension Main: UIPageViewControllerDelegate {
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
guard let viewController = pageViewController.viewControllers?.first,
index = viewControllerDatasource.indexOf(viewController) else {
fatalError("Can't prevent bounce if there's not an index")
}
currentIndex = index
}
}
}
另一个选择是将ScrollView.bounce设置为false。 这解决了我的pageViewController问题(当然不是关于ScrollView的问题)。 弹跳被禁用,所有页面都可以滚动而无需反弹。
UIPageViewController
子类上的scrollview,则可以使用下面的代码片段。请注意,这不仅禁用弹跳,还禁用页面的水平滚动。UISegmentedControl
来在PageViewController中切换页面,因此垂直滚动被完全禁用并对我有效。希望它也能帮助其他人。class MyPageViewController: UIPageViewController {
private lazy var scrollView: UIScrollView = { view.subviews.compactMap({ $0 as? UIScrollView }).first! }()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.isScrollEnabled = false
}
}
以下是Dong Ma编辑的答案,其中:
currentIndex
信息:
如何操作:
UIViewController
,其中添加了UIPageViewController
作为子VC。class ViewController: UIViewController {
var pageNavigationController: UIPageViewController!
private var lastPosition: CGFloat
private var nextIndex: Int
var currentIndex: Int
// rest of UI's setups
}
ViewController
设置为 UIPageViewController
的代理:extension ViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
guard
let currentVisibleViewController = pageViewController.viewControllers?.first,
let nextIndex = pageViewControllers.firstIndex(of: currentVisibleViewController)
else {
return
}
self.nextIndex = nextIndex
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed, let currentVisibleViewController = pageViewController.viewControllers?.first, let newIndex = pageViewControllers.firstIndex(of: currentVisibleViewController) {
self.currentIndex = newIndex
}
self.nextIndex = self.currentIndex
}
}
ViewController
设置为UIPageController
的数据源:extension ViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
// provide next VC
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
// provide prev VC
}
// IMPORTANT: that's the key why it works, don't forget to add it
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return currentIndex
}
}
ViewController
设置为UIPageViewController
的UIScrollView
的代理来“禁用”弹跳效果:// MARK: - UIScrollViewDelegate (disable bouncing for UIPageViewController)
extension BasePaginationVC: UIScrollViewDelegate {
func attachScrollViewDelegate() {
for subview in pageNavigationController.view.subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
lastPosition = scrollView.contentOffset.x
break
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
switch UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) {
case .leftToRight:
if nextIndex > currentIndex {
if scrollView.contentOffset.x < (lastPosition - (0.9 * scrollView.bounds.size.width)) {
currentIndex = nextIndex
}
} else {
if scrollView.contentOffset.x > (lastPosition + (0.9 * scrollView.bounds.size.width)) {
currentIndex = nextIndex
}
}
if currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
case .rightToLeft:
if nextIndex > currentIndex {
if scrollView.contentOffset.x > (lastPosition + (0.9 * scrollView.bounds.size.width)) {
currentIndex = nextIndex
}
} else {
if scrollView.contentOffset.x < (lastPosition - (0.9 * scrollView.bounds.size.width)) {
currentIndex = nextIndex
}
}
if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x < scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == 0 && scrollView.contentOffset.x > scrollView.bounds.size.width {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
@unknown default:
fatalError("unknown default")
}
lastPosition = scrollView.contentOffset.x
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
switch UIView.userInterfaceLayoutDirection(for: view.semanticContentAttribute) {
case .leftToRight:
if currentIndex == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
case .rightToLeft:
if currentIndex == pageViewControllers.count - 1 && scrollView.contentOffset.x <= scrollView.bounds.size.width {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
} else if currentIndex == 0 && scrollView.contentOffset.x >= scrollView.bounds.size.width {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0)
}
@unknown default:
fatalError("unknown default")
}
}
}
我的解决方案是使用Swift 5
在我的场景中,我首先在第二页加载UIPageViewController
。我有三个页面,因此我从中间打开。
这是我的UIPageViewController
代码
import UIKit
class MainPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate {
let idList = ["OverviewController", "ImportantItemsController", "ListMenuController"] // A list of all of my viewControllers' storyboard id
var currentPage = 1 // Tracking the current page
override func viewDidLoad() {
super.viewDidLoad()
setupPageController()
for subview in self.view.subviews { // Getting the scrollView
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
break;
}
}
}
// UIPageViewControllerDataSource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
if (index > 0) {
return storyboard?.instantiateViewController(withIdentifier: idList[index - 1])
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = idList.firstIndex(of: viewController.restorationIdentifier!)!
if (index < idList.count - 1) {
return storyboard?.instantiateViewController(withIdentifier: idList[index + 1])
}
return nil
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return idList.count
}
// UIPageViewControllerDelegate
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
guard let vc = pageViewController.viewControllers?.first else { return }
switch vc {
case is ImportantItemsController:
currentPage = 1
case is OverviewController:
currentPage = 0
default:
currentPage = 2
}
}
}
// ScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let totalViewControllersInPageController = idList.count
if (currentPage == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
} else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
scrollView.contentOffset = CGPoint(x: scrollView.bounds.size.width, y: 0);
}
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let totalViewControllersInPageController = idList.count
if (currentPage == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
} else if (currentPage == totalViewControllersInPageController - 1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
targetContentOffset.pointee = CGPoint(x: scrollView.bounds.size.width, y: 0);
}
}
fileprivate func setupPageController() {
let controller = storyboard?.instantiateViewController(withIdentifier: idList[1]) as! ImportantItemsController // Loading on the second viewController
setViewControllers([controller], direction: .forward, animated: true, completion: nil)
dataSource = self
delegate = self
}
}