Search code examples
swiftxcodetableviewdraguiviewpropertyanimator

Broken scrolling in TableView with UIViewPropertyAnimator


Plese, help me with resolving this bug. I have first ViewController (MainVC) and this VC has a child View Controller (MainChildVC). I can see top of child VC in my MainVC. When I dragging childVC - it's ok, opens correctly, but when I try to scroll my tableView in childVC - childVC returns to its original position and doesn't open again. Please, help to resolve it.

Here is my MainChildVC code:

    enum ChildVCState {
        case expanded
        case collapsed
    }
    
    class MainVC: UIViewController {

var mainChild: MainChildVC!
lazy var mainChildHeight: CGFloat = view.frame.size.height - 50
    let mainChildHanldeAreaHeight = 80

var runningAnimations = [UIViewPropertyAnimator]()
    var animationProgressWhenInterrupted: CGFloat = 0
    
    override func loadView() {
        super.loadView()
        view = mainViews
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .black
        self.definesPresentationContext = true
        setupChild()
    }

    private func setupChild() {
        mainChild = MainChildVC()
        addChild(mainChild)
        view.insertSubview(mainChild.view, at: 2)
        mainChild.didMove(toParent: self)
        mainChild.view.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            mainChild.view.topAnchor.constraint(equalTo: mainViews.waveAnimationView.bottomAnchor, constant: -10),
            mainChild.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10),
            mainChild.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10),
            mainChild.view.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 1.0)
        ])
        mainChild.view.clipsToBounds = true
        
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(MainVC.handleMainChildTap(recognizer:)))
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(MainVC.handleMainChildPan(recognizer:)))
        
//        mainChild.mainChildViews.handleArea.addGestureRecognizer(panGestureRecognizer)
//        mainChild.mainChildViews.handleArea.addGestureRecognizer(tapGestureRecognizer)
        mainChild.view.addGestureRecognizer(tapGestureRecognizer)
        mainChild.view.addGestureRecognizer(panGestureRecognizer)
    }
    @objc private func handleMainChildTap(recognizer: UITapGestureRecognizer) {
        switch recognizer.state {
        case .ended:
            animateTransitionIfNeeded(state: nexnState, duration: 0.9)
        default:
            break
        }
    }
    
    @objc private func handleMainChildPan(recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .began:
            //start animation
            startInteractiveTransition(state: nexnState, duration: 0.9)
        case .changed:
            //update transition
            let translation = recognizer.translation(in: mainChild.mainChildViews.handleArea)
            var fractionComplete = translation.y / self.mainChildHeight
            fractionComplete = mainChildVisible ? fractionComplete : -fractionComplete
            updateInteractiveTransition(fractionCompleted: fractionComplete)
        case .ended:
            //continue transition
            continueInteractiveTransition()
        default:
            break
        }
    }
    
    private func animateTransitionIfNeeded(state: ChildVCState, duration: TimeInterval) {
        if runningAnimations.isEmpty {
            let frameAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) {
                switch state {
                case .expanded:
                    self.mainChild.view.frame.origin.y = self.view.frame.height - self.view.frame.height + 40 //self.mainChildHeight
                case .collapsed:
                    self.mainChild.view.frame.origin.y = self.view.frame.height - self.view.frame.height * 0.4
                }
            }
            frameAnimator.addCompletion { _ in
                self.mainChildVisible = !self.mainChildVisible
                self.runningAnimations.removeAll()
            }
            frameAnimator.startAnimation()
            runningAnimations.append(frameAnimator)
            
            let cornerRadiusAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
                switch state {
                case .expanded:
                    self.mainChild.view.layer.cornerRadius = 12
                    //self.view.alpha = 0
                    self.mainChild.view.alpha = 1
                    self.mainChild.view.isUserInteractionEnabled = true
                    self.mainChild.mainChildViews.tableView.isUserInteractionEnabled = true
                    self.mainChild.mainChildViews.handleArea.backgroundColor = .black
                case .collapsed:
                    self.view.alpha = 1
                    self.mainChild.view.layer.cornerRadius = 0
                    self.mainChild.mainChildViews.tableView.isUserInteractionEnabled = false
                    self.mainChild.mainChildViews.handleArea.backgroundColor = .green
                }
            }
            cornerRadiusAnimator.startAnimation()
            runningAnimations.append(cornerRadiusAnimator)
        }
    }
    private func startInteractiveTransition(state: ChildVCState, duration: TimeInterval) {
        if runningAnimations.isEmpty {
            // run animation
            animateTransitionIfNeeded(state: state, duration: duration)
        }
        for animator in runningAnimations {
            animator.pauseAnimation()
            animationProgressWhenInterrupted = animator.fractionComplete
        }
    }
    
    private func updateInteractiveTransition(fractionCompleted: CGFloat) {
        for animator in runningAnimations {
            animator.fractionComplete = fractionCompleted + animationProgressWhenInterrupted
        }
    }
    
    private func continueInteractiveTransition() {
        for animator in runningAnimations {
            animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
        }
    }

extension MainChildVC: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

Here is my ChildVC code:

import UIKit
import youtube_ios_player_helper

final class MainChildVC: UIViewController {
    
    let mainChildViews = MainChildViews()
    
    override func loadView() {
        super.loadView()
        view = mainChildViews
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.alpha = 1
        view.backgroundColor = .black
        _ = Timer.scheduledTimer(timeInterval: 20, target: self, selector: #selector(updateCellData), userInfo: nil, repeats: true)
        
        mainChildViews.tableView.delegate = self
        mainChildViews.tableView.dataSource = self
                
        tabBarController?.tabBar.isTranslucent = true
        tabBarController?.tabBar.backgroundImage = UIImage()
        tabBarController?.tabBar.shadowImage = UIImage()
        tabBarController?.tabBar.backgroundColor = UIColor.clear
        tabBarController?.tabBar.barTintColor = UIColor.clear
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        view.isUserInteractionEnabled = true
        //view.alpha = 1
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        view.alpha = 1.0
        
    }
                                                                
    
    @objc private func updateCellData() {
        if let previousCell = mainChildViews.tableView.cellForRow(at: IndexPath(row: 0, section: 4)) as? ClipsTableViewCell {
            previousCell.playerView.stopVideo()
        }
        videoIDs[0] = videoIDs.randomElement()!
        mainChildViews.tableView.reloadSections(IndexSet(integer: 4), with: .left)
    }
    
  }
}

Solution

  • Ok, I've just fixed this issue. The problem was with top constraint of my child view:

    mainChild.view.topAnchor.constraint(equalTo: mainViews.waveAnimationView.bottomAnchor, constant: -10)
    

    I deleted it and added constraints in variables in my Main View Controller:

    private var childViewHeight = NSLayoutConstraint()
    private var childViewBottom = NSLayoutConstraint()
    

    In setupChild method I set these constraints:

    childViewHeight = mainChild.view.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.4)
    childViewHeight.isActive = true
    

    Then in animateTransitionIfNeeded method I added this above switch statements:

            self.childViewHeight.isActive = false
            self.childViewHeight = self.mainChild.view.heightAnchor.constraint(equalTo: self.view.heightAnchor)
            self.childViewHeight.isActive = true
            self.view.layoutIfNeeded()
    

    Just changing previous value in childViewHeight and set new value. So it helped me.