UIPageViewController gesture is calling viewControllerAfter: but doesn't animate

后端 未结 4 974
梦谈多话
梦谈多话 2020-12-09 21:07

I have a really interesting issue with UIPageViewController.

My project is set up very similarly to the example Page Based Application template. Every now and then (

4条回答
  •  一个人的身影
    2020-12-09 22:08

    Since I was still receiving mysterious crashes with the implementation in my first answer, I kept searching for a "good enough" solution which depends less on personal assumptions about the page view controller's (PVC) underlying behavior. Here is what I managed to come up with.

    My former approach was kind of intrusive and was more of a workaround than an acceptable solution. Instead of fighting the PVC to force it to do what I thought it was supposed to do, it seems that it's better accept the facts that:

    • the pageViewController:viewControllerBeforeViewController: and pageViewController:viewControllerAfterViewController: methods can be called an arbitrary number of times by UIKit, and
    • there is absolutely no guarantee that either of these correspond to a paging animation, nor that they will be followed by a call to pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

    That means we cannot use the before/after methods as "animation-begin" (note, however, that didFinishAnimating still serves as "animation-end" event). So how do we know an animation has indeed started?

    Depending on our needs, we may be interested in the following events:

    1. the user begins fiddling with the page: A good indicator for this is the before/after callbacks, or more precisely the first of them.

    2. first visual feedback of the page turning gesture: We can use KVO on the state property of the tap and pan gesture recognizers of the PVC. When a UIGestureRecognizerStateBegan value is observed for panning, we can be pretty sure that visual feedback will follow.

    3. the user finishes dragging the page around by releasing the touch: Again, KVO. When the UIGestureRecognizerStateRecognized value is reported either for panning or tapping, it is when the PVC is actually going to turn the page, so this may be used as "animation-begin".

    4. UIKit starts the paging animation: I have no idea how to get a direct feedback for this.

    5. UIKit concludes the paging animation: Piece of cake, just listen to pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:.

    For KVO, just grab the gesture recognizers of the PVC as below:

    @interface MyClass () 
    {
        UIPanGestureRecognizer* pvcPanGestureRecognizer;
        UITapGestureRecognizer* pvcTapGestureRecognizer;
    }
    ...
    for ( UIGestureRecognizer* recognizer in pageViewController.gestureRecognizers )
    {
        if ( [recognizer isKindOfClass:[UIPanGestureRecognizer class]] )
        {
            pvcPanGestureRecognizer = (UIPanGestureRecognizer*)recognizer;
        }
        else if ( [recognizer isKindOfClass:[UITapGestureRecognizer class]] )
        {
            pvcTapGestureRecognizer = (UITapGestureRecognizer*)recognizer;
        }
    }
    

    Then register your class as observer for the state property:

    [pvcPanGestureRecognizer addObserver:self
                              forKeyPath:@"state"
                                 options:NSKeyValueObservingOptionNew
                                 context:NULL];
    [pvcTapGestureRecognizer addObserver:self
                              forKeyPath:@"state"
                                 options:NSKeyValueObservingOptionNew
                                 context:NULL];
    

    And implement the usual callback:

    - (void)observeValueForKeyPath:(NSString *)keyPath
            ofObject:(id)object
            change:(NSDictionary *)change
            context:(void *)context
    {
        if ( [keyPath isEqualToString:@"state"] && (object == pvcPanGestureRecognizer || object == pvcTapGestureRecognizer) )
        {
            UIGestureRecognizerState state = [[change objectForKey:NSKeyValueChangeNewKey] intValue];
            switch (state)
            {
                case UIGestureRecognizerStateBegan:
                // trigger for visual feedback
                break;
    
                case UIGestureRecognizerStateRecognized:
                // trigger for animation-begin
                break;
    
                // ...
            }
        }
    }
    

    When you are done, don't forget to unsubscribe from those notifications, otherwise you may get leaks and strange crashes in your app:

    [pvcPanGestureRecognizer removeObserver:self
                                 forKeyPath:@"state"];
    [pvcTapGestureRecognizer removeObserver:self
                                 forKeyPath:@"state"];
    

    That's all folks!

提交回复
热议问题