7

We have run into this issue when implementing interactive dismissal of a modal view controller (dragging modal down should dismiss it) via UIPercentDrivenInteractiveTransition.

Setup:

  1. setup UIViewController embedded in UINavigationController with at least one button in UINavigationBar
  2. modally present another UIViewController embedded in UINavigationController with at least one button in UINavigationBar
  3. setup UIPanGestureRecognizer on modaly presented UINavigationController to drive UIPercentDrivenInteractiveTransition
  4. drag modal down "holding" it by point on UINavigationBar

Issue:

  • while slowly dragging down, animation glitches causing modal view to jump up and down

  • glitch only appears when :

    1. both UINavigationBars have at least one button on them
    2. you "hold" modal by the point on UINavigationBar

Minimal example can be downloaded from github repo.

Has anyone come accross such an issue? Are there any workarounds? Is there some flaw in our setup?

Update

Issue has been simulated on running project above on iPhone 5 simulator with iOS 9.3, OSX 10.11.4, compiled with Xcode 7.3.1.

Update 2

Further investigation showed, that issue is probably not in the animation: For some reason in given setup pan.translationInView(view) is returning unexpected values which causes animation to jump.

Partial workaround

Based on Vladimir's idea we partially fixed the issue by overriding hitTest method of UINavigationBar:

class DraggableNavigationBar: UINavigationBar {

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        guard let view = super.hitTest(point, withEvent: event) else { return nil }

        if view is UIControl || pointIsInsideNavigationButton(point) {
            return view
        } else {
            return nil
        }
    }

    private func pointIsInsideNavigationButton(point: CGPoint) -> Bool {
        return subviews
            .filter { $0.frame.contains(point) }
            .filter { String($0.dynamicType) == "UINavigationItemButtonView" }
            .isEmpty == false
    }
}
Jakub Vano
  • 3,833
  • 15
  • 29
  • Works for me. Can't see any glitches either on my iPhone 6 or in the iOS Simulator for iPhone 6. – Brett Donald May 23 '16 at 00:42
  • NO issue in above code.work like charms – Hitesh Surani May 23 '16 at 05:08
  • can share exact issue with me.Which OS type and xCode version and deployment target etc. – Hitesh Surani May 23 '16 at 05:10
  • I've been able to reproduce the glitch. It only happens when starting the pan from the `UINavigationBar`. Dragging down, the animation jumps back to 0%, then back to the intended 1-2%. The same pattern happens when cancelling the dismissal, after you hit 0%, it jumps back to 1-2%, then back to 0%. Things that remove the glitch: 1. Removing the `UIBarButtonItem` from the modal VC 2. Removing the `UIBarButtonItem` from the presenting VC 2. Commenting out the `modalPresentationStyle` assignment – bsmith11 May 23 '16 at 05:42
  • @bsmith11 Yep, that is the glitch I'm describing. Unfortunatelly, none of the solutions you've mentioned is applicable for our case: commenting out `modalPresentationStyle` causes background view controller to disappear and we need to have buttons on both navigation bars. – Jakub Vano May 23 '16 at 11:22
  • @HiteshSurani I've added system specs on which I can reproduce the issue – Jakub Vano May 23 '16 at 11:27
  • @JAKUB No issue in your exiting code while run using above configration. – Hitesh Surani May 24 '16 at 05:21

2 Answers2

1

Very interesting glitch. I found a partial solution of this problem few days ago, and since nobody found a full solution, I'll post this, maybe it will be helpful.

If you override hitTest method of UINavigationBar you can get rid of this issue when you dragging modal by holding on UINavigationBar:

extension UINavigationBar {

    override public func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

        guard let view = super.hitTest(point, withEvent: event) else { return nil }

        if view.isKindOfClass(UIControl) {
            return super.hitTest(point, withEvent: event)
        } else {
            return nil
        }
    }
}

Unfortunately if you drag modal by holding on UIBarButtonItem on UINavigationBar, glitch still be present.

You can also try another approach.

As you noticed, pan.translationInView(view) returns incorrect values which causes animation to jump. You need to compare this value to y coordinate of modal view during dragging. You can get this value by checking presentation layer of the modal view controller:

...

let translation = pan.translationInView(view)

if let layer = view.layer.presentationLayer() {
            print(layer.frame.origin.y)
}

...

You can see that when pan.translationInView(view) starts to show wrong value, layer.frame.origin.y still will be correct in that moment. You can compare these two values and find the pattern when value is incorrect, and change it to correct by adding few points to translation.y value.

Vladimir K
  • 1,382
  • 1
  • 12
  • 27
  • Thanks for the idea! I ended up overriding `hitTest`, but also allowed taps on back buttons (surprisingly they are not `UIControl`). Reacting to difference of `translation` and `presentationLayer` would be quite tricky, as one would have to account for different dragging speeds and direction changes. – Jakub Vano May 25 '16 at 10:51
0

I don't have a complete solution but I was able to reduce the glitch by certain amount. I could reproduce this issue on iPhone 5s with iOS 9.3.2 [by dragging down the screen holding Navigation Bar]

The problem seems to be in the UIView.animateWithDuration block of DismissalAnimator. By commenting out the delay and options i.e keeping them to the defaults you can reduce the jumping of the view. You could also try and check for diffrent UIViewAnimationOptions for which you get minimum jump.

    UIView.animateWithDuration(0.3,
        animations: {
            dismissedView.frame = finalFrame
        },
        completion: { _ in
            let didComplete = !transitionContext.transitionWasCancelled()
            transitionContext.completeTransition(didComplete)
        }
   )

There is a question which seems to be dealing with same issue you are facing. And the responses varying from disabling auto layout, putting layoutIfNeeded in animation block [tried, both didn't work].

Community
  • 1
  • 1
Penkey Suresh
  • 5,816
  • 3
  • 36
  • 55
  • Thanks for the suggestion. However, changing `.CurveLinear` causes animation to desynchronize from finger movement. Also, given how specific this issue is (buttons in `UINavigationBar`, dragging by point in `UINavigationBar`) i dont think that linked question is related, but will check it out. – Jakub Vano May 24 '16 at 07:36