Как я выяснил в этом вопросе, анимированные переходы push / pop, представленные начиная с iOS 7 (где контроллер представления, расположенный ниже в стеке навигации, "выдвигается на половину скорости", так чтоон перекрывает тот, который был нажат / вытолкнут) делает невозможным переход между контроллерами прозрачного представления без артефактов.
Поэтому я решил принять протокол UINavigationControllerDelegate
, точнее метод: navigationController(_:animationControllerFor:from:to:)
, по существу реплицирует анимированные переходы UINavigationController с нуля.
Это позволяет мне добавить представление маски к представлению, от которого переходят во время толчкаоперация, так что нижняя секция обрезается, и никаких видимых артефактов не происходит.
До сих пор я только реализовал операцию push (в конце концов, мне придется делать popоперации, а также интерактивное всплывающее окно, основанное на жестах, поп-форма), Я создал пользовательский UINavigationController
подкласс и положить его на GitHub .
(В настоящее время он поддерживает анимированные push и pop, но не интерактивность. Кроме того, я пока не понял, как воспроизвести анимацию «слайда» заголовка панели навигации - она просто пересекается.)
Это сводится к следующим шагам:
- Изначально преобразуйте вид
.to
вправо, вне экрана.Во время анимации постепенно преобразуйте его обратно в идентичность (центр экрана). - Установите белый UIView как
mask
для .from
вида, первоначально с границами, равными .from
вид (без маскировки).Во время анимации постепенно уменьшайте ширину свойства frame
маски до половины его первоначального значения. - Во время анимации постепенно переводите представление
from
наполовину за кадром влево.
В коде:
class AnimationController: NSObject, UIViewControllerAnimatedTransitioning {
// Super slow for debugging:
let duration: TimeInterval = 1
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from) else {
return
}
guard let toView = transitionContext.view(forKey: .to) else {
return
}
guard let toViewController = transitionContext.viewController(forKey: .to) else {
return
}
//
// (Code below assumes navigation PUSH operation. For POP,
// use similar code but with view roles and direction
// reversed)
//
// Add target view to container:
transitionContext.containerView.addSubview(toView)
// Set tagret view frame, centered on screen
let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
toView.frame = toViewFinalFrame
let containerBounds = transitionContext.containerView.bounds
toView.center = CGPoint(x: containerBounds.midX, y: containerBounds.midY)
// But translate it to fully the RIGHT, for now:
toView.transform = CGAffineTransform(translationX: containerBounds.width, y: 0)
// We will slide the source view out, to the LEFT, by half as much:
let fromTransform = CGAffineTransform(translationX: -0.5*containerBounds.width, y: 0)
// Apply a white UIView as mask to the source view:
let maskView = UIView(frame: CGRect(origin: .zero, size: fromView.frame.size))
maskView.backgroundColor = .white
fromView.mask = maskView
// The mask will shrink to half the width during the animation:
let maskViewNewFrame = CGRect(origin: .zero, size: CGSize(width: 0.5*fromView.frame.width, height: fromView.frame.height))
// Animate:
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut], animations: {
fromView.transform = fromTransform // off-screen to the left (half)
toView.transform = .identity // Back on screen, fully centered
maskView.frame = maskViewNewFrame // Mask to half width
}, completion: {(completed) in
// Remove mask, or funny things will happen to a
// table view or scroll view if the user
// "rubber-bands":
fromView.mask = nil
transitionContext.completeTransition(completed)
})
}
Результат:
(я добавилтекстовый вид на контроллер детального вида для наглядности)