UIPageViewController и раскадровка

Я пытаюсь настроить UIPageViewController СПЕЦИАЛЬНО из раскадровки:

введите здесь описание изображения

TutorialPageViewController.h

#import <UIKit/UIKit.h>
@interface TutorialPageViewController : UIPageViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
@end

TutorialPageViewController.m

#import "TutorialPageViewController.h"

@interface TutorialPageViewController ()
@property (assign, nonatomic) NSInteger index;
@end

@implementation TutorialPageViewController
{
    NSArray *myViewControllers;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.delegate = self;
    self.dataSource = self;
    [self didMoveToParentViewController:self];
    UIStoryboard *tutorialStoryboard = [UIStoryboard storyboardWithName:@"TutorialStoryboard" bundle:[NSBundle mainBundle]];
    UIViewController *tuto1 = [tutorialStoryboard instantiateViewControllerWithIdentifier:@"TutorialPageViewController_1"];
    UIViewController *tuto2 = [tutorialStoryboard instantiateViewControllerWithIdentifier:@"TutorialPageViewController_2"];

    myViewControllers = @[tuto1, tuto2, tuto1, tuto2];
    self.index = 0;

    [self setViewControllers:@[tuto1] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}

- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {
    return myViewControllers[index];
}

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

    NSUInteger index = self.index;

    if (index == 0) { return nil; }

    // Decrease the index by 1 to return
    index--;
    return [self viewControllerAtIndex:index];
}

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

    NSUInteger index = self.index;
    index++;
    if (index > [myViewControllers count]) { return nil; }

    return [self viewControllerAtIndex:index];
}

- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController {
    // The number of items reflected in the page indicator.
    return [myViewControllers count];
}

- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController {
    // The selected item reflected in the page indicator.
    return 0;
}

@end

Проблема в том, что...

  • Первая страница хорошо отображается с индикатором страницы. Во время смахивания,
  • Я хорошо вижу вторую страницу.
  • Как только переход завершается, я получаю черный экран (с индикатором страницы, правильно отображающим номер страницы 2). Взаимодействие с пользователем больше недоступно.

person Benjamin Toueg    schedule 23.08.2013    source источник
comment
Я впервые вижу, как сам UIPageViewController используется для собственного источника данных и делегата. Кажется логичным, но каждый учебник, который я видел, не идет по этому пути. Надеюсь, ты будешь работать.   -  person Travis M.    schedule 08.08.2014
comment
@BenjaminToueg, я уверен, что didMoveToParentViewController здесь не нужен.   -  person Fattie    schedule 24.09.2014
comment
Этот старый вопрос по существу прост, если вы знакомы с представлениями контейнеровучебник   -  person Fattie    schedule 30.05.2017


Ответы (8)


2020. Свифт обновлен.

2017 ответ...

В настоящее время это очень легко сделать, просто используя раскадровку.

Такого рода полноэкранные обучающие программы какое-то время были популярны в качестве заставок к приложениям, поэтому я назвал класс ниже IntroPages.

Шаг 1. Создайте контейнерное представление, которое является UIPageViewController.

Если вы новичок в iOS, вот руководство по представлению контейнера.

(Примечание: если вы не знаете, как изменить представление контейнера на UIPageViewController, прокрутите вниз до раздела Как изменить... в этом руководстве!

введите здесь описание изображения )

Вы можете придать контейнеру любую форму. Как и любое представление контейнера, оно может быть полноэкранным или небольшой частью экрана — как вам угодно.

Шаг 2,

Создайте четыре простых, обычных контроллера представления, которые могут быть чем угодно — изображениями, текстом, таблицами, чем угодно. (Фиолетовый в примере.)

четыре контроллера

Обратите внимание, что они просто находятся на вашей раскадровке, не связывая их ни с чем.

Шаг 3: вы должны установить идентификаторы этих четырех страниц. id1, id2, id3, id4 в порядке.

установить идентификаторы

Шаг 4, скопируйте и вставьте! Вот класс IntroPages,

class IntroPages: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var pages = [UIViewController]()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
        self.dataSource = self

        let p1: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id1")
        let p2: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id2")
        let p3: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "id3")

        // etc ...

        pages.append(p1)
        pages.append(p2)
        pages.append(p3)

        // etc ...

        setViewControllers([p1], direction: UIPageViewController.NavigationDirection.forward, animated: false, completion: nil)
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController)-> UIViewController? {
       
        let cur = pages.firstIndex(of: viewController)!

        // if you prefer to NOT scroll circularly, simply add here:
        // if cur == 0 { return nil }

        var prev = (cur - 1) % pages.count
        if prev < 0 {
            prev = pages.count - 1
        }
        return pages[prev]
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController)-> UIViewController? {
         
        let cur = pages.firstIndex(of: viewController)!

        // if you prefer to NOT scroll circularly, simply add here:
        // if cur == (pages.count - 1) { return nil }

        let nxt = abs((cur + 1) % pages.count)
        return pages[nxt]
    }

    func presentationIndex(for pageViewController: UIPageViewController)-> Int {
        return pages.count
    }
}

(Посмотрите на комментарии — там есть код для зацикливания или линейного пейджинга по вашему выбору.)

В раскадровке посмотрите на UIPageViewController. Установите класс IntroPages.

Вот и все — готово.

Вы просто устанавливаете стиль перехода на раскадровке,

введите здесь описание изображения

очень вероятно, что вам нужен Scroll, а не другой.

Готово! И сейчас ...


Введение в добавление UIPageControl...

Вы добавляете UIPageControl в класс-оболочку самого высокого уровня, Intro в приведенном выше примере изображения.

(Так что, как ни странно, нет в контроллере просмотра страницы, не в IntroPages.)

Таким образом, на раскадровке очень просто перетащите UIPageControl на Intro.

Примечание! Как ни странно, в раскадровках вы не можете перемещать UIPageControl.

Когда вы перетаскиваете элемент управления страницы на контроллер представления, он просто находится в фиксированном месте. Это совершенно странно, но это так. Не тратьте время на то, чтобы переместить его :)

Из Intro вам нужно будет получить доступ к контроллеру просмотра страниц (IntroPages) обычным способом:

class Intro: UIViewController {

    @IBOutlet var pageControl: UIPageControl!
    var pageViewController: IntroPages!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "segueIntroPages" {
            // if new to container views, identifier explained here:
            // https://stackoverflow.com/a/23403979/294884
            pageViewController = (segue.destination as! IntroPages)
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.bringSubviewToFront(pageControl)
        pageControl.currentPage = 0
    }

Обратите внимание на эту ключевую строку:

    view.bringSubviewToFront(pageControl)

Добавьте эту функцию

@IBAction func userDidChangePageControl(_ sender: UIPageControl) {}

а затем в раскадровке щелкните элемент управления страницей и перетащите valueChanged в эту функцию.

Самая простая версия функции:

@IBAction func userDidChangePageControl(_ sender: UIPageControl) {
    pageViewController.setViewControllers(
        [pageViewController.pages[newIndex]],
        direction: (pageViewController.currentIndex < newIndex)
                     ? .forward : .reverse,
        animated: true, completion: nil)
}

Однако см. этот контроль качества Правильно решите новый режим анимации быстрого щелчка/избегайте половины страницы ошибка для нового UIPageControl согласно Apple для более полного исследования этого.

в IntroPages добавить свойство...

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

в IntroPages также добавьте следующее, чтобы, когда пользователь проводит пальцем по экрану, UIPageControl знал, что делать:

func pageViewController(_ pageViewController: UIPageViewController, 
       didFinishAnimating finished: Bool,
       previousViewControllers: [UIViewController], 
       transitionCompleted completed: Bool) {
    (parent as? Intro)?.pageControl.currentPage = currentIndex
}

в IntroPages также добавьте следующее из-за ошибки / необычного поведения Apple:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let subViews = view.subviews
    var scrollView: UIScrollView? = nil
    var pageControl: UIPageControl? = nil
    
    // maintain this code order...
    for view in subViews {
        if view.isKind(of: UIScrollView.self) {
            scrollView = view as? UIScrollView
        }
        else if view.isKind(of: UIPageControl.self) {
            pageControl = view as? UIPageControl
        }
    }
    
    // maintain this code order...
    if (scrollView != nil && pageControl != nil) {
        scrollView?.frame = view.bounds
        if let pageControl = pageControl {
                    view.bringSubviewToFront(pageControl) }
    }
} 

Вы в пути.

person Fattie    schedule 24.09.2014
comment
Красиво - это работает фантастически! Теперь все, что ему нужно, это индикаторы страниц и способ отключить эффект скручивания страницы для простого перехода к следующему просмотру. - person Drew; 17.04.2015
comment
Таким образом, мы не можем программно изменить transitionStyle на Scroll From page curl. Чтобы решить эту проблему, перетащите UIPageVIewController, подключите его к контейнеру (как встроенный) и в Attributed Inspector измените стиль перехода. Не забудьте изменить имя класса. - person Ajumal; 21.04.2015
comment
@JoeBlow Привет, я следовал твоим инструкциям. IntroPages появились, но я не могу провести их !!. Я что-то упускаю ?? - person Tai Dao; 12.09.2015
comment
Можно ли обрабатывать реализацию массива страниц в самой раскадровке? Если у меня есть 15 контроллеров представления в потоке, создание экземпляров всех из них и сохранение в массиве может привести к проблемам с памятью, и приложение может рухнуть..... - person Satyam; 14.05.2018
comment
@Satyam Невероятно, что у вас будут проблемы с памятью с 20 или 30 экранами - просто вперед! - person Fattie; 14.05.2018
comment
@Fattie, ты знаешь, как продвигаться программно? Я думаю, что мне нужно уведомление/наблюдатель, но я не уверен, как это сделать или как правильно вызвать функцию setViewControllers из действия в родительском VC. Я написал здесь вопрос, если у вас есть время взглянуть: inside-a-co" title="swift, как программно продвигать uipageviewcontroller, который находится внутри co">stackoverflow.com/questions/55198627/ , я был бы очень признателен. - person CristianMoisei; 16.03.2019
comment
@drew надеюсь, что годы были для вас хорошими :) Для Дрю или тех, кто читает, я добавил эссе о том, как начать работу с UIPageControl (точки внизу). - person Fattie; 09.03.2021

Для тех, кто хочет видеть работающую прокрутку страницы (вперед/назад)

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
     viewControllerBeforeViewController:(UIViewController *)viewController
  {
     NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];
     // get the index of the current view controller on display

     if (currentIndex > 0)
     {
        return [myViewControllers objectAtIndex:currentIndex-1];
        // return the previous viewcontroller
     } else
     {
         return nil;
         // do nothing
     }
  }
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
 viewControllerAfterViewController:(UIViewController *)viewController
  {
     NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];
     // get the index of the current view controller on display
     // check if we are at the end and decide if we need to present
     // the next viewcontroller
     if (currentIndex < [myViewControllers count]-1)
     {
        return [myViewControllers objectAtIndex:currentIndex+1];
        // return the next view controller
     } else
     {
        return nil;
        // do nothing
     }
  }

Просто чтобы добавить к этому замечательному ответу EditOR, вот что вы делаете, если предпочитаете «круговое» разбиение по страницам: по-прежнему используя ту же технику EditOR

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
        viewControllerBeforeViewController:(UIViewController *)viewController
    {
    NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];

    --currentIndex;
    currentIndex = currentIndex % (myViewControllers.count);
    return [myViewControllers objectAtIndex:currentIndex];
    }

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
        viewControllerAfterViewController:(UIViewController *)viewController
    {
    NSUInteger currentIndex = [myViewControllers indexOfObject:viewController];

    ++currentIndex;
    currentIndex = currentIndex % (myViewControllers.count);
    return [myViewControllers objectAtIndex:currentIndex];
    } 
person EditOR    schedule 28.08.2014
comment
Есть ли у нас какие-либо средства для проверки текущего номера индекса страницы в контроллере просмотра страницы? - person G.Abhisek; 28.12.2015

Расширение ответа Джо Блоу с помощью кода Swift для класса UIPageViewController:

import UIKit

class MyPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var pages = [UIViewController]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.delegate = self
        self.dataSource = self

        let page1: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page1")
        let page2: UIViewController! = storyboard?.instantiateViewControllerWithIdentifier("page2")

        pages.append(page1)
        pages.append(page2)

        setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
    }

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.indexOf(viewController)!
        let previousIndex = abs((currentIndex - 1) % pages.count)
        return pages[previousIndex]
    }

    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.indexOf(viewController)!
        let nextIndex = abs((currentIndex + 1) % pages.count)
        return pages[nextIndex]
    }

    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
        return pages.count
    }

    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
        return 0
    }
}

Узнайте больше об использовании UIPageViewController. с представлением контейнера с настройкой раскадровки.

person samwize    schedule 13.10.2015
comment
Это удобно, спасибо. Следует отметить, что вышеизложенное предназначено для кругового пейджинга, как упоминалось в других ответах (пейджинг вправо в конечном итоге начнется сначала, а не дойдет до конца и не остановится). - person Clifton Labrum; 05.02.2016
comment
выдающийся, @samwize - person Fattie; 03.04.2016

Кажется, есть много вопросов относительно UIPageViewController в Storyboard.

Вот демонстрационный код, чтобы показать вам, как вы можете использовать UIPageViewController в раскадровке в качестве автономного полноэкранного просмотра. или как UIContainerView, если вы хотите просмотреть только небольшую область экрана.

введите здесь описание изображения введите здесь описание изображения введите здесь описание изображения

person William T.    schedule 25.09.2015

Обновлено для Swift 3:

class YourPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var pages = [UIViewController]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.delegate = self
        self.dataSource = self

        let page1: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "page1")
        let page2: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "page2")

        pages.append(page1)
        pages.append(page2)

        setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
        let previousIndex = abs((currentIndex - 1) % pages.count)
        return pages[previousIndex]
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
        let nextIndex = abs((currentIndex + 1) % pages.count)
        return pages[nextIndex]
    }

    func presentationIndex(for pageViewController: UIPageViewController) -> Int {
        return pages.count
    }
}
person PLJNS    schedule 17.09.2016

Чтобы иметь бесконечную прокрутку назад, используя ответ @samwize, вам нужно добавить условие для проверки отрицательных значений. В противном случае вы просто переключаетесь между первой и второй страницей. Это необходимо только в том случае, если вы планируете иметь более двух страниц.

func pageViewController(_ pageViewController: UIPageViewController,
                            viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
        var previousIndex = (currentIndex - 1) % pages.count
        if previousIndex < 0 {previousIndex = pages.count - 1}
        return pages[previousIndex]
    }
person Aquila Sagitta    schedule 06.01.2017

Проблема в том, что вы неправильно повторно используете экземпляры UIViewController:

myViewControllers = @[tuto1, tuto2, tuto1, tuto2];

Я бы посоветовал вам иметь NSMutableSet, который служил бы пулом многократно используемых экземпляров UIViewController.

В viewControllerBeforeViewController: и viewControllerBeforeViewController: найдите NSMutableSet, используя NSPredicate, чтобы найти UIViewController с parentViewController равным nil. Если найдете, верните. Если нет, создайте новый экземпляр, добавьте его в NSMutableSet и затем верните.

Когда вы закончите и ваши тесты пройдены, вы можете извлечь пул в свой собственный класс.

person Rudolf Adamkovič    schedule 18.01.2014
comment
Это не кажется правильным, Рудольф. Он отлично работает только с обычным массивом. Совершенно никаких проблем. Все четыре венчурных капиталиста просто есть - это не проблема. - person Fattie; 24.09.2014
comment
@JoeBlow Да, массив или набор, решать вам. Важен алгоритм, особенно тот факт, что UIPageViewController становится parentViewController. Исходя из этого, вы можете отфильтровать массив/набор и найти неиспользуемый экземпляр UIViewController. - person Rudolf Adamkovič; 25.09.2014

Ответ для Swift 5:

import UIKit

class MyPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    var allViewControllers = [UIViewController]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.dataSource = self

        let vc1: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "viewController1")
        let vc2: UIViewController! = storyboard?.instantiateViewController(withIdentifier: "viewController2")
        allViewControllers = [vc1, vc2]

        self.setViewControllers([vc1], direction: UIPageViewController.NavigationDirection.forward, animated: false)
    }

    // UIPageViewControllerDataSource

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
    {
        let currentIndex = allViewControllers.firstIndex(of: viewController)!
        return currentIndex == 0 ? nil : allViewControllers[currentIndex-1]
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
    {
        let currentIndex = allViewControllers.firstIndex(of: viewController)!
        return currentIndex == allViewControllers.count-1 ? nil : allViewControllers[currentIndex+1]
    }

    func presentationCount(for pageViewController: UIPageViewController) -> Int
    {
      return allViewControllers.count
    }

    func presentationIndex(for pageViewController: UIPageViewController) -> Int
    {
        return 0
    }
}
person jrc    schedule 03.09.2019