iOS Swift:如何重新创建照片应用程序UICollectionView布局

xxt*_*axx 3 transitions uinavigationcontroller ios uicollectionview swift

我想知道如何安静一段时间如何创建iOS照片应用程序布局.我怎样才能使它看起来像放大到一个集合,同时导航栏显示一个后退按钮?

它是一个新的视图控制器,它被推送到UINavigationController?如果是这样,他们在扩展时如何设法匹配瓷砖.

甚至可能有第三方库让我轻松地重新创建这样的布局?

希望你能帮助我理解这是如何工作的概念.

gwi*_*yai 9

回答你的第一个问题,"它是一个新的视图控制器,它被推到UINavigationController上吗?".是的,它是一个新的视图控制器.Apple在这里使用的是UIViewControllerTransitioningDelegate,它允许您呈现关于视图控制器如何呈现和解除的自定义动画.

现在关于第二个问题,"希望你能帮助我理解它是如何工作的概念." 没有简单的方法来介绍它,因为涉及到很多.我重新创建了我将在下面展示的效果,但首先我需要解释一些核心原则.

来自Apple的文档,

实现转换委托对象时,可以返回不同的动画对象,具体取决于视图控制器是在呈现还是被解除.所有转换都使用转换动画对象 - 符合UIViewControllerAnimatedTransitioning协议的对象 - 来实现基本动画.过渡动画制作者对象在有限的时间段内执行一组动画.

换句话说,UIViewControllerTransitioningDelegate需要您创建的动画对象,该对象描述应如何呈现视图控制器以及如何将其解除.这些代理方法中只有两个对您想要实现的目标感兴趣,它们是:

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    let animator = PresentAnimator()
    return animator
}
Run Code Online (Sandbox Code Playgroud)

这会要求您的代表在呈现视图控制器时使用过渡动画对象.

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    let animator = DismissAnimator()
    return animator
}
Run Code Online (Sandbox Code Playgroud)

这将要求您的代理人在解除视图控制器时使用过渡动画对象.

PresentAnimator和DismissAnimator对象都符合UIViewControllerAnimatedTransitioning.来自Apple的文档:

在动画对象中,实现transitionDuration(using :)方法以指定转换的持续时间,并实现animateTransition(using :)方法自行创建动画.有关转换中涉及的对象的信息将以上下文对象的形式传递给animateTransition(using :)方法.使用该对象提供的信息在指定的持续时间内在屏幕上或屏幕外移动目标视图控制器的视图.

基本上,每个动画制作者对象都将描述视图控制器动画的持续时间以及动画的动画方式.

现在这里展示了所有这些.这就是我们将要实现的目标:

最终产品

在故事板中创建两个视图控制器.我的第一个视图控制器叫做ViewController,它包含一个Collection View和一个带有标识符"MediaCell"的Collection View单元格和一个填充该集合视图单元格的图像.集合视图单元有一个名为ImageCollectionViewCell的类,只有这个:

    class ImageCollectionViewCell: UICollectionViewCell {

     @IBOutlet weak var image: UIImageView! //links to the collection view cell's image

     }
Run Code Online (Sandbox Code Playgroud)

我的第二个视图控制器叫做ImageRevealViewController,它只有一个图像视图和顶部的灰色视图,我用它来模拟导航栏和自定义后退按钮(我已经用普通的UINavigationController导航栏尝试了所有这些但是解雇了动画师无法工作.没有任何羞耻的事情是制作看起来和行为像导航栏的东西虽然我只是为了演示).

故事板

相册

这将是您的ViewController的代码.基本上,这将是用户找到照片集合的地方,就像相册一样.你会看到我使用了两个测试图像.

    import UIKit

    class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

@IBOutlet weak var collectionView: UICollectionView!

var selectedCell = UICollectionViewCell() //the selected cell, important for the animator

var media: [UIImage] = [UIImage]() //the photo album's images

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    media.append(UIImage(named: "testimage1")!)

    media.append(UIImage(named: "testimage2")!)

    collectionView.delegate = self

    collectionView.dataSource = self
}



func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

    selectedCell = collectionView.cellForItem(at: indexPath)!

    let selectedCellImage = selectedCell as! ImageCollectionViewCell

    let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)

    let imageRevealVC = mainStoryboard.instantiateViewController(withIdentifier: "ImageRevealVC") as! ImageRevealViewController

    imageRevealVC.transitioningDelegate = self

    imageRevealVC.imageToReveal = selectedCellImage.image.image

     /*
      This is where I tried using the nav controller but things did not work out for the dismiss animator. I have commented it out.
     */

    //let navController = UINavigationController(rootViewController: imageRevealVC)

    //navController.transitioningDelegate = self

    //navigationController?.pushViewController(imageRevealVC, animated: true)

    present(imageRevealVC, animated: true, completion: nil)

}

func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    return media.count

}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MediaCell", for: indexPath) as! ImageCollectionViewCell

    cell.image.image = media[indexPath.row]

    cell.image.contentMode = .scaleAspectFill

    return cell
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let itemsPerRow:CGFloat = 3
    let hardCodedPadding:CGFloat = 2
    let itemWidth = (collectionView.bounds.width / itemsPerRow) - hardCodedPadding
    let itemHeight = itemWidth
    return CGSize(width: itemWidth, height: itemHeight)
}




 }

    extension ViewController: UIViewControllerTransitioningDelegate {

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    let animator = PresentAnimator()
    animator.originFrame = selectedCell.frame //the selected cell gives us the frame origin for the reveal animation
    return animator
}

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    let animator = DismissAnimator()
    return animator
}




 }
Run Code Online (Sandbox Code Playgroud)

UIViewControllerTransitioningDelegate与我谈到的动画师对象一起结束.请注意,在集合视图的didSelect中,我实例化了新的视图控制器并使其转换委托等于self.

动画师

制作动画师总是有三个步骤.

  1. 设置过渡
  2. 创建动画
  3. 完成过渡

现在为现在的动画师.创建一个名为PresentAnimator的新Swift类并添加以下内容:

    import Foundation

     import UIKit

   class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {

let duration = 0.5

var originFrame = CGRect.zero

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


func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    let containerView = transitionContext.containerView


    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!


    //2) create animation
    let finalFrame = toView.frame

    let xScaleFactor = originFrame.width / finalFrame.width
    let yScaleFactor = originFrame.height / finalFrame.height

    let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)

    toView.transform = scaleTransform
    toView.center = CGPoint(
        x: originFrame.midX,
        y: originFrame.midY
    )

    toView.clipsToBounds = true



    containerView.addSubview(toView)


    UIView.animate(withDuration: duration, delay: 0.0,
                   options: [], animations: {

                    toView.transform = CGAffineTransform.identity
                    toView.center = CGPoint(
                        x: finalFrame.midX,
                        y: finalFrame.midY
                    )

    }, completion: {_ in

        //3 complete the transition
        transitionContext.completeTransition(
            !transitionContext.transitionWasCancelled
        )
    })

}

   }
Run Code Online (Sandbox Code Playgroud)

现在为Dismiss Animator.创建一个名为DismissAnimator的新类并添加以下内容:

    import Foundation

    import UIKit

    class DismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {

let duration = 0.5

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

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    //1) setup the transition
    let containerView = transitionContext.containerView

    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    containerView.insertSubview(toView, belowSubview: fromView)

    //2) animations!

    UIView.animate(withDuration: duration, delay: 0.0, options: [], animations: {

        fromView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {_ in

        //3) complete the transition
        transitionContext.completeTransition(
            !transitionContext.transitionWasCancelled
        )
    })


}

    }
Run Code Online (Sandbox Code Playgroud)

图像显示

现在为最后一步,显示图像的视图控制器.在你的ImageRevealController中添加:

    import UIKit

    class ImageRevealViewController: UIViewController {

    var imageToReveal: UIImage!

@IBOutlet weak var imageRevealed: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()

    imageRevealed.image = imageToReveal



}


@IBAction func backButton(_ sender: Any) {

    dismiss(animated: true, completion: nil)

}

  }
Run Code Online (Sandbox Code Playgroud)

backButton连接到我添加到视图的按钮,其作用类似于导航栏.您可以添加自己的后退指示灯,使其更加真实.

有关UIViewControllerTransitioningDelegate的更多信息,这里有一个部分"From View Controller"使用UIViewControllerContextTransitioning消失,您可以查看并向我提供了答案.