This article is simultaneously published on my wechat official account. You can follow it by scanning the QR code at the bottom of the article or searching HelloWorld Jieshao on wechat.

Written in the beginning

Good morning, everyone. Today I am bringing you another article about UICollectionView. In the last article, we implemented a cool waterfall layout and gave you a preliminary understanding of how to create a custom layout in UICollectionView. But in the last article to achieve a slightly simpler custom layout, can only be said to be relatively rough calculation of the layout of each item position, understand the inheritance from the UICollectionFlowLayout subclass it needs to overload the meaning of the method, so today in this article we will implement a more complex custom layout: Cover Flow effect bar!

First of all, take a look at the effect picture of Cover Flow as follows:

Thought analysis

Without further ado, the Cover Flow layout has the following characteristics:

  • UICollectionView scrolls horizontally

  • As the UICollectionView scrolls, the Cell automatically scales, enlarges when the center of the Cell coincides with the center of the UICollectionView, and shrinks when it deviates from the center

  • Cell scrolling is paginated and stops at the center of the UICollectionView

The requirements are clear, so how do we implement them?

First, to implement a UICollectionView that only supports horizontal scrolling, simply set the scrollDirection in the UICollectionFlowLayout object to Horizontal.

Second step, in order to achieve the zooming effect when the Cell scrolls with UICollectionView, we need to find an appropriate time to scale the Cell. My idea is to first calculate the X coordinate of the center point of the whole scrolling content of UICollectionView. Then, the layout of each Cell is traversed to find the x coordinate of its center point, and the offset value of the two X coordinates is calculated. The smaller the distance between them, the smaller the scaling ratio will be; otherwise, the larger the scaling ratio will be. I set the maximum scaling ratio to be 1.

The third step is to achieve the scrolling effect of pagination with damping, and when the slide stops, the current enlarged Cell is displayed in the center. Some students will say: UICollectionView has paging effects. Just set isPagingEnabled to True to enable paging. You are right, but if the width and margins of our Cell do not fill up the UICollectionView, there will be a problem. Although you have achieved paging effect, your Cell will not be in the middle during scrolling. How to do this without setting isPagingEnabled? Please read on.

For those of you who have read my previous UICollectionView series, if you remember, I wrote a tutorial called “Using UICollectionView for paging slides” with a link (), It tells you how to do pagination without setting isPagingEnabled, and there’s an important method I mentioned called:

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
Copy the code

It does this because when UICollectionView stops scrolling, it returns a new offset point. It has two arguments. The first parameter is proposedContentOffset, which is the offset point when scrolling is about to stop, and the second parameter, Velocity, which is scrolling speed. Now that we have the current scroll stop coordinate, we can modify it so that its new offset coordinates are centered on the Cell. Without further elaboration, we can browse the code below.

Logic implementation

Talk is cheap, show me the code. Here is the source code of Cover Flow layout for your reference. I have annotated some of the calculation logic in it, and the code is as follows:

// // CoverFlowLayout.swift // SwiftScrollBanner // // Created by shenjie on 2021/2/24. // import UIKit class CoverFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {/ / 1. Get the array layout within the scope of the let attributes. = super layoutAttributesForElements (in: the rect) / / 2. Let centerX = collectionView! .contentOffset.x + collectionView! Width / 2 // 3. Scale the attributes of each cell accordingly based on the current scroll? .foreach ({(attr) in // Get the center of each cell and calculate the offset of the two centers let pad = abs(centerx-attr.center.x) // how to calculate the scaling ratio? My idea is that the smaller the distance, the smaller the scaling ratio, the maximum scaling ratio is 1, when the x coordinates of the two center points // coincide, Let scale = 1 / (1 + pad * CGFloat(factor)) attr. Transform = CGAffineTransform(scaleX: scale, y: scale) }) // 4. // -parameters: /// -proposedcontentoffset: /// -velocity: Returns: Roll stop point override func targetContentOffset(forProposedContentOffset proposedContentOffset) CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { var targetPoint = proposedContentOffset // 1. Let centerX = Proposedcontentoffset. x + collectionView! . Bounds. Width / 2 / / 2. Access to this point the layout of the visual range attributes let attrs = self. LayoutAttributesForElements (in: CGRect (x: proposedContentOffset.x, y: proposedContentOffset.y, width: collectionView! .bounds.size.width, height: collectionView! Var moveDistance: CGFloat = CGFloat(MAXFLOAT) // 4 Walk through the array to find the minimum distance attrs! .forEach { (attr) in if abs(attr.center.x - centerX) < abs(moveDistance) { moveDistance = attr.center.x - centerX } } //  5. Returns a new offset point if targetPoint. < x > 0 & & targetPoint. X collectionViewContentSize width - collectionView! .bounds.width { targetPoint.x += moveDistance } return targetPoint } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } override var collectionViewContentSize: CGSize { return CGSize(width: sectionInset.left + sectionInset.right + (CGFloat(collectionView! .numberOfItems(inSection: 0)) * (itemSize.width + minimumLineSpacing)) - minimumLineSpacing, height: 0) } }Copy the code

Cohesion UIViewController

Now that Cover Flow’s custom layout has been implemented, all that is left is to render it in the view controller. This step is easy to implement.

// // CoverFlowViewController.swift // SwiftScrollBanner // // Created by shenjie on 2021/2/23. // import UIKit class CoverFlowViewController: UIViewController { private let cellID = "baseCellID" var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. setUpView() } Override func viewDidLayoutSubviews () {super. ViewDidLayoutSubviews ()} func setUpView () {/ / initialization flowlayout let layout = CoverFlowLayout() let margin: CGFloat = 20 let collH: CGFloat = 200 let itemH = collH - margin * 2 let itemW = view.bounds.width - margin * 2 - 100 layout.itemSize = CGSize(width: itemW, height: itemH) layout.minimumLineSpacing = 5 layout.minimumInteritemSpacing = 5 layout.sectionInset = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: Horizontal // Initialize the ectionView collectionView = UICollectionView(frame: CGRect(x: 0, y: 180, width: view.bounds.width, height: collH), collectionViewLayout: layout) collectionView.backgroundColor = .black collectionView.showsHorizontalScrollIndicator = false CollectionView. The dataSource = self collectionView. Delegate = self/Cell/registration collectionView.register(BaseCollectionViewCell.self, forCellWithReuseIdentifier: cellID) view.addSubview(collectionView) } } extension CoverFlowViewController: UICollectionViewDelegate{ func scrollViewDidScroll(_ scrollView: UIScrollView) { } } extension CoverFlowViewController: UICollectionViewDataSource{ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 15 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! BaseCollectionViewCell cell.cellIndex = indexPath.item cell.backgroundColor = indexPath.item % 2 == 0 ? .purple : .red return cell } }Copy the code

After compiling and running, the result is as shown in the figure below:

Written in the end

Well, that’s the end of this tutorial. This article is the fourth in a series of UICollectionView tutorials that I will continue to update; If you have any questions, you can communicate with me through my official number, and you are welcome to correct your mistakes. As usual, the project address is attached at the end:

Github.com/ShenJieSuzh…

Related reading:

UICollectionView Custom layout to implement waterfall flow view

Use UICollectionView to achieve the paging slide effect

Use UICollectionView to achieve the home card rotation effect

Follow my technical official account “HelloWorld Jie Xiao “for more quality technical articles.