UIPageViewController: возвращает текущее видимое представление
как вы знаете, что текущая страница / вид отображается внутри UIPageViewController
?
Я переопределил viewDidAppear
метод моих дочерних представлений, так что они отправляют идентификатор родительскому представлению в их viewDidAppear
метод.
однако проблема заключается в следующем: я не могу надежно использовать этот идентификатор в качестве идентификатора для отображаемой страницы. потому что если пользователь переворачивает страницу, но на полпути решает остановить поворот и положить страницу обратно,viewDidAppear
уже были названы. (вид видна за скрученной страницей).
возможно, я должен переключиться на новый идентификатор, если текущий вид исчезнет. Но мне интересно, нет ли более простого способа вернуть вид, который в настоящее время виден?
19 ответов:
вы должны вручную отслеживать текущую страницу. Делегат метод
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
скажет вам, когда обновить эту переменную. Последний аргумент методаtransitionCompleted:
может сказать вам, завершил ли пользователь переход поворота страницы или нет.
по состоянию на iOS 6 я обнаружил, что
viewControllers
свойство UIPageViewController постоянно обновляется, так что он всегда будет содержать один контроллер представления, представляющий текущую страницу, и ничего больше. Таким образом, вы можете получить доступ к текущей странице, позвонивviewControllers[0]
(при условии, что вы показываете только один контроллер вида за раз).массив viewController обновляется только после того, как страница "блокируется", поэтому, если пользователь решит частично открыть следующую страницу, она не станет "текущей" страница, если они не завершат переход.
Если вы хотите отслеживать "номера страниц", назначьте контроллерам представления значение индекса при их создании с помощью методов источника данных UIPageViewController.
например:
-(void)autoAdvance { UIViewController *currentVC = self.viewControllers[0]; NSUInteger currentIndex = [myViewControllers indexOfObject:currentVC]; if ( currentIndex >= (myViewControllers.count-1) ) return; [self setViewControllers:@[myViewControllers[ currentIndex+1 ]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil]; } -(NSInteger)presentationIndexForPageViewController: (UIPageViewController *)pageViewController { // return 0; UIViewController *currentVC = self.viewControllers[0]; NSUInteger currentIndex = [myViewControllers indexOfObject:currentVC]; return currentIndex; }
но обратите внимание на комментарии что это ненадежно.
к сожалению, все вышеперечисленные методы мне не помогли. Тем не менее, я нашел решение с помощью тегов. Может быть, это не самое лучшее, но это работает и надеюсь, что это поможет кому-то:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed { if (completed) { int currentIndex = ((UIViewController *)self.pageViewController.viewControllers.firstObject).view.tag; self.pageControl.currentPage = currentIndex; } }
В Swift: (спасибо @Jessy)
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed else { return } self.pageControl.currentPage = pageViewController.viewControllers!.first!.view.tag }
пример:суть
основываясь на ответе Оле...
вот как я реализовал 4 метода для отслеживания текущей страницы и обновления индикатора страницы до правильного индекса:
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController{ return (NSInteger)[self.model count]; } - (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController{ return (NSInteger)self.currentIndex; } - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{ SJJeanViewController* controller = [pendingViewControllers firstObject]; self.nextIndex = [self indexOfViewController:controller]; } - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{ if(completed){ self.currentIndex = self.nextIndex; } self.nextIndex = 0; }
Решение ниже работает для меня.
Apple может избежать много хлопот, сделав родной uipageviewcontroller просмотр прокрутки разбиение на страницы более настраиваемым. Мне пришлось прибегнуть к наложению нового UIView и UIPageControl только потому, что родная разбиение на страницы UIPageViewController не будет поддерживать прозрачный фон или перемещение в рамке представления.
- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed { if (!completed) { return; } NSUInteger currentIndex = [[self.pageViewController.viewControllers lastObject] index]; self.pageControl.currentPage = currentIndex; }
Swift 4
нет ненужного кода. 3 способа сделать это. Используя UIPageViewControllerDelegate метод.
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed else { return } // using content viewcontroller's index guard let index = (pageViewController.viewControllers?.first as? ContentViewController)?.index else { return } // using viewcontroller's view tag guard let index = pageViewController.viewControllers?.first?.view.tag else { return } // switch on viewcontroller guard let vc = pageViewController.viewControllers?.first else { return } let index: Int switch vc { case is FirstViewController: index = 0 case is SecondViewController: index = 1 default: index = 2 } }
я отслеживаю индекс страницы, используя небольшую функцию и указывая pageIndex как статический NSInteger.
-(void) setPageIndex { DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0]; pageIndex = [self.modelController indexOfViewController:theCurrentViewController]; }
, а вызов
[self setPageIndex];
внутри функции, указанной Ole, а также после обнаружения изменения ориентации.
Я сначала использовал решение кори, но он не работал на iOS5, а затем использовал,
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{ if(completed) { _currentViewController = [pageViewController.viewControllers lastObject]; } }
он пытался переключаться через разные страницы, и сейчас он работает хорошо.
к сожалению, ничего выше не работает для меня.
у меня есть два контроллера вида, и когда я немного (около 20px) прокручиваю последний вид назад, он запускает делегат:
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
и говоря, что текущая страница (индекс) является
0
что неправильно.использование делегата внутри дочернего viewController что-то вроде:
- (void)ViewController:(id)VC didShowWithIndex:(long)page; // and a property @property (nonatomic) NSInteger index;
это срабатывает внутри
viewDidAppear
как:- (void)viewDidAppear:(BOOL)animated { ... [self.delegate ViewController:self didShowWithIndex:self.index]; }
работал для меня.
это работает для меня надежно
У меня есть пользовательский UIPageController. Этот pageController.currentPage обновляется из отображаемого UIViewController в viewWillAppear
var delegate: PageViewControllerUpdateCurrentPageNumberDelegate? init(delegate: PageViewControllerUpdateCurrentPageNumberDelegate ){ self.delegate = delegate super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewWillAppear(animated: Bool) { if delegate != nil { self.delegate!.upateCurrentPageNumber(0) //(0) is the pageNumber corresponding to the displayed controller } } //In the pageViewController protocol PageViewControllerUpdateCurrentPageNumberDelegate { func upateCurrentPageNumber(currentPageIndex: Int) } create the view display controllers initializing with the delegate orderedViewControllers = { return [ IntroductionFirstPageViewController(delegate: self), IntroductionSecondPageViewController(delegate: self), IntroductionThirdPageViewController(delegate: self) ] }() the function implementing the protocol func upateCurrentPageNumber(currentPageIndex: Int){ pageControl.currentPage = currentPageIndex }
Спасибо за ваш ответ Ребята, я столкнулся с подобной проблемой, пришлось хранить индекс. Я немного изменяю свой код, вставьте его ниже:
- (MenuListViewController *)viewControllerAtIndex:(NSInteger)index { if (_menues.count < 1) return nil; // MenuListViewController *childViewController = [MenuListViewController initWithSecondSetFakeItems]; MenuListViewController *childViewController = self.menues[index]; childViewController.index = index; return childViewController; } #pragma mark - Page View Controller Data Source - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed{ if (completed) { NSUInteger currentIndex = ((MenuListViewController *)self.pageController.viewControllers.firstObject).index; NSLog(@"index %lu", (unsigned long)currentIndex); } } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { NSUInteger index = [(MenuListViewController *)viewController index]; if (index == 0) return nil; index --; return [self viewControllerAtIndex:index]; } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { NSUInteger index = [(MenuListViewController *)viewController index]; index ++; if (index == _menues.count) return nil; return [self viewControllerAtIndex:index]; }
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed { NSLog(@"Current Page = %@", pageViewController.viewControllers); UIViewController *currentView = [pageViewController.viewControllers objectAtIndex:0]; if ([currentView isKindOfClass:[FirstPageViewController class]]) { NSLog(@"First View"); } else if([currentView isKindOfClass:[SecondPageViewController class]]) { NSLog(@"Second View"); } else if([currentView isKindOfClass:[ThirdViewController class]]) { NSLog(@"Third View"); } } //pageViewController.viewControllers always return current visible View ViewController
ниже демонстрационный код (в Swift 2), который демонстрирует, как это делается путем реализации простой учебник swiper изображения. Комментарии в самом коде :
import UIKit /* VCTutorialImagePage represents one page show inside the UIPageViewController. You should create this page in your interfacebuilder file: - create a new view controller - set its class to VCTutorialImagePage - sets its storyboard identifier to "VCTutorialImagePage" (needed for the loadView function) - put an imageView on it and set the contraints (I guess to top/bottom/left/right all to zero from the superview) - connect it to the "imageView" outlet */ class VCTutorialImagePage : UIViewController { //image to display, configure this in interface builder @IBOutlet weak var imageView: UIImageView! //index of this page var pageIndex : Int = 0 //loads a new view via the storyboard identifier static func loadView(pageIndex : Int, image : UIImage) -> VCTutorialImagePage { let storyboard = UIStoryboard(name: storyBoardHome, bundle: nil) let vc = storyboard.instantiateViewControllerWithIdentifier("VCTutorialImagePage") as! VCTutorialImagePage vc.imageView.image = image vc.pageIndex = pageIndex return vc } } /* VCTutorialImageSwiper takes an array of images (= its model) and displays a UIPageViewController where each page is a VCTutorialImagePage that displays an image. It lets you swipe throught the images and will do a round-robbin : when you swipe past the last image it will jump back to the first one (and the other way arround). In this process, it keeps track of the current displayed page index */ class VCTutorialImageSwiper: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate { //our model = images we are showing let tutorialImages : [UIImage] = [UIImage(named: "image1")!, UIImage(named: "image2")!,UIImage(named: "image3")!,UIImage(named: "image4")!] //page currently being viewed private var currentPageIndex : Int = 0 { didSet { currentPageIndex=cap(currentPageIndex) } } //next page index, temp var for keeping track of the current page private var nextPageIndex : Int = 0 //Mark: - life cylce override func viewDidLoad() { super.viewDidLoad() //setup page vc dataSource=self delegate=self setViewControllers([pageForindex(0)!], direction: .Forward, animated: false, completion: nil) } //Mark: - helper functions func cap(pageIndex : Int) -> Int{ if pageIndex > (tutorialImages.count - 1) { return 0 } if pageIndex < 0 { return (tutorialImages.count - 1) } return pageIndex } func carrouselJump() { currentPageIndex++ setViewControllers([self.pageForindex(currentPageIndex)!], direction: .Forward, animated: true, completion: nil) } func pageForindex(pageIndex : Int) -> UIViewController? { guard (pageIndex < tutorialImages.count) && (pageIndex>=0) else { return nil } return VCTutorialImagePage.loadView(pageIndex, image: tutorialImages[pageIndex]) } func indexForPage(vc : UIViewController) -> Int { guard let vc = vc as? VCTutorialImagePage else { preconditionFailure("VCPagImageSlidesTutorial page is not a VCTutorialImagePage") } return vc.pageIndex } //Mark: - UIPageView delegate/datasource func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { return pageForindex(cap(indexForPage(viewController)+1)) } func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { return pageForindex(cap(indexForPage(viewController)-1)) } func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) { nextPageIndex = indexForPage(pendingViewControllers.first!) } func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if !finished { return } currentPageIndex = nextPageIndex } func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int { return tutorialImages.count } func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int { return currentPageIndex } }
Я использую
view.tag
в течение некоторого времени, пытаясь отслеживать текущую страницу было слишком сложно.в этом коде индекс хранится внутри
tag
собственность каждогоview
и используется для извлечения следующего или предыдущего VC. С помощью этого метода также можно создать бесконечный свиток. Проверьте комментарий в коде, чтобы просмотреть это решение, а также:extension MyPageViewController: UIPageViewControllerDataSource { func viewControllerWithIndex(var index: Int) -> UIViewController! { let myViewController = storyboard?.instantiateViewControllerWithIdentifier("MyViewController") as MyViewController if let endIndex = records?.endIndex { if index < 0 || index >= endIndex { return nil } // Instead, We can normalize the index to be cyclical to create infinite scrolling // if index < 0 { index += endIndex } // index %= endIndex } myViewController.view.tag = index myViewController.record = records?[index] return myViewController } func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? { let index = viewController.view?.tag ?? 0 return viewControllerWithIndex(index + 1) } func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? { let index = viewController.view?.tag ?? 0 return viewControllerWithIndex(index - 1) } func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int { return records?.count ?? 0 } func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int { return (pageViewController.viewControllers.first as? UIViewController)?.view.tag ?? 0 } }
У меня есть массив viewControllers, который я показываю в UIPageViewController.
extension MyViewController: UIPageViewControllerDataSource { func presentationCount(for pageViewController: UIPageViewController) -> Int { return self.viewControllers.count } func presentationIndex(for pageViewController: UIPageViewController) -> Int { return self.currentPageIndex } } extension MyViewController: UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if !completed { return } guard let viewController = previousViewControllers.last, let index = indexOf(viewController: viewController) else { return } self.currentPageIndex = index } fileprivate func indexOf(viewController: UIViewController) -> Int? { let index = self.viewControllers.index(of: viewController) return index } }
важная вещь, чтобы отметить здесь заключается в том, что setViewControllers метод UIPageViewController не дает никакого обратного вызова делегата. Обратные вызовы делегата представляют только действия касания пользователя в UIPageViewController.
это решение я придумал:
class DefaultUIPageViewControllerDelegate: NSObject, UIPageViewControllerDelegate { // MARK: Public values var didTransitionToViewControllerCallback: ((UIViewController) -> Void)? // MARK: Private values private var viewControllerToTransitionTo: UIViewController! // MARK: Methods func pageViewController( _ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController] ) { viewControllerToTransitionTo = pendingViewControllers.last! } func pageViewController( _ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool ) { didTransitionToViewControllerCallback?(viewControllerToTransitionTo) } }
использование:
let pageViewController = UIPageViewController() let delegate = DefaultUIPageViewControllerDelegate() delegate.didTransitionToViewControllerCallback = { pageViewController.title = .title } pageViewController.title = viewControllers.first?.title pageViewController.delegate = delegate
убедитесь в том, чтобы установить первоначальное название
самый простой способ приблизиться к этому IMHO-использовать PageControl для хранения потенциального результата перехода, а затем вернуться, если переход был отменен. Это означает, что элемент управления страницей изменяется, как только пользователь начинает прокручивать, что меня устраивает. Это требует, чтобы у вас был свой собственный массив UIViewControllers (в этом примере называется
allViewControllers
)func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { if let index = self.allViewControllers.index(of: pendingViewControllers[0]) { self.pageControl.currentPage = index } } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if !completed, let previousIndex = self.allViewControllers.index(of: previousViewControllers[0]) { self.pageControl.currentPage = previousIndex } }
отслеживание нашего текущего номера страницы в методах делегата:
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController
мы используем вспомогательный getVC, чтобы обновить наш номер страницы и возвращает следующий/предыдущий ВК. Это хорошее место для этого, потому что getVC вызывается только в том случае, если есть следующий или предыдущий vc.
///Helper func getVC(for pgNum: Int) -> BasePageVC { let vc = storyboard?.instantiateViewController(withIdentifier: "page"+String(pgNum)) as! BasePageVC vc.pageNumber = pgNum self.currentPage = pgNum vc.data = self.data return vc } /// Next Page func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let vc = viewController as? BasePageVC else { fatalError("Vc is not BasePageViewController in viewControllerAfter") } let nextPage = vc.pageNumber! + 1 guard nextPage < self.data.pages.count - 1 else { return nil } return getVC(for: nextPage) } /// Previous Page func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let vc = viewController as? BasePageVC else { fatalError("Vc is not BasePageViewController in viewControllerAfter") } let prevPage = vc.pageNumber! - 1 guard prevPage >= 0 else { return nil } return getVC(for: prevPage) }