Jakub Vano Jakub Vano - 6 months ago 43
iOS Question

Glitch when interactively dismissing modal

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
      UINavigationBar
      s 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
}
}

Answer

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.

Comments