Iphone UIPageViewController,如何在不破坏数据源指定顺序的情况下正确跳转到特定页面?
我发现了一些关于如何使Iphone UIPageViewController,如何在不破坏数据源指定顺序的情况下正确跳转到特定页面?,iphone,objective-c,ios,uipageviewcontroller,Iphone,Objective C,Ios,Uipageviewcontroller,我发现了一些关于如何使UIPageViewController跳转到特定页面的问题,但我注意到跳转的另一个问题,这些问题的答案似乎都不承认 不必详细介绍我的iOS应用程序(类似于分页日历),下面是我的体验。我声明一个UIPageViewController,设置当前视图控制器,并实现一个数据源 // end of the init method pageViewController = [[UIPageViewController alloc] initWith
UIPageViewController
跳转到特定页面的问题,但我注意到跳转的另一个问题,这些问题的答案似乎都不承认
不必详细介绍我的iOS应用程序(类似于分页日历),下面是我的体验。我声明一个UIPageViewController
,设置当前视图控制器,并实现一个数据源
// end of the init method
pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
pageViewController.dataSource = self;
[self jumpToDay:0];
}
//...
- (void)jumpToDay:(NSInteger)day {
UIViewController *controller = [self dequeuePreviousDayViewControllerWithDaysBack:day];
[pageViewController setViewControllers:@[controller]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSInteger days = ((THDayViewController *)viewController).daysAgo;
return [self dequeuePreviousDayViewControllerWithDaysBack:days + 1];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSInteger days = ((THDayViewController *)viewController).daysAgo;
return [self dequeuePreviousDayViewControllerWithDaysBack:days - 1];
}
- (UIViewController *)dequeuePreviousDayViewControllerWithDaysBack:(NSInteger)days {
return [[THPreviousDayViewController alloc] initWithDaysAgo:days];
}
编辑说明:我为出列方法添加了简化代码。即使使用这种亵渎神明的实现,我在页面顺序方面也有完全相同的问题
初始化操作都按预期进行。增量分页也可以正常工作。问题是,如果我今天再打电话给jumpToDay,订单就会变得混乱
如果用户在第5天并跳到第1天,向左滚动将再次显示第5天,而不是相应的第0天。这似乎与UIPageViewController
如何保留对附近页面的引用有关,但我找不到任何对方法的引用,这些引用会迫使它刷新缓存
有什么想法吗?我使用这个功能(我总是处于横向、两页模式)
-(void)flipToPage:(NSString*)索引{
intx=[索引intValue];
传单PageContentViewController*CurrentViewController=[self.pageViewController.ViewController对象索引:0];
NSUInteger RetrievedIndex=[self indexOfViewController:CurrentViewController];
传单PageContentViewController*firstViewController=[self-viewControllerAtIndex:x];
传单PageContentViewController*secondViewController=[self-viewControllerAtIndex:x+1];
NSArray*视图控制器=零;
viewControllers=[NSArray arrayWithObjects:firstViewController,secondViewController,nil];
如果(检索索引x){
[self.pageViewController设置ViewController:ViewController方向:UIPageViewControllerNavigationDirection反向动画:是完成:NULL];
}
}
}
所以我遇到了与您相同的问题,我需要能够“跳转”到一个页面,然后在我回指页面时发现“顺序混乱”。据我所知,页面视图控制器肯定在缓存视图控制器,当您“跳转”到页面时,必须指定方向:前进或后退。然后,它假设新的视图控制器是前一个视图控制器的“邻居”,因此在您回击时自动显示前一个视图控制器。我发现只有在使用UIPageViewControllerTransitionStyleScroll
而不是UIPageViewControllerTransitionStylePageCurl
时才会发生这种情况。页面卷曲样式显然不执行相同的缓存,因为如果您“跳转”到页面,然后用手势返回,它会将pageViewController:viewController(Before/After)viewController:
消息发送到数据源,使您能够提供正确的邻居视图控制器
- (IBAction)pageControlCurrentPageDidChange:(id)sender
{
self.currentIndex = self.pageControl.currentPage;
MYViewController *currentPageViewController = (MYViewController *)self.pageViewController.viewControllers.firstObject;
currentPageViewController.pageData = [self.pageDataSource dataForPage:self.currentIndex];
[currentPageViewController updateDisplay];
}
解决方案:
执行“跳转”到页面时,您可以首先跳转到相邻页面,跳转到您要跳转到的页面(动画:否),然后在跳转的完成块中,跳转到所需页面。这将更新缓存,以便当您返回时,将显示正确的邻居页面。缺点是需要创建两个视图控制器;您要跳到的那个,以及在回指后应该显示的那个
下面是我为UIPageViewController
编写的一个类别的代码:
@implementation UIPageViewController (Additions)
- (void)setViewControllers:(NSArray *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction invalidateCache:(BOOL)invalidateCache animated:(BOOL)animated completion:(void (^)(BOOL finished))completion {
NSArray *vcs = viewControllers;
__weak UIPageViewController *mySelf = self;
if (invalidateCache && self.transitionStyle == UIPageViewControllerTransitionStyleScroll) {
UIViewController *neighborViewController = (direction == UIPageViewControllerNavigationDirectionForward
? [self.dataSource pageViewController:self viewControllerBeforeViewController:viewControllers[0]]
: [self.dataSource pageViewController:self viewControllerAfterViewController:viewControllers[0]]);
[self setViewControllers:@[neighborViewController] direction:direction animated:NO completion:^(BOOL finished) {
[mySelf setViewControllers:vcs direction:direction animated:animated completion:completion];
}];
}
else {
[mySelf setViewControllers:vcs direction:direction animated:animated completion:completion];
}
}
@end
要测试这一点,您可以创建一个新的“基于页面的应用程序”,并添加一个“转到”按钮,该按钮将“跳转”到某个日历月,然后返回。请确保将转换样式设置为滚动。我可以确认此问题,并且它仅在使用UIPageViewControllerTransitionStyleScroll
而不是UIPageViewControllerTransitionStylePageCurl
时发生
解决方法:进行循环,并在每次翻页时调用UIPageViewController setViewControllers
,直到到达所需页面
这将使UIPageViewController中的内部数据源索引保持同步。我有一种不同的方法,如果您的页面设计为在init之后更新:
选择手动页面时,我会更新一个标志
- (void)scrollToPage:(NSInteger)page animated:(BOOL)animated
{
if (page != self.currentPage) {
[self setViewControllers:@[[self viewControllerForPage:page]]
direction:(page > self.currentPage ?
UIPageViewControllerNavigationDirectionForward :
UIPageViewControllerNavigationDirectionReverse)
animated:animated
completion:nil];
self.currentPage = page;
self.forceReloadNextPage = YES; // to override view controller automatic page cache
}
}
- (ScheduleViewController *)viewControllerForPage:(NSInteger)page
{
CustomViewController * scheduleViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"CustomViewController"];
scheduleViewController.view.tag = page; // keep track of pages using view.tag property
scheduleViewController.data = [self dataForPage:page];
if (self.currentViewController)
scheduleViewController.calendarLayoutHourHeight = self.currentViewController.calendarLayoutHourHeight;
return scheduleViewController;
}
然后强制下一页重新加载正确的数据:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
{
CustomViewController * nextViewController = [pendingViewControllers lastObject];
// When manual scrolling occurs, the next page is loaded from UIPageViewController cache
// and must be refreshed
if (self.forceReloadNextPage) {
// calculate the direction of the scroll to know if to load next or previous page
NSUInteger page = self.currentPage + 1;
if (self.currentPage > nextViewController.view.tag) page = self.currentPage - 1;
nextViewController.data = [self dataForPage:page];
self.forceReloadNextPage = NO;
}
}
,作者Matt Neuburg记录了这个确切的问题,我发现他的解决方案感觉比目前公认的答案好一点。该解决方案效果很好,它有一个负面的副作用,即在图像之前/之后设置动画,然后用所需的页面替换该页面。我觉得这是一种奇怪的用户体验,Matt的解决方案解决了这个问题
__weak UIPageViewController* pvcw = pvc;
[pvc setViewControllers:@[page]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES completion:^(BOOL finished) {
UIPageViewController* pvcs = pvcw;
if (!pvcs) return;
dispatch_async(dispatch_get_main_queue(), ^{
[pvcs setViewControllers:@[page]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO completion:nil];
});
}];
如果您不需要为新页面设置动画(我没有),那么下面的代码对我很有用,在故事板中调用“值更改”。我没有在视图控制器之间更改,而是更改与当前视图控制器关联的数据
- (IBAction)pageControlCurrentPageDidChange:(id)sender
{
self.currentIndex = self.pageControl.currentPage;
MYViewController *currentPageViewController = (MYViewController *)self.pageViewController.viewControllers.firstObject;
currentPageViewController.pageData = [self.pageDataSource dataForPage:self.currentIndex];
[currentPageViewController updateDisplay];
}
currentIndex在那里,因此我可以在页面之间滑动时更新页面控件的currentPage
pageDataSource dataForPage:返回由页面显示的数据对象数组。我自己也为这个问题挣扎了很长时间。对我来说,我有一个从故事板加载的UIPageViewController(我称之为PageController),并在其上添加了一个UIViewController“ContentVC”
我让ContentVC负责加载到内容区域的数据,让PageController负责滑动/转到/页面指示器更新。ContentVC有一个ivar CurrentPageIndex
- (void)setPageForward
{
ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]];
NSArray *viewControllers = @[FirstVC];
[PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return [CurrentPageIndex integerValue];
}
private func slideToPage(index: Int, completion: (() -> Void)?) {
let tempIndex = currentPageIndex
if currentPageIndex < index {
for var i = tempIndex+1; i <= index; i++ {
self.setViewControllers([viewControllerArray[i]], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: {[weak self] (complete: Bool) -> Void in
if (complete) {
self?.updateCurrentPageIndex(i-1)
completion?()
}
})
}
}
else if currentPageIndex > index {
for var i = tempIndex - 1; i >= index; i-- {
self.setViewControllers([viewControllerArray[i]], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: {[weak self] (complete: Bool) -> Void in
if complete {
self?.updateCurrentPageIndex(i+1)
completion?()
}
})
}
}
}
-(void)buyAction
{
isFromBuy = YES;
APPChildViewController *initialViewController = [self viewControllerAtIndex:4];
viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
-(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
if (isFromBuy) {
isFromBuy = NO;
return 5;
}
return 0;
}
weak var pvcw = pageViewController
pageViewController!.setViewControllers([page], direction: UIPageViewControllerNavigationDirection.Forward, animated: true) { _ in
if let pvcs = pvcw {
dispatch_async(dispatch_get_main_queue(), {
pvcs.setViewControllers([page], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
})
}
}
weak var weakPageVc = pageVc
pageVc.setViewControllers([page], direction: .forward, animated: true) { finished in
guard let pageVc = weakPageVc else {
return
}
DispatchQueue.main.async {
pageVc.setViewControllers([page], direction: .forward, animated: false)
}
}
let orderedViewControllers = [UIViewController(),UIViewController(), UIViewController()]
let pageViewController = UIPageViewController()
let pageControl = UIPageControl()
func jump(to: Int, completion: @escaping (_ vc: UIViewController?) -> Void){
guard orderedViewControllers.count > to else{
//index of bounds
return
}
let toVC = orderedViewControllers[to]
var direction: UIPageViewController.NavigationDirection = .forward
if pageControl.currentPage < to {
direction = .forward;
} else {
direction = .reverse;
}
pageViewController.setViewControllers([toVC], direction: direction, animated: true) { _ in
DispatchQueue.main.async {
self.pageViewController.setViewControllers([toVC], direction: direction, animated: false){ _ in
self.pageControl.currentPage = to
completion(toVC)
}
}
}
}
self.jump(to: 5) { (vc) in
// you can do anything for new vc.
}