Масштабирование анимации с помощью UICollectionView внутри UINavigationController - PullRequest
0 голосов
/ 21 марта 2020

Я пытаюсь анимировать переход от UICollectionViewController к UIViewController.

TLDR: в последней функции, как я могу получить кадр, который будет учитывать панель навигации?

Каждая ячейка имеет UIImageView, и я хочу увеличить его при переходе к контроллеру представления. Это похоже на приложение «Фото», за исключением того, что изображение не отцентрировано, и вокруг него есть другие виды (надписи, и т. Д. c.).

UIImageView размещается с использованием AutoLayout, центрируется по горизонтали и на 89 точек из верхнее безопасное руководство по компоновке.

Это почти работает, за исключением того, что я не могу получить правильный кадр для целевого UIImageView, он не учитывает навигацию, и поэтому начало кадра у отключено столько же.

Мой UICollectionViewController - это UINavigationControllerDelegate

class CollectionViewController: UINavigationControllerDelegate {
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard segue.identifier == detailSegue,
            let destination = segue.destination as? CustomViewController,
            let indexPath = sender as? IndexPath else { return }
        navigationController?.delegate = self
        …
    }

    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        switch operation {
        case .push:
            return ZoomInAnimator()
        default:
            return nil
        }
    }
}

Объект ZoomInAnimator выглядит так:

open class ZoomInAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }

public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let fromViewController = transitionContext.viewController(forKey: .from),
            let fromContent = (fromViewController  as? ZoomAnimatorDelegate)?.animatedContent(),
            let toViewController = transitionContext.viewController(forKey: .to),
            let toContent = (toViewController as? ZoomAnimatorDelegate)?.animatedContent() else { return }

        let finalFrame = toContent.pictureFrame!

        // Set pre-animation state
        toViewController.view.alpha = 0
        toContent.picture.isHidden = true

        // Add a new UIImageView where the "from" picture currently is
        let transitionImageView = UIImageView(frame: fromContent.pictureFrame)
        transitionImageView.image = fromContent.picture.image
        transitionImageView.contentMode = fromContent.picture.contentMode
        transitionImageView.clipsToBounds = fromContent.picture.clipsToBounds

        transitionContext.containerView.addSubview(toViewController.view)
        transitionContext.containerView.addSubview(transitionImageView)

        // Animate the transition
        UIView.animate(
            withDuration: transitionDuration(using: transitionContext),
            delay: 0,
            usingSpringWithDamping: 0.8,
            initialSpringVelocity: 0,
            options: [.transitionCrossDissolve],
            animations: {
                transitionImageView.frame = finalFrame
                toViewController.view.alpha = 1
        }) { completed in
            transitionImageView.removeFromSuperview()
            toContent.picture.isHidden = false
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
}

Как видите, Я спрашиваю оба контроллера для их UIImageViews и их фреймов, используя animatedContent(). Эта функция определена как в UICollectionViewController, так и в UIViewController, которые оба соответствуют этому протоколу:

protocol ZoomAnimatorDelegate {
    func animatedContent() -> AnimatedContent?
}

, где AnimatedContent представляет собой простую структуру:

struct AnimatedContent {
    let picture: UIImageView!
    let pictureFrame: CGRect!
}

Вот как эта функция реализован с обеих сторон.

Эта сторона отлично работает (UICollectionViewController / fromViewController в переходе):

extension CollectionViewController: ZoomAnimatorDelegate {

    func animatedContent() -> AnimatedContent? {
        guard let indexPath = collectionView.indexPathsForSelectedItems?.first else { return nil }
        let cell = cellOrPlaceholder(for: indexPath) // Custom method to get the cell, I'll skip the details
        let frame = cell.convert(cell.picture.frame, to: view)
        return AnimatedContent(picture: cell.picture, pictureFrame: frame)
    }
}

Это проблематичная c часть.

Начало кадра изображения составляет 89 пт и игнорирует панель навигации, которая добавляет смещение 140 пт. Я пытался все виды конвертировать (в :) без какого-либо успеха. Кроме того, я немного обеспокоен вызовом view.layoutIfNeeded здесь, это способ сделать это?

class ViewController: ZoomAnimatorDelegate {
    @IBOutlet weak var picture: UIImageView!
    func animatedContent() -> AnimatedContent? {
        view.layoutIfNeeded()
        return AnimatedContent(picture: picture, profilePictureFrame: picture.frame)
    }
}

РЕДАКТИРОВАТЬ: Вот обновленная версия animateTransition (пока не используются снимки , но я доберусь туда)

public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    guard let fromViewController = transitionContext.viewController(forKey: .from) as? CollectionViewController,
        let fromContent = fromViewController.animatedContent(),
        let toViewController = transitionContext.viewController(forKey: .to) as? ViewController,
        let toImageView = toViewController.picture else { return }

    transitionContext.containerView.addSubview(toViewController.view)
    toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
    toViewController.view.setNeedsLayout()
    toViewController.view.layoutIfNeeded()
    let finalFrame = toViewController.view.convert(toImageView.frame, to: transitionContext.containerView)

    let transitionImageView = UIImageView(frame: fromContent.pictureFrame)
    transitionImageView.image = fromContent.picture.image
    transitionImageView.contentMode = fromContent.picture.contentMode
    transitionImageView.clipsToBounds = fromContent.picture.clipsToBounds
    transitionContext.containerView.addSubview(transitionImageView)

    // Set pre-animation state
    toViewController.view.alpha = 0
    toImageView.isHidden = true

    UIView.animate(
        withDuration: transitionDuration(using: transitionContext),
        delay: 0,
        usingSpringWithDamping: 0.8,
        initialSpringVelocity: 0,
        options: [.transitionCrossDissolve],
        animations: {
            transitionImageView.frame = finalFrame
            toViewController.view.alpha = 1
    }) { completed in
        transitionImageView.removeFromSuperview()
        toImageView.isHidden = false
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    }
}

1 Ответ

1 голос
/ 21 марта 2020

Проблема в том, что вы получаете последний кадр неправильно:

let finalFrame = toContent.pictureFrame!

Вот что вы должны были сделать:

  1. Начните с запроса контекста перехода для последний кадр представления контроллера вида to путем вызова finalFrame(for:). Тот факт, что вы никогда не вызываете этот метод, является явной ошибкой; Вы должны всегда называть это!

  2. Теперь используйте это как рамку для анимации (если вы знаете, что изображение заполнит ее полностью), или используйте ее в качестве основы для вычисления этого кадра.

    В последнем случае я выполняю фиктивную операцию макета, чтобы узнать, какой кадр изображения будет в представлении контроллера представления, а затем преобразовать система координат для системы координат контекста перехода. Тот факт, что я не вижу, чтобы вы выполняли какое-либо преобразование системы координат, является признаком еще одной ошибки в вашем коде. Помните, frame рассчитывается в терминах системы координат суперпредставления представления.

Вот пример из одного из моих приложений; это представленный V C, а не выдвинутый V C, но это не имеет никакого значения для расчета:

enter image description here

Посмотрите, откуда я знаю, где красный образец будет в последнем кадре представления представленного контроллера представления, и я перемещаю снимок красного образца в этот кадр с точки зрения системы координат контекста перехода.

Также обратите внимание, что Я использую снимок в качестве прокси. Вы используете свободный просмотр изображений в качестве прокси, и это тоже может быть хорошо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...