Можно ли программно перевернуть страницу в UIPageViewController
?
Можно ли программно повернуть страницу в UIPageViewController?
Ответы (17)
Да, это возможно с помощью метода:
- (void)setViewControllers:(NSArray *)viewControllers
direction:(UIPageViewControllerNavigationDirection)direction
animated:(BOOL)animated
completion:(void (^)(BOOL finished))completion;`
Это тот же метод, который используется для настройки первого контроллера представления на странице. Похоже, вы можете использовать его для перехода на другие страницы.
Интересно, почему viewControllers
- это массив, а не отдельный контроллер представления?
Это потому, что контроллер просмотра страницы может иметь «корешок» (как в iBooks), отображающий 2 страницы контента за раз. Если вы отображаете по одной странице контента за раз, просто передайте массив из 1 элемента.
Пример в Swift:
pageContainer.setViewControllers([displayThisViewController], direction: .Forward, animated: true, completion: nil)
Поскольку мне это тоже было нужно, я более подробно расскажу, как это сделать.
Примечание: я предполагаю, что вы использовали стандартную форму шаблона для создания своей структуры UIPageViewController
, в которой созданы как modelViewController
, так и dataViewController
, когда вы ее вызываете. Если вы не понимаете, что я написал - вернитесь и создайте новый проект, который использует 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!
}
Вот еще один пример кода для одностраничного представления. Реализация для 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];
Я мог полностью упустить что-то здесь, но это решение кажется намного проще, чем многие другие предложенные.
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)
}
}
}
}
Этот код у меня отлично сработал, спасибо.
Вот что я с ним сделал. Некоторые методы для перехода вперед или назад и один для перехода непосредственно к определенной странице. Это для 6-страничного документа в портретной ориентации. Он будет работать нормально, если вы вставите его в реализацию RootController шаблона pageViewController.
-(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];
}
}
Swift 4:
Мне нужно было перейти к следующему контроллеру, когда я нажимаю далее на контроллере представления pagecontroller, а также обновляю индекс pageControl, так что это было лучшее и наиболее простое решение для меня:
let pageController = self.parent as! PageViewController
pageController.setViewControllers([parentVC.orderedViewControllers[1]], direction: .forward, animated: true, completion: nil)
pageController.pageControl.currentPage = 1
А как насчет использования методов из источника данных?
UIViewController *controller = [pageViewController.dataSource pageViewController:self.pageViewController viewControllerAfterViewController:pageViewController.viewControllers.firstObject];
[pageViewController setViewControllers:@[controller] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
Аналогично обратному.
Однако использование этого вызова метода self.pageViewController setViewControllers не вызывает viewDidDissapear в viewController для страницы, которая была отклонена, тогда как ручное переключение страницы вызывает viewDidDissapear на отклоненной странице. Я считаю, что это причина моего крушения
«Количество предоставленных контроллеров представления (0) не соответствует количеству, необходимому (1) для запрошенного перехода»
В Swift:
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
}
}
}
Мне также нужна была кнопка для перехода влево на 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];
}
}
Вы можете сделать что-то очень похожее, чтобы создать правую кнопку навигации отсюда.
В случае если PAGE CONTROL для обозначения текущей страницы O && Вы хотите, чтобы страницы перемещались с помощью прокрутки, а также путем нажатия настраиваемых кнопок в Page ViewController, приведенное ниже может быть полезно.
//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;}
- (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];
Надеюсь это работает :)
Для одной страницы я только что отредактировал ответ @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];
}
Как отметил @samwize, способ разбивки на страницы заключается в использовании
setViewControllers:array
Вот как я это сделал: у меня есть источник данных и делегат отдельно. Вы можете вызвать этот метод и изменить только «следующее» представление. Раньше вам нужно было применить правила разбиения на страницы. То есть вам нужно проверить направление и следующий контроллер. Если вы используете этот метод со своим настраиваемым источником данных, массив отображаемых контроллеров не изменится (поскольку вы будете использовать настраиваемый массив в подклассе NSObject).
Используя ваш собственный источник данных, метод просто устанавливает новое представление (с определенным направлением). Поскольку вы по-прежнему будете контролировать страницы, которые будут отображаться при обычном пролистывании.
Я работаю над этим со вчерашнего дня, и я наконец-то заработал. Я не мог полностью использовать стандартный шаблон, потому что у меня есть три разных viewController как childviews:
1 - rootViewController;
2 - model;
3 - viewControllers
3.1 - VC1;
3.2 - VC2;
3.3 - VC3.
Note: all of VCs has Storyboard ID.
Мне нужна была кнопка триггера только в первом VC, поэтому я добавил одно свойство для pageViewController и модели:
#import <UIKit/UIKit.h>
#import "Model.h"
@interface VC1 : UIViewController
@property (nonatomic, strong) UIPageViewController *thePageViewController;
@property (nonatomic, strong) SeuTimePagesModel *theModel;
@end
Я переписал метод инициализации модели, чтобы передать раскадровку и 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;
}
Наконец, я добавил IBAction в свой vc1 для запуска метода pageViewController setViewControllers: direction: animated: завершения::
- (IBAction)go:(id)sender
{
VC2 *vc2 = [_model.pages objectAtIndex:1];
NSArray *viewControllers = @[vc2];
[_pageViewController setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
Примечание. Мне не нужно искать индексы венчурных капиталистов, моя кнопка переводит меня ко второму венчурному капиталу, но получить все индексы и отслеживать ваши дочерние просмотры несложно:
- (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];
}
Надеюсь, это вам поможет!
Вот решение, использующее методы UIPageViewControllerDataSource:
Код отслеживает текущий отображаемый контроллер представления в 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 в методах UIPageViewControllerDelegate: pageViewController: willTransitionToViewControllers: и pageViewController: didFinishAnimating: previousViewControllers: transitionCompleted: strong >.
- (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) вы можете программно переключиться на следующий или предыдущий контроллер с помощью UIPageViewControllerDataSouce strong> методы pageViewController: viewControllerBeforeViewController:, pageViewController: viewControllerAfterViewController:, чтобы получить следующий или предыдущий контроллер представления и перейти к нему, вызвав [self.pageViewController setViewControllers: @ [vc] direction: UIPageViewControllerNavigationDirectionForward анимировано: ДА, завершение: ноль];
- (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];
}
}
Поскольку ViewControllers встроены в PageViewController, все, что вам нужно сделать, это:
- Получите родительский контроллер представления и приведите его к соответствующему классу.
- Используйте это свойство родительского источника данных, чтобы получить следующий ViewController.
- Используйте метод SetParentViewControllers для установки и перехода на следующую страницу.
Пример в Xamarin, но функционально похож на Swift и т. Д. Следующий код будет в делегате нажатия кнопки в одном из ViewControllers, отображаемых в виде страницы:
var parent = ParentViewController as WelcomePageViewController;
var next = parent.DataSource.GetNextViewController(parent, this);
parent.SetViewControllers(new UIViewController[] { next }, UIPageViewControllerNavigationDirection.Forward, true, null);