UFO ET IT

UIPageViewController, 데이터 소스에서 지정한 순서를 엉망으로 만들지 않고 특정 페이지로 올바르게 점프하려면 어떻게해야합니까?

ufoet 2020. 11. 26. 20:25
반응형

UIPageViewController, 데이터 소스에서 지정한 순서를 엉망으로 만들지 않고 특정 페이지로 올바르게 점프하려면 어떻게해야합니까?


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 일로 이동하면 왼쪽으로 스크롤하면 적절한 0 일 대신 -5 일이 다시 표시됩니다. 이것은 UIPageViewController근처 페이지에 대한 참조를 유지 하는 방법과 관련이있는 것 같습니다. 캐시를 새로 고치도록 강제하는 메서드에 대한 참조를 찾지 못했습니다.

어떤 아이디어?


Matt Neuburg의 프로그래밍 iOS6 은이 정확한 문제를 문서화했으며 실제로 그의 솔루션이 현재 받아 들여지는 답변보다 약간 기분이 좋다는 것을 발견했습니다. 훌륭하게 작동하는이 솔루션은 전후 이미지에 애니메이션을 적용한 다음 해당 페이지를 원하는 페이지로 부조리하게 대체하는 부정적인 부작용이 있습니다. 나는 그것이 이상한 사용자 경험이라고 느꼈고 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];
                   });
               }];

그래서 나는 페이지로 '점프'할 수 있어야하는 당신과 같은 문제에 부딪 쳤고 페이지를 다시 제스처 할 때 '주문이 엉망이되었습니다'를 발견했습니다. 내가 말할 수있는 한, 페이지 뷰 컨트롤러는 확실히 뷰 컨트롤러를 캐싱하고 있으며 페이지로 '점프'할 때 방향을 지정해야합니다 : 앞으로 또는 뒤로. 그런 다음 새 뷰 컨트롤러가 이전 뷰 컨트롤러의 '이웃'이라고 가정하므로 다시 제스처를 할 때 이전 뷰 컨트롤러를 자동으로 표시합니다. 나는 이것이 당신이를 사용할 때만 발생 UIPageViewControllerTransitionStyleScroll하고 UIPageViewControllerTransitionStylePageCurl. 페이지 컬 스타일은 '점프'하면 분명히 동일한 캐싱을 수행하지 않습니다.pageViewController:viewController(Before/After)ViewController:

솔루션 : 페이지로 '점프'를 수행 할 때 먼저 점프하려는 페이지 ( animated:NO) 의 인접 페이지로 이동 한 다음 해당 점프의 완료 블록에서 원하는 페이지로 이동할 수 있습니다. 그러면 다시 제스처를 할 때 올바른 이웃 페이지가 표시되도록 캐시가 업데이트됩니다. 단점은 두 개의 뷰 컨트롤러를 만들어야한다는 것입니다. 당신이 점프하고있는 하나와 뒤로 몸짓을 한 후에 표시되어야하는 하나.

내가 작성한 범주에 대한 코드는 다음과 같습니다 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

이를 테스트하기 위해 할 수있는 일은 새로운 '페이지 기반 애플리케이션'을 만들고 특정 월로 '점프'한 다음 다시 제스처하는 '이동'버튼을 추가하는 것입니다. 스크롤 할 전환 스타일을 설정해야합니다.


이 기능을 사용합니다 (저는 항상 가로 모드, 2 페이지 모드).

-(void) flipToPage:(NSString * )index {


int x = [index intValue];
LeafletPageContentViewController *theCurrentViewController = [self.pageViewController.viewControllers   objectAtIndex:0];

NSUInteger retreivedIndex = [self indexOfViewController:theCurrentViewController];

LeafletPageContentViewController *firstViewController = [self viewControllerAtIndex:x];
LeafletPageContentViewController *secondViewController = [self viewControllerAtIndex:x+1 ];


NSArray *viewControllers = nil;

viewControllers = [NSArray arrayWithObjects:firstViewController, secondViewController, nil];


if (retreivedIndex < x){

    [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];

} else {

    if (retreivedIndex > x ){

        [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL];
      } 
    }
} 

다음은 하위 클래스에 사용할 Swift 솔루션입니다 UIPageViewController.

당신이 viewControllers의 배열 저장 가정 viewControllerArray하고있는 현재 페이지 인덱스를 updateCurrentPageIndex.

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

djibouti33의 답변의 신속한 버전 :

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

iOS 10에서는 더 이상 그렇지 않으며 더 이상 허용 된 답변 솔루션을 사용할 필요가 없다는 점에 유의하는 것이 중요합니다. 언제나처럼 계속하십시오.


나는이 문제를 확인 할 수 있으며, 사용하는 경우에만 발생하는 것이 UIPageViewControllerTransitionStyleScroll아니라 UIPageViewControllerTransitionStylePageCurl.

해결 방법 :UIPageViewController setViewControllers 원하는 페이지에 도달 할 때까지 루프를 만들고 각 페이지 넘김을 호출 합니다.

이렇게하면 UIPageViewController의 내부 데이터 소스 색인이 동기화됩니다.


이것은 유일한 해결책입니다

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

I had a different approach, should be possible if your pages are designed to be updated after init:
When a manual page is selected I update a flag

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

and then force the the next page to reload with the correct data:

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

If you do not need to animate to the new page, as I didn't, the following code worked for me, called on "Value Changed" in the storyboard. Instead of changing between view controllers, I change the data associated with the current view controller.

    - (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 is there so I can update the pageControl's currentPage when I swipe between pages.

pageDataSource dataForPage: returns an array of data objects that are displayed by the pages.


Here is an up-to-date Swift 3+ version of the answer by @djibouti33 with cleaned-up syntax.

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

I was struggling with this issue for a long time myself. For me I had a UIPageViewController (I called it PageController) load from storyboard and on it I add a UIViewController 'ContentVC'.

I let the ContentVC takes care of the data to be loaded on to the content area and let PageController takes care of the sliding/goto/PageIndicator updates. The ContentVC has an ivar CurrentPageIndex and sends that value to PageController so PageController knows which page it's on. In my .m file that has PageController I have these two methods.

Note that I used set to 0 and so every time PageVC reloads it goes to the first page which I don't want, [self viewControllerAtIndex:0].

- (void)setPageForward
{  
  ContentVC *FirstVC = [self viewControllerAtIndex:[CurrentPageIndex integerValue]];

  NSArray *viewControllers = @[FirstVC];
  [PageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}

This second method is PageViewController's DataSource method. presentationIndexForPageViewController will set the highlighted dot to the right page (the page you want). Note that if we return 0 here the page indicator will highlight the first dot which indicates the first page and we don't want that.

- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController 
{
  return [CurrentPageIndex integerValue];
}

    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)

                }
            }
        }
    }

USAGE:

self.jump(to: 5) { (vc) in
    // you can do anything for new vc.
}

참고URL : https://stackoverflow.com/questions/13633059/uipageviewcontroller-how-do-i-correctly-jump-to-a-specific-page-without-messing

반응형