【问题标题】:UIPageViewController and storyboardUIPageViewController 和故事板
【发布时间】:2013-08-26 06:24:27
【问题描述】:

我正在尝试从情节提要专门配置 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 页)。没有用户 互动不再可用。

【问题讨论】:

  • 这是我第一次看到 UIPageViewController 本身用于它自己的数据源和委托。似乎合乎逻辑,但我见过的每个教程都没有走这条路。希望你继续努力。
  • @BenjaminToueg,我很确定这里不需要“didMoveToParentViewController”。
  • 如果您对 容器视图 - tutorial 感到满意,这个老问题基本上很简单

标签: ios uistoryboard uipageviewcontroller xcode-storyboard


【解决方案1】:

2020 年。 Swift 已更新。

2017 年答案...

如今,只需使用 Storyboard 即可轻松做到这一点。

这种“滑动全屏教程”作为应用“介绍”流行了一段时间,所以我把下面的课程称为IntroPages。

第 1 步,制作一个 容器视图,它是一个 UIPageViewController。

如果是 iOS 新手,这里是container view tutorial。

(注意:如果您不知道如何将容器视图“更改”为 UIPageViewController,请向下滚动到该教程中的“如何更改...”部分! p>

)

您可以将容器制作成任何您想要的形状。与任何容器视图一样,它可以是全屏或屏幕的一小部分 - 随心所欲。

第 2 步,

制作四个简单、普通的视图控制器,它们可以是任何你想要的——图像、文本、表格,任何东西。 (示例中为紫色。)

请注意,它们只是坐在您的故事板上,不要将它们链接到任何东西。

第 3 步,您必须设置这四个页面的 ID。 “id1”、“id2”、“id3”、“id4”都可以。

第四步,复制粘贴!这是类 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
    }
}

(查看 cmets - 有您喜欢的 looping 或 linear 分页代码。)

在故事板上查看 UIPageViewController。将类设置为IntroPages。

这就是它的全部 - 你已经完成了。

您只需在情节提要上设置过渡样式,

你很可能想要“滚动”,而不是另一个。

你已经完成了!现在...


添加UIPageControl的介绍...

您将UIPageControl 添加到最高级别的包装类中,即上图示例中的“Intro”。

(因此,令人惊讶的是,不在页面视图控制器中,而不是在“IntroPages”中。)

因此,在情节提要上,非常简单地将UIPageControl 拖到“简介”上。

注意!奇怪的是,在情节提要中,您无法移动 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)
}

但是请参阅此 QAProperly solve the new fast-click animation mode / avoid half-page bug for the new UIPageControl as per 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
}

由于 Apple 错误/异常行为,在 IntroPages 中还添加了以下内容:

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) }
    }
} 

你在路上。

【讨论】:

  • 很好——这太棒了!现在它所需要的只是页面指示器和一种禁用页面卷曲效果的方法,以便简单地滑入下一个视图。
  • 这样我们就不能以编程方式将 transitionStyle 更改为 Scroll From page curl。为了解决这个问题,拖动 UIPageVIewController ,将其连接到容器(作为嵌入)并在 Attributed Inspector 中更改过渡样式。不要忘记更改类名。
  • @JoeBlow 您好,我已按照您的指示进行操作。 IntroPages 已显示,但我无法滑动它!!。我错过了什么吗??
  • 是否可以在情节提要本身中处理“页面”数组类型的实现?如果我在流程中有 15 个视图控制器,实例化所有它们并存储在数组中可能会导致内存问题并且应用程序可能会崩溃.....
  • @Satyam 很难想象你会遇到 20 或 30 个屏幕的内存问题 - 继续吧!
【解决方案2】:

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
    }
}

【讨论】:

    【解决方案3】:

    要使用@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]
        }
    

    【讨论】:

      【解决方案4】:

      Storyboard 中似乎有很多关于 UIPageViewController 的问题。

      这里是some demo code,向您展示如何将情节提要中的 UIPageViewController 用作独立的全屏视图或 UIContainerView,如果您只想对屏幕的一小部分区域进行分页。

      【讨论】:

        【解决方案5】:

        为 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
            }
        }
        

        【讨论】:

          【解决方案6】:

          使用 UIPageViewController 类的 Swift 代码扩展 Joe Blow's answer:

          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
              }
          }
          

          阅读更多关于 using UIPageViewController with container view 的故事板设置。

          【讨论】:

          • 这很方便,谢谢。需要注意的是,以上是针对其他答案中提到的“轮回”分页(向右分页最终会从头开始,而不是到达结尾并停止)。
          • 优秀,@samwize
          【解决方案7】:

          对于想要查看工作页面滚动(向前/向后)的人

          -(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];
              } 
          

          【讨论】:

          • 我们有什么方法可以检查页面视图控制器中的当前页面索引号吗?
          【解决方案8】:

          问题在于您不正确地重用UIViewController 实例:

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

          我建议您有一个 NSMutableSet 用作可重复使用的 UIViewController 实例池。

          在viewControllerBeforeViewController: 和viewControllerBeforeViewController: 中搜索您的NSMutableSet,使用NSPredicate 找到UIViewController,其中parentViewController 等于nil。如果你找到了,把它还给你。如果没有,则实例化一个新的,将其添加到NSMutableSet,然后返回。

          当您完成并且您的测试通过时,您可以将池提取到它自己的类中。

          【讨论】:

          • 这似乎不正确,鲁道夫。它仅适用于普通数组。完全没有问题。所有四个 VC 都在“那里”——没问题。
          • @JoeBlow 是的,数组或集合,由你决定。重要的是算法,尤其是UIPageViewController变成parentViewController这一事实。基于此,您可以过滤数组/集合并找到未使用的 UIViewController 实例。
          猜你喜欢
          • 2012-04-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-18
          • 2013-03-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多