是否可以在 UIPageViewController 中以编程方式翻页?

是否可以在 UIPageViewController中以编程方式翻页?

119086 次浏览

是的,这种方法是可行的:

- (void)setViewControllers:(NSArray *)viewControllers
direction:(UIPageViewControllerNavigationDirection)direction
animated:(BOOL)animated
completion:(void (^)(BOOL finished))completion;`

这与在页面上设置第一个视图控制器所使用的方法相同。类似地,您可以使用它来转到其他页面。

想知道为什么 viewControllers是一个数组,而不是一个单一的视图控制器?

这是因为页面视图控制器可以有一个“脊柱”(就像 iBooks 中一样) ,一次显示2页内容。如果一次显示一页内容,那么只需传入一个包含1个元素的数组。

斯威夫特的一个例子:

pageContainer.setViewControllers([displayThisViewController], direction: .Forward, animated: true, completion: nil)

由于我也需要这个,我将详细介绍如何做到这一点。

注意: 我假设您使用了标准模板表单来生成 UIPageViewController结构-当您调用它时,同时创建了 modelViewControllerdataViewController。如果你不明白我写了什么-回去创建一个新的项目,使用 UIPageViewController作为它的基础。到时候你就明白了。

因此,需要翻到特定的页面时,需要设置上面列出的方法的各个部分。对于这个练习,我假设它是一个 两个景观的景观。同时,我实现了一个 IBAction,这样它就可以通过按钮或者其他什么来完成-它很容易使它的选择器调用和传递所需的项目。

因此,对于这个示例,您需要显示两个视图控制器,还可以选择是在书中向前还是向后。

请注意,我只是硬编码哪里去第4和第5页,并使用一个向前滑动。 从这里您可以看到,所有您需要做的就是传递将帮助您获得这些项目的变量..。

-(IBAction) flipToPage:(id)sender {


// Grab the viewControllers at position 4 & 5 - note, your model is responsible for providing these.
// Technically, you could have them pre-made and passed in as an array containing the two items...


DataViewController *firstViewController = [self.modelController viewControllerAtIndex:4 storyboard:self.storyboard];
DataViewController *secondViewController = [self.modelController viewControllerAtIndex:5 storyboard:self.storyboard];


//  Set up the array that holds these guys...


NSArray *viewControllers = nil;


viewControllers = [NSArray arrayWithObjects:firstViewController, secondViewController, nil];


//  Now, tell the pageViewContoller to accept these guys and do the forward turn of the page.
//  Again, forward is subjective - you could go backward.  Animation is optional but it's
//  a nice effect for your audience.


[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];


//  Voila' - c'est fin!


}

这个代码对我很有用,谢谢。

我就是这么处理它的。一些向前或向后单步执行的方法,以及一种直接转到特定页面的方法。这是一个6页的文件在纵向视图。如果您将其粘贴到 pageViewController 模板的 RootController 实现中,那么它将工作正常。

-(IBAction)pageGoto:(id)sender {


//get page to go to
NSUInteger pageToGoTo = 4;


//get current index of current page
DataViewController *theCurrentViewController = [self.pageViewController.viewControllers   objectAtIndex:0];
NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];


//get the page(s) to go to
DataViewController *targetPageViewController = [self.modelController viewControllerAtIndex:(pageToGoTo - 1) storyboard:self.storyboard];


DataViewController *secondPageViewController = [self.modelController viewControllerAtIndex:(pageToGoTo) storyboard:self.storyboard];


//put it(or them if in landscape view) in an array
NSArray *theViewControllers = nil;
theViewControllers = [NSArray arrayWithObjects:targetPageViewController, secondPageViewController, nil];




//check which direction to animate page turn then turn page accordingly
if (retreivedIndex < (pageToGoTo - 1) && retreivedIndex != (pageToGoTo - 1)){
[self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
}


if (retreivedIndex > (pageToGoTo - 1) && retreivedIndex != (pageToGoTo - 1)){
[self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL];
}


}




-(IBAction)pageFoward:(id)sender {


//get current index of current page
DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];


//check that current page isn't first page
if (retreivedIndex < 5){


//get the page to go to
DataViewController *targetPageViewController = [self.modelController viewControllerAtIndex:(retreivedIndex + 1) storyboard:self.storyboard];


//put it(or them if in landscape view) in an array
NSArray *theViewControllers = nil;
theViewControllers = [NSArray arrayWithObjects:targetPageViewController, nil];


//add page view
[self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];


}


}




-(IBAction)pageBack:(id)sender {




//get current index of current page
DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];


//check that current page isn't first page
if (retreivedIndex > 0){


//get the page to go to
DataViewController *targetPageViewController = [self.modelController viewControllerAtIndex:(retreivedIndex - 1) storyboard:self.storyboard];


//put it(or them if in landscape view) in an array
NSArray *theViewControllers = nil;
theViewControllers = [NSArray arrayWithObjects:targetPageViewController, nil];


//add page view
[self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL];


}


}

然而,使用这个“ self. pageViewController setViewController”方法调用不会在视图控制器中调用“ viewDidDissapear”,而在手动翻页时会在被转开的页面上调用 viewDidDissapear。我相信这就是我撞车的原因

提供的视图控制器的数量(0)与请求的转换所需的数量(1)不匹配

我还需要一个按钮来在 PageViewController 上向左导航,这个按钮应该可以做到你所期望的滑动动作。

因为我在“ PageViewController: viewControllerBeforeViewController”中已经有了一些处理自然滑动导航的规则,所以我希望按钮能够重复使用所有的代码,并且像前面的答案一样简单地 不能使用特定的索引到达页面。所以我不得不采取另一种解决办法。

请注意,下面的代码用于页面视图控制器,该控制器是我的自定义 ViewController 中的一个属性,并将其脊柱设置为 mid。

下面是我为“导航左键”编写的代码:

- (IBAction)btnNavigateLeft_Click:(id)sender {


// Calling the PageViewController to obtain the current left page
UIViewController *currentLeftPage = [_pageController.viewControllers objectAtIndex:0];


// Creating future pages to be displayed
NSArray *newDisplayedPages = nil;
UIViewController *newRightPage = nil;
UIViewController *newLeftPage = nil;


// Calling the delegate to obtain previous pages
// My "pageViewController:viewControllerBeforeViewController" method returns nil if there is no such page (first page of the book).
newRightPage = [self pageViewController:_pageController viewControllerBeforeViewController:currentLeftPage];
if (newRightPage) {
newLeftPage = [self pageViewController:_pageController viewControllerBeforeViewController:newRightPage];
}


if (newLeftPage) {
newDisplayedPages = [[NSArray alloc] initWithObjects:newLeftPage, newRightPage, nil];
}


// If there are two new pages to display, show them with animation.
if (newDisplayedPages) {
[_pageController setViewControllers:newDisplayedPages direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
}
}

你可以做一些类似的事情,从这里制作一个正确的导航按钮。

正如@samwize 指出的,分页的方法是使用

setViewControllers:array

我是这么做的: 我把数据源和委托分开了。您可以调用此方法,并且只更改“ next”视图。以前您必须应用分页规则。 也就是说,您必须检查方向和下一个控制器。如果对自定义数据源使用该方法,则显示的控制器数组将不会更改(因为将在 NSObject 子类上使用自定义数组)。

使用您自己的数据源,该方法只是设置一个新的视图(具有特定的方向)。因为您仍然可以控制在正常滑动时显示的页面。

下面是单页视图的另一个代码示例。这里省略了 ViewControllerAtIndex的实现,它应该返回给定索引的正确视图控制器。

- (void)changePage:(UIPageViewControllerNavigationDirection)direction {
NSUInteger pageIndex = ((FooViewController *) [_pageViewController.viewControllers objectAtIndex:0]).pageIndex;


if (direction == UIPageViewControllerNavigationDirectionForward) {
pageIndex++;
}
else {
pageIndex--;
}


FooViewController *viewController = [self viewControllerAtIndex:pageIndex];


if (viewController == nil) {
return;
}


[_pageViewController setViewControllers:@[viewController]
direction:direction
animated:YES
completion:nil];
}

可以通过调用

[self changePage:UIPageViewControllerNavigationDirectionReverse];
[self changePage:UIPageViewControllerNavigationDirectionForward];

我从昨天就开始练习了,终于成功了。我不能完全使用标准模板,因为我有三个不同的 viewController 作为子视图:

1 - rootViewController;
2 - model;
3 - viewControllers
3.1 - VC1;
3.2 - VC2;
3.3 - VC3.
Note: all of VCs has Storyboard ID.

我只在第一个 VC 中需要一个触发按钮,所以我为 pageViewController 和 model 添加了一个属性:

#import <UIKit/UIKit.h>
#import "Model.h"


@interface VC1 : UIViewController


@property (nonatomic, strong) UIPageViewController *thePageViewController;
@property (nonatomic, strong) SeuTimePagesModel *theModel;


@end

我重写了模型的 init 方法,将 Storyboard 和 pageViewController 作为参数传递,这样我就可以将它们设置为我的 VC1:

- (id)initWithStoryboard:(UIStoryboard *)storyboard
andPageViewController:(UIPageViewController *)controller
{
if (self = [super init])
{
VC1 *vc1 = [storyboard instantiateViewControllerWithIdentifier:@"VC1"];
vc1.pageViewController = controller;
vc1.model = self;


VC2 *vc2 = [storyboard instantiateViewControllerWithIdentifier:@"VC2"];
VC3 *vc3 = [storyboard instantiateViewControllerWithIdentifier:@"VC3"];
self.pageData = [[NSArray alloc] initWithObjects:vc1, vc2, vc3, nil];
}


return self;
}

最后,我在 vc1中添加了一个 IBAction 来触发 pageViewController 方法 < em > setViewController: direction: Animated: complete: :

- (IBAction)go:(id)sender
{
VC2 *vc2 = [_model.pages objectAtIndex:1];
NSArray *viewControllers = @[vc2];
[_pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}

注意 我不需要查找 VC 索引,我的按钮把我带到我的第二个 VC,但它不难获取所有的索引,并保持跟踪你的子视图:

- (IBAction)selecionaSeuTime:(id)sender
{
NSUInteger index = [_model.pages indexOfObject:self];
index++;


VC2 *vc2 = [_model.pages objectAtIndex:1];
NSArray *viewControllers = @[vc2];
[_pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}

希望对你有帮助!

斯威夫特:

import UIKit


extension HomeViewController {


// MARK: - Carousel management.


func startTimerForMovingCarousel(let numberOfSecondsBetweenFiringsOfTheTimer: NSTimeInterval) {
if (self.timerForMovingCarousel == nil) {
self.timerForMovingCarousel = NSTimer.scheduledTimerWithTimeInterval(numberOfSecondsBetweenFiringsOfTheTimer,
target: self,
selector: "moveCarousel",
userInfo: nil,
repeats: true)
}
}


func moveCarousel() {
println("I move the carousel.")


let currentContentViewControllerDisplayed = self.pageViewController!.viewControllers[0] as! ContentViewController


var index: Int = currentContentViewControllerDisplayed.index


if index == self.arrayViewControllers.count - 1 {
index = 0
} else {
++index
}


let contentViewControllerToDisplay = self.arrayViewControllers[index] as! ContentViewController


self.pageViewController.setViewControllers([contentViewControllerToDisplay],
direction: UIPageViewControllerNavigationDirection.Forward,
animated: true,
completion: nil)


self.pageControl.currentPage = contentViewControllerToDisplay.index
}


func invalidateTimerForMovingCarousel() {
if (self.timerForMovingCarousel != nil) {
self.timerForMovingCarousel.invalidate()
self.timerForMovingCarousel = nil
}
}


}

在单页中,我只编辑了@Jack Humphries 的回答

-(void)viewDidLoad
{
counter = 0;
}
-(IBAction)buttonClick:(id)sender
{
counter++;
DataViewController *secondVC = [self.modelController viewControllerAtIndex:counter storyboard:self.storyboard];
NSArray *viewControllers = nil;
viewControllers = [NSArray arrayWithObjects:secondVC, nil];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
}

使用 dataSource 中的方法怎么样?

UIViewController *controller = [pageViewController.dataSource pageViewController:self.pageViewController viewControllerAfterViewController:pageViewController.viewControllers.firstObject];
[pageViewController setViewControllers:@[controller] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];

类比倒退。

以防 如果 PAGE CONTROL 指示当前页面为 O & 你希望页面通过滚动导航 & 也通过点击页面查看控制器中的自定义按钮,下面可能会有帮助。

//Set Delegate & Data Source for PageView controller [Say in View Did Load]
self.pageViewController.dataSource = self;
self.pageViewController.delegate = self;


// Delegates called viewControllerBeforeViewController when User Scroll to previous page


- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController{


[_nextBtn setTitle:@"Next" forState:UIControlStateNormal];


NSUInteger index = ((PageContentViewController*) viewController).pageIndex;


if (index == NSNotFound){
return nil;
}else if (index > 0){
index--;
}else{
return nil;
}
return [self viewControllerAtIndex:index];
}

当用户滚动到下一页时,调用 viewControllerAfterViewController 的//委托

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController{


NSUInteger index = ((PageContentViewController*) viewController).pageIndex;


if (index == NSNotFound){
return nil;
}else if (index < 3){
index++;
}else{
return nil;
}
return [self viewControllerAtIndex:index];}


//Delegate called while transition of pages. The need to apply logic in this delegate is to maintain the index of current page.


- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{


buttonIndex = (int)((PageContentViewController*) pendingViewControllers.firstObject).pageIndex;

}

-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if (buttonIndex == 0){
_backButton.hidden = true;
}else if (buttonIndex == [self.pageImages count] - 1){
_backButton.hidden = false;
[_nextBtn setTitle:@"Begin" forState:UIControlStateNormal];
}else{
_backButton.hidden = false;
}

}

//自定义按钮的(上一页及下一页)逻辑

-(void)backBtnClicked:(id)sender{
if (buttonIndex > 0){
buttonIndex -= 1;
}
if (buttonIndex < 1) {
_backButton.hidden = YES;
}
if (buttonIndex >=0) {
[_nextBtn setTitle:@"Next" forState:UIControlStateNormal];
PageContentViewController *startingViewController = [self viewControllerAtIndex:buttonIndex];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}

}

-(void)nextBtnClicked:(id)sender{
if (buttonIndex < 3){
buttonIndex += 1;
}
if(buttonIndex == _pageImages.count){
//Navigate Outside Pageview controller
}else{
if (buttonIndex ==3) {


[_nextBtn setTitle:@"Begin" forState:UIControlStateNormal];
}
_backButton.hidden = NO;


PageContentViewController *startingViewController = [self viewControllerAtIndex:buttonIndex];
NSArray *viewControllers = @[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}

}

//BUTTON INDEX & MAX COUNT
-(NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController{
return [self.pageImages count];}


-(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController{
return  buttonIndex;}

我可能完全遗漏了一些东西,但是这个解决方案似乎比其他许多人提出的要简单得多。

extension UIPageViewController {
func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
if let currentViewController = viewControllers?[0] {
if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) {
setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
}
}
}
}
- (void)moveToPage {


NSUInteger pageIndex = ((ContentViewController *) [self.pageViewController.viewControllers objectAtIndex:0]).pageIndex;


pageIndex++;


ContentViewController *viewController = [self viewControllerAtIndex:pageIndex];


if (viewController == nil) {
return;
}
[self.pageViewController setViewControllers:@[viewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];


}

//现在调用此方法

[ self moveToPage ] ;

我希望它能起作用:)

迅捷4:

我需要去下一个控制器,当我点击一个 pageController 视图控制器的下一步,也更新 pageControl 索引,所以这是最好的,最直接的解决方案 为了我:

let pageController = self.parent as! PageViewController


pageController.setViewControllers([parentVC.orderedViewControllers[1]], direction: .forward, animated: true, completion: nil)


pageController.pageControl.currentPage = 1

下面是一个使用 数据源方法的解决方案:

代码在 UIPageViewController中跟踪当前显示的视图控制器。

...
@property (nonatomic, strong) UIViewController *currentViewController;
@property (nonatomic, strong) UIViewController *nextViewController;
...

首先将 CurrentViewController初始化为 UIPageViewController的初始视图控制器集。

- (void) viewDidLoad {
[super viewDidLoad];
...
self.currentViewController = self.viewControllers[0];
...
[self.pageViewController setViewControllers: @[self.currentViewController] direction: dir animated: YES completion: nil];
}




然后在 授权方法中跟踪 CurrentViewController: PageViewController:PageViewController: did 完成动画: previousViewController: 过渡完成:


- (nullable UIViewController *) pageViewController: (UIPageViewController *) pageViewController viewControllerBeforeViewController: (UIViewController *) viewController {
NSInteger idx = [self.viewControllers indexOfObject: viewController];
if (idx > 0) {
return self.viewControllers[idx - 1];
} else {
return nil;
}
}


- (nullable UIViewController *) pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController: (UIViewController *) viewController {
NSInteger idx = [self.viewControllers indexOfObject: viewController];
if (idx == NSNotFound) {
return nil;
} else {
if (idx + 1 < self.viewControllers.count) {
return self.viewControllers[idx + 1];
} else {
return nil;
}
}
}


- (void) pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
self.nextViewController = [pendingViewControllers firstObject];
}


- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
if (completed) {
self.currentViewController = self.nextViewController;
}
}

最后,在您选择的方法中(在下面的例子中是 - (IBAction) onNext: (id) sender) ,您可以使用 数据源方法 PageViewController: viewControllerBefore ViewController:PageViewController: viewControllerAfterViewController:以编程方式切换到下一个或上一个控制器,以获得下一个或上一个视图控制器,并通过调用 [ self. pageViewController setViewController:@[ vc ]方向: UIPageViewControllerNavigationDirectionForward 动画: YES 完成: nil ] ;导航到它


- (void) onNext {
UIViewController *vc = [self pageViewController: self.tutorialViewController viewControllerAfterViewController: self.currentViewController];
if (vc != nil) {
self.currentViewController = vc;
[self.tutorialViewController setViewControllers: @[vc] direction: UIPageViewControllerNavigationDirectionForward animated: YES completion: nil];
}
}


因为 ViewController 嵌入在 PageViewController 中,所以您需要做的就是:

  • 检索父视图控制器并强制转换为相关类。
  • 使用父数据源属性获取下一个 ViewController。
  • 使用 SetParentViewController 方法设置并转换到下一页。

例如在 Xamarin,但功能类似斯威夫特等。下面的代码位于显示为页面的 ViewController 中的 Button Click 委托中:

var parent = ParentViewController as WelcomePageViewController;
var next = parent.DataSource.GetNextViewController(parent, this);


parent.SetViewControllers(new UIViewController[] { next }, UIPageViewControllerNavigationDirection.Forward, true, null);

2022年最简单的解决方案:

无论如何,你需要这个简单的函数:

var currentIndex: Int {
if let visibleViewController = viewControllers?.first,
let ci = pages.firstIndex(of: visibleViewController) {
return ci
}
else {
return 0
}
}

那么对任何页面进行动画制作都很容易:

public func go(toIndex: Int) {
setViewControllers(
[pages[toIndex]],
direction: ((currentIndex < toIndex) ? .forward : .reverse),
animated: true)
}

就是这样。