代码之家  ›  专栏  ›  技术社区  ›  Benjamin Toueg

UIPageViewController和情节提要

  •  52
  • Benjamin Toueg  · 技术社区  · 11 年前

    我正在尝试从情节提要中专门配置UIPageViewController:

    enter image description here

    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页)。无用户 交互不再可用。
    8 回复  |  直到 8 年前
        1
  •  78
  •   Fattie    1 年前

    2023.Swift更新。

    2017答案。。。

    现在做这件事很容易 简单地使用情节提要 .

    这些“刷屏全屏教程”作为应用程序的“简介”流行了一段时间,所以我给下面的课打了电话 IntroPages .

    步骤1,制作 容器视图 即UIPageViewController。

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

    (注意:如果您不知道如何将容器视图“更改”为UIPageViewController,请向下滚动到该部分 “如何改变…” 在那个教程上!

    enter image description here )

    你可以制作容器 你想要什么形状 。与任何容器视图一样,它可以是全屏视图,也可以是屏幕的一小部分,无论您想要什么。

    步骤2,

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

    four controllers

    请注意,它们只是 坐在你的故事板上 ,不要将它们与任何内容联系起来。

    第三步,你必须 设置ID 这四页中的一页。“id1”、“id2”、“id3”和“id4”都可以。

    set the IDs

    第四步,复制粘贴!这是课程简介页面,

    import UIKit
    class IntroPages: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    
        // ** see note below about ".scroll" mode
    
        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。将类设置为 简介页面 .

    这就是一切——你完了。

    你只需在故事板上设置过渡样式,

    enter image description here

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


    关于使用正常、常用、“.roll”模式的重要注意事项。。。

    令人难以置信的是,奇怪的是,苹果已经从Xcode故事板中IB的下拉菜单中删除了正常的“.roll”选项。

    (在故事板上,它们只允许你选择奇怪的选项,比如“卷曲”,从来没有人使用过。)

    现在您必须在代码中完成这一操作,这很简单。添加这一行:

    class IntroPages: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
    
       required init?(coder: NSCoder) {
           super.init(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
       }
    
       etc, as above ... var pages = [UIViewController]()
    

    这很傻,但事情就是这样( More info .)

    你完了!现在。。。


    添加UIPageControl简介。。。

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

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

    因此,在故事板上,非常简单地拖动 分页控件 转到“简介”。

    笔记奇怪的是,在故事板中, 无法移动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) {}
    

    然后在情节提要中单击页面控件并拖动 值已更改 对该功能。

    这个 最简单的大纲版本 函数的

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

    然而 看见 本QA https://stackoverflow.com/questions/66545624 以便对此进行更全面的调查。

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

    你在路上。

        2
  •  22
  •   Fattie    10 年前

    对于希望看到工作页面滚动(向前/向后)的用户

    -(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];
        } 
    
        3
  •  11
  •   Community CDub    7 年前

    延伸 Joe Blow's answer 带有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
        }
    }
    

    在上阅读更多信息 using UIPageViewController with container view 带有故事板设置。

        4
  •  5
  •   Travis M.    8 年前

    故事板中似乎有很多关于UIPageViewController的问题。

    这是 some demo code 向您展示如何将故事板中的UIPageViewController用作独立的全屏视图或UIContainerView(如果您只想分页屏幕的一小部分)。

    enter image description here enter image description here enter image description here

        5
  •  0
  •   PLJNS    8 年前

    针对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
  •  0
  •   Aquila Sagitta    8 年前

    要使用@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]
        }
    
        7
  •  -1
  •   Rudolf Adamkovič    11 年前

    问题是你的重复使用不当 UIViewController 实例:

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

    我建议你喝一杯 NSMutableSet 这将成为一个可重复使用的池 UI视图控制器 实例。

    在里面 viewControllerBeforeViewController: 视图控制器前面的视图控制器: 搜索您的 NS可变集 使用 NSPredicate 查找 UI视图控制器 具有 parentViewController 等于 nil 。如果找到一个,则返回它。如果没有,则实例化一个新的,将其添加到 NS可变集 然后返回。

    完成测试并通过测试后,可以将池提取到自己的类中。

        8
  •  -1
  •   jrc    5 年前

    回答 斯威夫特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
        }
    }