Swift: Анимация ограничений, приводящая к различным результатам на разных устройствах

#swift #xcode #uitabbarcontroller #uianimation

Вопрос:

У меня есть UITabBarController с 2 вкладками, между которыми пользователь может переключаться. Я пытаюсь анимировать переход между обеими вкладками. Вот результат, который я хочу получить:

введите описание изображения здесь

Теперь это отлично работает на ряде устройств и симуляторов. Однако на некоторых устройствах я получаю именно это:

введите описание изображения здесь

Таким образом, это не относится к версиям iOS, потому что он работает, например, на iPad Pro (12,9 дюйма) iOS 13.0 sim, но не на iPad Air 3-го поколения iOS 13.0.

Вот код для анимации:

 class TabBarAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
struct Constants {
    static let duration = 1.0
    static let initialDestinationAlpha: CGFloat = 0.0
    static let finalDestinationAlpha: CGFloat = 1.0
    static let buttonPadding: CGFloat = 30
    static let dateIndex = 5
    static let commanderIDIndex = 4
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard let destination = transitionContext.view(forKey: UITransitionContextViewKey.to),
          let fromVC = transitionContext.viewController(forKey: .from)?.children.first as? BaseFlightLookupViewController,
          let toVc = transitionContext.viewController(forKey: .to)?.children.first as? BaseFlightLookupViewController
    else { return }
    
    destination.alpha = Constants.initialDestinationAlpha
    transitionContext.containerView.addSubview(destination)
    
    // Setup constraints to be used for view setup prior to animation
    var loginSetupConstraints = [NSLayoutConstraint]()
    var retrieveSetupConstraints = [NSLayoutConstraint]()
    
    // These constraints are activated during animation and removed once complete
    var fromLoginConstraints = [NSLayoutConstraint]()
    var fromRetrieveConstraints = [NSLayoutConstraint]()

    // Pre-animation setup for transition from login to retrieve
    if let from = fromVC as? AircraftLoginViewController, let to = toVc as? RetrieveCompletedOrderViewController {
        loginSetupConstraints = [
            to.date.trailingAnchor.constraint(equalTo: from.view.leadingAnchor),
            to.date.leadingAnchor.constraint(equalTo: from.view.leadingAnchor, constant: -from.iata.frame.width),
            to.date.bottomAnchor.constraint(equalTo: from.submitButton.topAnchor, constant: -Constants.buttonPadding)
        ]
        
        to.date.setFieldColor(UIColor.i6.blue)
        from.stackView.addSubview(to.date)
        
        loginSetupConstraints.forEach { $0.isActive = true }
        from.view.layoutIfNeeded()
    }
    // Pre-animation setup for transition from retrieve to login
    if let from = fromVC as? RetrieveCompletedOrderViewController,
       let to = toVc as? AircraftLoginViewController,
       let useCommanderID = AppConfig.current.config?.shouldUseCommanderID,
       useCommanderID {
        retrieveSetupConstraints = [
            to.commanderId.topAnchor.constraint(equalTo: from.aircraftRegistration.bottomAnchor),
            to.commanderId.trailingAnchor.constraint(equalTo: from.view.leadingAnchor),
            to.commanderId.leadingAnchor.constraint(equalTo: from.view.leadingAnchor, constant: -to.commanderId.frame.width)
        ]
        to.commanderId.setFieldColor(UIColor.i6.purple)
        from.stackView.addSubview(to.commanderId)
        
        retrieveSetupConstraints.forEach { $0.isActive = true }
        from.view.layoutIfNeeded()
    }
    
    UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
        if let from = fromVC as? AircraftLoginViewController,
           let to = toVc as? RetrieveCompletedOrderViewController {
            
            fromLoginConstraints = [
                to.date.topAnchor.constraint(equalTo: from.iata.bottomAnchor),
                to.date.leadingAnchor.constraint(equalTo: from.flightNumber.leadingAnchor),
                from.submitButton.topAnchor.constraint(equalTo: to.date.bottomAnchor, constant: Constants.buttonPadding)
            ]
            
            // If commanderID is present, add relevant constraints
            if let useCommanderID = AppConfig.current.config?.shouldUseCommanderID, useCommanderID {
                fromLoginConstraints  = [
                    from.iata.topAnchor.constraint(equalTo: from.aircraftRegistration.bottomAnchor),
                    from.commanderId.trailingAnchor.constraint(equalTo: from.view.leadingAnchor),
                    from.commanderId.leadingAnchor.constraint(equalTo: from.view.leadingAnchor, constant: -from.commanderId.frame.width)
                ]
            }
            
            self.transitionToRetrieveOrders(from: from, to: to, constraints: fromLoginConstraints, setupConstraints: loginSetupConstraints)
            
        } else if let from = fromVC as? RetrieveCompletedOrderViewController,
                  let to = toVc as? AircraftLoginViewController {
            
            fromRetrieveConstraints = [
                from.date.trailingAnchor.constraint(equalTo: from.view.leadingAnchor),
                from.date.leadingAnchor.constraint(equalTo: from.view.leadingAnchor, constant: -from.date.frame.width),
                from.submitButton.topAnchor.constraint(equalTo: from.iata.bottomAnchor, constant: Constants.buttonPadding)
            ]
            
            if let useCommanderID = AppConfig.current.config?.shouldUseCommanderID, useCommanderID {
                fromRetrieveConstraints  = [
                    to.commanderId.topAnchor.constraint(equalTo: from.aircraftRegistration.bottomAnchor),
                    to.commanderId.leadingAnchor.constraint(equalTo: from.flightNumber.leadingAnchor),
                    from.iata.topAnchor.constraint(equalTo: to.commanderId.bottomAnchor)
                ]
            } else {
                fromRetrieveConstraints.append(from.iata.topAnchor.constraint(equalTo: from.aircraftRegistration.bottomAnchor))
            }
            
            self.transitionToLogin(from: from, to: to, constraints: fromRetrieveConstraints, setupConstraints: retrieveSetupConstraints)
        }
        
    }, completion: {
        destination.alpha = Constants.finalDestinationAlpha
        destination.transform = .identity
        
        if let from = fromVC as? AircraftLoginViewController {
            self.finaliseTransition(fromVC: from, toVC: toVc, constraints: fromLoginConstraints)
        } else {
            self.finaliseTransition(fromVC: fromVC, toVC: toVc, constraints: fromRetrieveConstraints)
        }
        
        transitionContext.completeTransition($0)
    })
}

private func transitionToRetrieveOrders(from: AircraftLoginViewController, to: RetrieveCompletedOrderViewController, constraints: [NSLayoutConstraint], setupConstraints: [NSLayoutConstraint]) {
    guard let useCommanderID = AppConfig.current.config?.shouldUseCommanderID else { return }
    
    // De-activate setup constraints
    setupConstraints.forEach { $0.isActive = false }
    // Activate animation constraints
    constraints.forEach { $0.isActive = true }
    
    let fields = useCommanderID ? [from.flightNumber, from.aircraftRegistration, from.commanderId, from.iata, to.date] :
        [from.flightNumber, from.aircraftRegistration, from.iata, to.date]
    
    fields.forEach { $0.setFieldColor(UIColor.i6.purple) }
    from.submitButton.setColor(color: UIColor.i6.purple)
    from.view.layoutIfNeeded()
}

private func transitionToLogin(from: RetrieveCompletedOrderViewController, to: AircraftLoginViewController, constraints: [NSLayoutConstraint], setupConstraints: [NSLayoutConstraint]) {
    guard let useCommanderID = AppConfig.current.config?.shouldUseCommanderID else { return }
    
    // De-activate setup constraints
    if useCommanderID {
        setupConstraints.forEach { $0.isActive = false }
    }
    // Activate animation constraints
    constraints.forEach { $0.isActive = true }
    
    // Set field and button color to match new view controller
    let fields = useCommanderID ? [from.flightNumber, from.aircraftRegistration, to.commanderId, from.iata, from.date] :
        [from.flightNumber, from.aircraftRegistration, from.iata, from.date]
    
    fields.forEach { $0.setFieldColor(UIColor.i6.blue) }
    from.submitButton.setColor(color: UIColor.i6.blue)
    from.view.layoutIfNeeded()
}

private func finaliseTransition(fromVC: BaseFlightLookupViewController, toVC: BaseFlightLookupViewController, constraints: [NSLayoutConstraint]) {
    if let from = fromVC as? AircraftLoginViewController, let to = toVC as? RetrieveCompletedOrderViewController {
        to.submitButton.topAnchor.constraint(equalTo: to.date.bottomAnchor, constant: Constants.buttonPadding).isActive = true
        
        // Deactivate constraints on from VC ready to transition back
        constraints.forEach { $0.isActive = false }
        
        // Reset field and button colours to initial state
        let fields = [from.flightNumber, from.aircraftRegistration, from.commanderId, from.iata]
        fields.forEach { $0.setFieldColor(UIColor.i6.blue) }
        from.submitButton.setColor(color: UIColor.i6.blue)
        to.stackView.insert(arrangedSubview: to.date, atIndex: Constants.dateIndex)
        
    } else if let from = fromVC as? RetrieveCompletedOrderViewController, let to = toVC as? AircraftLoginViewController {
        // Deactivate constraints on from VC ready to transition back
        constraints.forEach { $0.isActive = false }
        
        // Reset field and button colours to initial state
        let fields = [from.flightNumber, from.aircraftRegistration, from.iata, from.date]
        fields.forEach { $0.setFieldColor(UIColor.i6.purple) }
        from.submitButton.setColor(color: UIColor.i6.purple)
        if let useCommanderID = AppConfig.current.config?.shouldUseCommanderID, useCommanderID {
            to.stackView.insert(arrangedSubview: to.commanderId, atIndex: Constants.commanderIDIndex)
        }
    }
}

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return Constants.duration
}
}
 

I’m struggling to see why this would be producing such different results on different sims.