View being blocked by UITransitionView after being presented [duplicate]
Asked Answered
O

6

26

I have a side navigation controller and present it via a UIButton. When I make this NC the root view controller directly by [self presentviewcontroller: NC animated: YES completion: nil], for some reason the menu side of the NC is blocked by a UITransitionView that I cannot get to disappear.

Enter image description here

Enter image description here

I have tried the following:

UIWindow *window = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window];
    window.backgroundColor = kmain;


    CATransition* transition = [CATransition animation];
    transition.duration = .5;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromTop;

    [nc.view.layer addAnimation:transition forKey:kCATransition];

    [UIView transitionWithView:window
                      duration:0.5
                       options:UIViewAnimationOptionTransitionNone
                    animations:^{ window.rootViewController = nc; }
                    completion:^(BOOL finished) {
                        for (UIView *subview in window.subviews) {
                            if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
                                [subview removeFromSuperview];
                            }
                        }
                    }];

But it is very hacky, and as the rootviewcontroller of the window changes during the transition, it's a little choppy and part of the navigationcontroller and the top right corner turn black. It looks very bad.

Oubliette answered 21/3, 2016 at 22:9 Comment(3)
Did you find the root cause? I met the same issue here. Removing the view or disable touch on the view seems like a work around, but how do we avoid it? I don't understand why it came up.Ikon
It was so long ago that I'm not quite sure, but I believe I completely switched libraries and went with JASidePannelController: github.com/gotosleep/JASidePanels Much easier to work with.Oubliette
this problem is FINALLY SOLVED: https://mcmap.net/q/537399/-pass-touches-through-a-uiviewcontrollerBrawley
K
9

To get tap events through the UITransitionView, set the containerView's userInteractionEnabled to false. This is if you're doing a custom transition animation by using UIViewControllerAnimatedTransitioning.

Example, in your animateTransition(_:):

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

    let containerView = transitionContext.containerView
    containerView.isUserInteractionEnabled = false

    ...
}
Kezer answered 10/10, 2016 at 22:42 Comment(4)
If I set the containerView's interactions to disabled, the clicks on the presented view fall through as well.Arie
any solution to this?Gaeta
@Arie this is the work around: https://mcmap.net/q/522049/-view-being-blocked-by-uitransitionview-after-being-presented-duplicateHolcomb
this is wrong. the issue is you can't get at the UITransitionView. here's the solution: https://mcmap.net/q/537399/-pass-touches-through-a-uiviewcontroller (this answer is an important technique you have to use opften to "get through" views within a VC, but to go backwards you need this https://mcmap.net/q/537399/-pass-touches-through-a-uiviewcontroller )Brawley
H
5

In my situation I needed a halfSize view controller. I followed this answer which worked great until I realized I needed to still be able to interact with the presenting vc (the vc behind the halfSizeVC).

The key is that you have to set both of these frames with the same CGRect values:

halfSizeVC.frame = CGRect(x: 0, y: UIScreen.main.bounds.height / 2, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)

containerView = CGRect(x: 0, y: UIScreen.main.bounds.height / 2, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)

Here is the code to go from ViewController to HalfSizeController and make HalfSizeController 1/2 the screen size. Even with halfSizeVC on screen you will still be able to interact with the top half of the vc behind it.

You also have to make a PassthroughView class if you want to be able to touch something inside the halfSizeVC. I included it at the bottom.

The presenting vc is white with a purple button at the bottom. Tapping the purple button will bring up the red halfSizeVC.

vc/presentingVC:

import UIKit

class ViewController: UIViewController {

    lazy var purpleButton: UIButton = {
        let button = UIButton(type: .system)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Tap to Present HalfSizeVC", for: .normal)
        button.setTitleColor(UIColor.white, for: .normal)
        button.backgroundColor = UIColor.systemPurple
        button.addTarget(self, action: #selector(purpleButtonPressed), for: .touchUpInside)
        button.layer.cornerRadius = 7
        button.layer.masksToBounds = true
        return button
    }()

    var halfSizeVC: HalfSizeController?

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        // tap gesture on vc will dismiss HalfSizeVC
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissHalfSizeVC))
        view.addGestureRecognizer(tapGesture)
    }


    // tapping the purple button presents HalfSizeVC
    @objc func purpleButtonPressed() {

        halfSizeVC = HalfSizeController()

        // *** IMPORTANT ***
        halfSizeVC!.view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 2)

        halfSizeVC!.modalPresentationStyle = .custom

        present(halfSizeVC!, animated: true, completion: nil)
    }

    // dismiss HalfSizeVC by tapping anywhere on the white background
    @objc func dismissHalfSizeVC() {

        halfSizeVC?.dismissVC()
    }
}

halfSizeVC/presentedVC

import UIKit

class HalfSizeController: UIViewController {

    init() {
        super.init(nibName: nil, bundle: nil)
        modalPresentationStyle = .custom
        transitioningDelegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    lazy var topHalfDummyView: PassthroughView = {
        let view = PassthroughView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .clear
        view.isUserInteractionEnabled = true
        return view
    }()

    var isPresenting = false
    let halfScreenHeight = UIScreen.main.bounds.height / 2

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red

        setAnchors()
    }

    private func setAnchors() {
    
        view.addSubview(topHalfDummyView)
        topHalfDummyView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        topHalfDummyView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        topHalfDummyView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        topHalfDummyView.heightAnchor.constraint(equalToConstant: halfScreenHeight).isActive = true
    }

    public func dismissVC() {
        dismiss(animated: true, completion: nil)
    }
}

extension HalfSizeController: UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning {

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return self
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

        let containerView = transitionContext.containerView

        // *** IMPORTANT ***
        containerView.frame = CGRect(x: 0, y: halfScreenHeight, width: UIScreen.main.bounds.width, height: halfScreenHeight)

        let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
        guard let toVC = toViewController else { return }
        isPresenting = !isPresenting

        if isPresenting == true {
            containerView.addSubview(toVC.view)

            topHalfDummyView.frame.origin.y += halfScreenHeight

            UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {

                self.topHalfDummyView.frame.origin.y -= self.halfScreenHeight

            }, completion: { (finished) in
                transitionContext.completeTransition(true)
            })

        } else {
            UIView.animate(withDuration: 0.4, delay: 0, options: [.curveEaseOut], animations: {
            
            }, completion: { (finished) in
                self.topHalfDummyView.frame.origin.y += self.halfScreenHeight
                transitionContext.completeTransition(true)
            })
        }
    }
}

PassthroughView needed for the topHalfDummyView in HalfSizeVC

import UIKit

class PassthroughView: UIView {
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        print("Passing all touches to the next view (if any), in the view stack.")
        return false
    }
}

Before purple button is pressed:

enter image description here

After purple button is pressed:

enter image description here

If you press the white background the red color will get dismissed

You can just c+p all 3 files and run your project

Holcomb answered 20/8, 2020 at 1:36 Comment(7)
this should be an accepted answerHurlbut
This is not really a solution and only works in a few rare cases. Fortunately the problem HAS been solved: https://mcmap.net/q/537399/-pass-touches-through-a-uiviewcontrollerBrawley
@Brawley 1- It does work because I use it in my own live apps. 2- Have you tested it and it didn't work? If it didn't work then which part(s)? 3- You said "This is not really a solution" but then said "only works in a few rare cases". If it works in a few rare cases then it is a solution.Holcomb
Lance .. "In my situation I needed a halfSize view controller. " it only works in that caseBrawley
@Brawley I can agree with that.Holcomb
just for the record, the new solution that is now possible (see link) is extremely simple, well it's a one liner, and it works in 100% of cases completely reliably - it's a lifesaver. its' great how things (sometimes!) get easier in iOS over the years!Brawley
@Brawley I looked at it but haven't tried it yet. I'll try it later this week. Peace!Holcomb
B
3

I had a similar issue where a UITransitionView kept blocking my views, preventing any user interaction.

In my case this was due to an uncompleted custom animated UIViewController transition.

I forgot to properly complete my transition with:

TransitionContext.completeTransition(transitionContext.transitionWasCancelled)

or

TransitionContext.completeTransition(!transitionContext.transitionWasCancelled)

In the

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {}

from the UIViewControllerAnimatedTransitioning protocol

Blouin answered 21/10, 2019 at 14:52 Comment(1)
I met the same issue. I have a progress bar that shows animation. When it has done, I dismiss the progress bar and navigationController?.popToRootViewController. Maybe the progress bar is not dismissed before the ViewController was popped up. I fixed it by letting viewController popped up certainly after progress bar dismissed.Limy
H
1

I had the same issue, but in a little different scenario. I ended up doing something very similar to find the view, but instead of removing the view which can be more problematic, I disabled the user interaction, so any touch events just go throw it and any other objects can handle to user's interaction.

In my case this was only present after updating the app to iOS 10, and the same code running in iOS 9 didn't fall into this.

Heddle answered 4/10, 2016 at 18:59 Comment(0)
S
0

I was facing the same issue, and this solved issue for me,

navigationController.setNavigationBarHidden(true, animated: false)

This worked for me as I am having custom view as navigation bar in view controllers.

Sam answered 4/5, 2018 at 7:4 Comment(0)
S
0

I’ve had this issue when I was setting accessibilityElements on a popover view controller. I fixed it by removing assigning an array of elements.

Scot answered 30/11, 2018 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.