iOS Swift:如何重新创建Photos App UICollectionView布局 [英] iOS Swift: How to recreate a Photos App UICollectionView Layout

查看:286
本文介绍了iOS Swift:如何重新创建Photos App UICollectionView布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我现在想安静一会儿如何创建iOS Photos App布局.如何使它看起来像放大集合,同时导航栏显示后退按钮?

I am wondering for quiet a while now how to create the iOS Photos App layout. How can I make it so it looks like zooming in to a collection while at the same time the navigation bar shows a back button?

这是一个新的视图控制器,可以推送到UINavigationController上吗?如果是这样,它们在扩展时如何精确匹配图块.

Is it a new view controller which gets pushed onto a UINavigationController? And if so, how exactly do they manage to match the tiles while expanding.

也许甚至还有一个第三方库可以让我轻松地重新创建这样的布局?

Is there maybe even a 3rd party library which lets me easily recreate such a layout?

希望您能帮助我理解其工作原理的概念.

Hope you can help me to understand the concept of how this works.

推荐答案

要回答您的第一个问题,这是一个新的视图控制器,可以推送到UINavigationController上吗?".是的,它是一个新的视图控制器.苹果公司在这里使用的是一个UIViewControllerTransitioningDelegate,它允许您呈现自定义动画,说明如何呈现和关闭视图控制器.

To answer your first question, "Is it a new view controller which gets pushed onto a UINavigationController?". Yes, it is a new view controller. What Apple is using here is a UIViewControllerTransitioningDelegate which allows you to present a custom animation on how a view controller is presented and dismissed.

现在要回答第二个问题,希望您能帮助我理解其工作原理的概念."由于涉及到很多内容,所以没有简单的方法可以说明这一点.我已经重新创建了将在下面显示的效果,但是首先我需要解释一些核心原理.

Now on the second question, "Hope you can help me to understand the concept of how this works." There is no easy way to put it as quite a lot is involved. I have recreated the effect which I will show below but first I need to explain some core principles.

根据Apple的文档,

From Apple's docs,

在实现过渡委托对象时,可以根据呈现或关闭视图控制器来返回不同的动画师对象.所有过渡都使用过渡动画器对象(符合UIViewControllerAnimatedTransitioning协议的对象)来实现基本动画.过渡动画器对象会在有限的时间内执行一组动画.

When implementing your transitioning delegate object, you can return different animator objects depending on whether a view controller is being presented or dismissed. All transitions use a transition animator object—an object that conforms to the UIViewControllerAnimatedTransitioning protocol—to implement the basic animations. A transition animator object performs a set of animations over a finite period of time.

换句话说,UIViewControllerTransitioningDelegate期望您创建一个动画器对象,该对象描述了应该如何显示视图控制器以及如何取消视图控制器.这些委托方法中只有两个与您要实现的目标有关,它们是:

In other words, the UIViewControllerTransitioningDelegate expects an animator object which you create that describes how the view controller should be presented and how it should be dismissed. Only two of these delegates methods are of interest to what you want to achieve and these are:

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

这会要求您的委托人提供呈现视图控制器时要使用的过渡动画器对象.

This asks your delegate for the transition animator object to use when presenting a view controller.

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

这会要求您的委托人提供在关闭视图控制器时要使用的过渡动画器对象.

This asks your delegate for the transition animator object to use when dismissing a view controller.

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

Both the PresentAnimator and DismissAnimator object conform to UIViewControllerAnimatedTransitioning. From Apple's docs:

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

In your animator object, implement the transitionDuration(using:) method to specify the duration of your transition and implement the animateTransition(using:) method to create the animations themselves. Information about the objects involved in the transition is passed to your animateTransition(using:) method in the form of a context object. Use the information provided by that object to move the target view controller’s view on or off screen over the specified duration.

基本上,每个动画制作器对象都将描述视图控制器动画的持续时间以及如何对其进行动画处理.

Basically, each animator object will describe the duration of the view controller's animation and how it will be animated.

现在这是所有这些的演示.这是我们将要实现的:

Now here is a demonstration of all this. This is what we will achieve:

在情节提要中创建两个视图控制器.我的第一个视图控制器称为ViewController,其中包含一个Collection View和一个带有标识符"MediaCell"的Collection View单元,以及一个填充该Collection View单元的图像.集合视图单元格具有一个名为ImageCollectionViewCell的类,仅具有以下内容:

Create two view controllers in your storyboard. My first view controller is called ViewController which contains a Collection View and a Collection View cell with an identifier "MediaCell" and an image that fills that collection view cell. The collection view cell has a class called ImageCollectionViewCell with only this:

    class ImageCollectionViewCell: UICollectionViewCell {

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

     }

我的第二个视图控制器称为ImageRevealViewController,它在顶部仅具有一个图像视图和一个灰色视图,用于模拟导航栏和自定义后退按钮(我已经使用普通的UINavigationController导航栏尝试了所有这些操作但解雇动画师无法正常工作.尽管我的演示只是用于演示,但制作外观和行为类似于导航栏的内容并不会让人感到羞耻.

My second view controller is called ImageRevealViewController which simply has a single image view and a grey view at the top that I am using to simulate a navigation bar and a custom back button (I have tried all this with a normal UINavigationController nav bar but the dismiss animator fails to work. There is no shame through is making something that looks and acts like a navigation bar although mine is just for demo).

相册

这将是您的ViewController的代码.基本上,这是用户找到像相册一样的照片集的地方.如您所见,我为我使用了两个测试图像.

This will be the code for your ViewController. Basically this will be the place the user finds a collection of photos just like the Photo Album. I used two test images for mine as you will see.

    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
}




 }

UIViewControllerTransitioningDelegate位于我刚才谈到的动画师对象的最后.请注意,在集合视图的didSelect中,我实例化了新的视图控制器,并使它的过渡委托与self相等.

The UIViewControllerTransitioningDelegate is at the end alongside the animator objects I talked about. Notice in the didSelect of the collection view that I instantiate the new view controller and make its transitioning delegate equal to self.

动画师

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

There are always three steps to making an animator.

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

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

Now for the Present Animator. Create a new Swift class called PresentAnimator and add the following:

    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
        )
    })

}

   }

现在是解雇动画师".创建一个名为DismissAnimator的新类,并添加以下内容:

Now for the Dismiss Animator. Create a new class called DismissAnimator and add the following:

    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
        )
    })


}

    }

显示的图片

现在是最后一步,显示图像的视图控制器.在您的ImageRevealController中添加以下内容:

Now for the final step, the view controller that reveals the image. In your ImageRevealController add this:

    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)

}

  }

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

The backButton connects to the button that I added to the view that acts like nav bar. You can add your own back indicator to make it more authentic.

有关UIViewControllerTransitioningDelegate的更多信息,请在此处>来自视图控制器"部分中.使用UIViewControllerContextTransitioning消失,您可以调查一下并向我提供答案.

For more info on UIViewControllerTransitioningDelegate there is a section here "From View Controller" disappears using UIViewControllerContextTransitioning, you could look into and to which I have contributed an answer.

这篇关于iOS Swift:如何重新创建Photos App UICollectionView布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆