HeroTransitions / Hero

Elegant transition library for iOS & tvOS
https://HeroTransitions.github.io/Hero/
MIT License
22.06k stars 1.73k forks source link

Screen freezes (no crash just frozen) #451

Closed jjjeeerrr111 closed 2 years ago

jjjeeerrr111 commented 6 years ago

I have implemented this logic on view controller A with presents view controller B. View controller be will sometimes just fail to dismiss and remain stuck on screen with no way out except by force killing the app. This does not cause any exception just a frozen app.

View controller A presenting B:

let vc = PrizeDetailsViewController() vc.hero.isEnabled = true vc.hero.modalAnimationType = .selectBy(presenting: .pageIn(direction: .left), dismissing: .pageOut(direction: .right))

This is the dismiss logic for view controller B, it has a pan gesture recognizer and a back button. A and B are regular UIViewController (no UINavigationController).

dismiss logic in B:

func backButtonPressed(sender: UIButton) { hero.dismissViewController() }

`func handlePan(gestureRecognizer:UIPanGestureRecognizer) { let translation = panGR.translation(in: nil) let progress = translation.x / 2 / view.bounds.width

    if translation.x < 0 {
        Hero.shared.cancel()
    }

    switch panGR.state {
    case .began:
        // begin the transition as normal
        //dismiss(animated: true, completion: nil)
        hero.dismissViewController()
    case .changed:
        Hero.shared.update(progress)

        // update views' position (limited to only vertical scroll)
        Hero.shared.apply(modifiers: [.position(CGPoint(x:translation.x/2.0 + view.center.x, y:view.center.y))], to: view)
        //            Hero.shared.apply(modifiers: [.position(CGPoint(x:nameLabel.center.x, y:translation.y + nameLabel.center.y))], to: nameLabel)
    //            Hero.shared.apply(modifiers: [.position(CGPoint(x:descriptionLabel.center.x, y:translation.y + descriptionLabel.center.y))], to: descriptionLabel)
    default:
        // end or cancel the transition based on the progress and user's touch velocity
        if progress + panGR.velocity(in: nil).x / view.bounds.width > 0.3 {
            Hero.shared.finish()
        } else {
            Hero.shared.cancel()
        }
    }
}`

This works 90% of the time but for some reason will just stop randomly. I am wondering if I am setting it up properly.

Thanks

jjjeeerrr111 commented 6 years ago

This happens on mutliple screens where this type of behaviour is implemented.

lkzhao commented 6 years ago

Why do you have these line here:

if translation.x < 0 { Hero.shared.cancel() }

jjjeeerrr111 commented 6 years ago

Hi,

This makes sure the user cannot swipe in the opposite direction to dismiss the view. Without it the user can pan from right edge to left edge.

petard commented 6 years ago

I'm facing this issue too, i.e. freezing of the app without crash. My app has a PageViewController (PVC), each parent view presents different child controllers using Hero. The child controllers may have child controllers too, e.g. search result -> product view -> product details view. Randomly the child views might freeze, the app is unresponsive but hasn't crashed.

I removed Hero and the problem doesn't happen. Hints on how to debug this are very welcome as the app doesn't crash and there's no exception.

ManueGE commented 6 years ago

Having exactly the same issue with a very similar code. I also have a PageViewController as @petard.

It seems that the view controller is properly dismissed (or popped) but there are a snapshot of the view controller on the screen which does nothing. Also, that snapshot does not appear in the view inspector.

@petard @jjjeeerrr111 did you find any solution for this?

pablogeek commented 6 years ago

Hello guys, this is also happening to me. @lkzhao @petard @jjjeeerrr111 would be awesome if you could provide more info about how this is going. Thanks!

petard commented 6 years ago

@ManueGE thanks for providing more details! did you guys found a way to reproduce the issue? for me its totally random which is probably not very helpful to find a fix

ManueGE commented 6 years ago

@petard I am able to reproduce it now, it happens to me if I do a really quick pan gesture. Still I have no idea about why is it happening. I tried to do the same thing with the hero examples but no luck.

are you able to reproduce it this way?

petard commented 6 years ago

@ManueGE I tried panning around a lot but it hasn't happened yet.

ManueGE commented 6 years ago

@petard finally we fixed it. We were using a pop/push transition and it failed randomly. Now we have migrated to a modal transition and it works perfectly. I couldn't say why this happened with the navigation controller, though.

tifroz commented 6 years ago

Thanks @ManueGE I had the exact same issue - substituting a push transition with a modal transition fixed it!

Details of my implementation: interactive transition driven by a UIPanGestureRecognizer (panning too fast causes the UI to freeze quite frequently, either when bringing up the new screen or when dismissing it) - in my case no PageViewController involved, but I do have a view controller hierarchy with parent & child view controllers

Satchitananda commented 6 years ago

Having same issue, but with modal transition, not push. Using Hero from master branch. When panning with gesture issue not being reproduced, but when I do dismiss(animated: true, completion: nil) or hero.dismissViewController() app gets frozen from time to time

emrekaranfil commented 6 years ago

I have this problem. But not often work. I follow this topic.

petard commented 6 years ago

I've tried only applying modal transitions to the first VC on the top of the page view controller but the issues persists. Moving everything to modal would require quite a bit of refactoring as the app depends on the navigation bar.

@lkzhao Do you have any insights on how to fix this or what could be the root cause?

emrekaranfil commented 6 years ago

Hi everybody, my problem is as if solved. If you want try;

  1. Remove Pods folder
  2. Add Podfile in pod 'Hero', '~> 1.0.1'
  3. pod repo update && pod install

sometimes pods folder is break down

krekeroff90 commented 6 years ago

Call "finish()" on the main thread. This solved my problem when I use push transitions

dmachacon commented 5 years ago

Having same issue, but with modal transition, not push. Using Hero from master branch. When panning with gesture issue not being reproduced, but when I do dismiss(animated: true, completion: nil) or hero.dismissViewController() app gets frozen from time to time

try adding: Hero.shared.finish(animate: false)

after dismissing the view controller

kluku commented 5 years ago

Calling finish and cancel like this solved the problem for me:

extension HeroTransition {
    func cancelOnMain(animate: Bool = true) {
        DispatchQueue.main.async {
            Hero.shared.cancel(animate: animate)
        }
    }

    func finishOnMain(animate: Bool = true) {
        DispatchQueue.main.async {
            Hero.shared.finish(animate: animate)
        }
    }
}

I was already calling them on the main thread (tested) so it must have been this async that helped...

StainlessStlRat commented 4 years ago

Calling finish and cancel like this solved the problem for me:

extension HeroTransition {
    func cancelOnMain(animate: Bool = true) {
        DispatchQueue.main.async {
            Hero.shared.cancel(animate: animate)
        }
    }

    func finishOnMain(animate: Bool = true) {
        DispatchQueue.main.async {
            Hero.shared.finish(animate: animate)
        }
    }
}

I was already calling them on the main thread (tested) so it must have been this async that helped...

I added this extension and never see these functions get called.

amodkanthe commented 4 years ago

@jjjeeerrr111 @petard @emrekaranfil @ManueGE @kluku @tifroz @pablogeek

Any solution on this facing same issue with latest version of library on fast swipe I am using navigation controller and push

JoeMatt commented 4 years ago

I’m only helping manage and review at this time, I don’t know enough about the inner workings to make code edits without spending a lot of time manually running tests.

I haven’t had a chance to read every comment in this thread, is there a code sample or a pull request someone can point me to I can review?

Edit:

This looks like a simple threading problem actually after glancing at some of the comments above. The theory about it being a screenshot that iOS makes for the animation layers is plausible. If I member correctly court animation layers and you I’ve use have separate trees, the CA layers are used in quarts rendering and transitions, I’m speculating here but possibly iOS is snapshot interview controller as a core graphics Texture and the rendering tree is getting out of sync.

If that’s the case, it should be easy to detect if they dismissed if you controller calls dealloc()

I suspect as much due to some people mentioning turning off animations or forcing main thread solves the problem.

Whose responsibility the thread management is I’m not sure. If you create your view controllers it’s your responsibility to call non-thread safe methods on their allocating thread.

So even if there is a threading bag, again I don’t know the details of this code that well, it might not necessarily mean it’s something hero can resolve.

Another idea is to check thread sanity and print warnings in debug or add assertions to help debug threading issues in hero during development.

uonuon commented 4 years ago

Hello guys, I am having the same problem have anyone got to manage to fix it?

@objc func leftSwipeDismiss(gestureRecognizer: UIPanGestureRecognizer) {

        let translation = gestureRecognizer.translation(in: nil)
        let progress = translation.x / 2 / view.bounds.width
        let gestureView = gestureRecognizer.location(in: self.view)

        switch gestureRecognizer.state {
        case .began:
            if gestureView.x <= 30 {
                hero_dismissViewController()
            }

        case .changed:
            let translation = gestureRecognizer.translation(in: nil)
            let progress = translation.x / 2 / view.bounds.width
            Hero.shared.update(progress)
        default:
            if progress + gestureRecognizer.velocity(in: nil).x / view.bounds.width > 0.3 {
                Hero.shared.finish(animate: true)
            } else {
                Hero.shared.cancel(animate: true)
            }
        }

    }
forafontov98 commented 3 years ago

Hi! I have same problem, and I understood that it appears when you call Hero.shared.cancel() and then call self.hero.dismissViewController() (or self.dismiss()). So, check:

  1. all places where you call dismiss/dismissViewController
  2. be sure that you don't call Hero.shared.cancel() before dismiss/dismissViewController
  3. change "default" case to ".ended"
iKisliy commented 3 years ago

Was facing same issue, placing transition flag check before all gesture recognizer states except began fixed. Main issue was that Hero.shared.update was involuntary set before dismiss was called guard Hero.shared.transitioning else { return }

sekny commented 1 year ago

2023 this issue still happened on the latest version, I noticed that it freezes during call Hero.shared.apply. btw call function in the main thread like @kluku mention does not solve this problem

Below is my code and show where it stuck.

func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
        let percent = max(panGesture.translation(in: view).x, 0) / view.frame.width
        let direction = panGesture.direction(in: view)

        let translation = panGesture.translation(in: view)
        let newConstant = translation.x

        switch panGesture.state {
        case .began:
            if direction == .Right && !self.isKeyboardShow {
                if self.navigationController?.isHeroEnabled ?? false {
                    isEnabledHero = true
                    Hero.shared.containerColor = .white
                    Hero.shared.defaultAnimation = .autoReverse(presenting: .push(direction: .left))
                } else {
                    isEnabledHero = false
                    navigationController?.delegate = self
                }
                navigationController?.popViewController(animated: true)
            } else {
                UIApplication.topViewController()?.view.endEditing(true)
                Hero.shared.cancelOnMain()
            }
        case .changed:
            let progress = translation.x / 1.5 / view.bounds.width
            Hero.shared.update(progress)
            if let toView = Hero.shared.toViewController?.view {
                Hero.shared.apply(modifiers: [.shadowOpacity(0.1),
                                              .shadowColor(.clear),
                                              .opacity(1),
                                              .timingFunction(.easeOut)], to: toView)
            }

            if let fromView = Hero.shared.fromViewController?.view {
                Hero.shared.apply(modifiers: [.shadowOpacity(0.1),
                                              .shadowColor(.clear),
                                              .opacity(0.65),
                                              .timingFunction(.easeOut)], to: fromView)
            }

//========~~~~~~~~~~~> IT STUCK HERE

            if let percentDrivenInteractiveTransition = percentDrivenInteractiveTransition {
                percentDrivenInteractiveTransition.update(percent)
            }
        case .ended:
            let velocity = panGesture.velocity(in: view).x
            if (percent > 0.5 || velocity > 1000) {
                Hero.shared.finishOnMain()
                navigationBar.removeFromSuperview()
            } else {
                Hero.shared.cancelOnMain()
            }
        case .cancelled, .failed:
            Hero.shared.cancelOnMain()

        default:
            break
        }
    }