How to make the center cell of a UICollectionView overlap two other cells located on the side?

I create a collection view to create a carousel effect.

I need a central cell to overlap the other two cells left and right. The central cell should always be at the top.

But, when I try to overlap the cell on the side with the cell centered, it does not work. Instead, the right cell (blue cell) overlaps the center cell (black cell), as shown in the image below.

enter image description here

The code used for this effect is as follows:

Below is a view controller for representing a collection.

import UIKit private let reuseIdentifier = "Cell" let kRoomCellScaling: CGFloat = 0.6 class RoomsViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() // This method sets up the collection view let layout = UPCarouselFlowLayout() layout.itemSize = CGSizeMake(250, 250) layout.scrollDirection = .Horizontal layout.sideItemAlpha = 1 layout.sideItemScale = 0.8 layout.spacingMode = UPCarouselFlowLayoutSpacingMode.overlap(visibleOffset: 60) collectionView?.setCollectionViewLayout(layout, animated: false) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: UICollectionViewDataSource override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { // #warning Incomplete implementation, return the number of sections return 1 } override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of items return 3 } override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) // Configure the cell switch indexPath.row%3 { case 0: cell.backgroundColor = UIColor.redColor() case 1: cell.backgroundColor = UIColor.blackColor() case 2: cell.backgroundColor = UIColor.blueColor() default: break } return cell } } 

The following is a stream layout used to represent a collection.

 import UIKit public enum UPCarouselFlowLayoutSpacingMode { case fixed(spacing: CGFloat) case overlap(visibleOffset: CGFloat) } public class UPCarouselFlowLayout: UICollectionViewFlowLayout { private struct LayoutState { var size: CGSize var direction: UICollectionViewScrollDirection func isEqual(otherState: LayoutState) -> Bool { return CGSizeEqualToSize(self.size, otherState.size) && self.direction == otherState.direction } } @IBInspectable public var sideItemScale: CGFloat = 0.6 @IBInspectable public var sideItemAlpha: CGFloat = 0.6 public var spacingMode = UPCarouselFlowLayoutSpacingMode.fixed(spacing: 40) private var state = LayoutState(size: CGSizeZero, direction: .Horizontal) override public func prepareLayout() { super.prepareLayout() let currentState = LayoutState(size: self.collectionView!.bounds.size, direction: self.scrollDirection) if !self.state.isEqual(currentState) { self.setupCollectionView() self.updateLayout() self.state = currentState } } private func setupCollectionView() { guard let collectionView = self.collectionView else { return } if collectionView.decelerationRate != UIScrollViewDecelerationRateFast { collectionView.decelerationRate = UIScrollViewDecelerationRateFast } } private func updateLayout() { guard let collectionView = self.collectionView else { return } let collectionSize = collectionView.bounds.size let isHorizontal = (self.scrollDirection == .Horizontal) let yInset = (collectionSize.height - self.itemSize.height) / 2 let xInset = (collectionSize.width - self.itemSize.width) / 2 self.sectionInset = UIEdgeInsetsMake(yInset, xInset, yInset, xInset) let side = isHorizontal ? self.itemSize.width : self.itemSize.height let scaledItemOffset = (side - side*self.sideItemScale) / 2 switch self.spacingMode { case .fixed(let spacing): self.minimumLineSpacing = spacing - scaledItemOffset case .overlap(let visibleOffset): let fullSizeSideItemOverlap = visibleOffset + scaledItemOffset let inset = isHorizontal ? xInset : yInset self.minimumLineSpacing = inset - fullSizeSideItemOverlap } } override public func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } override public func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let superAttributes = super.layoutAttributesForElementsInRect(rect), let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil } return attributes.map({ self.transformLayoutAttributes($0) }) } private func transformLayoutAttributes(attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { guard let collectionView = self.collectionView else { return attributes } let isHorizontal = (self.scrollDirection == .Horizontal) let collectionCenter = isHorizontal ? collectionView.frame.size.width/2 : collectionView.frame.size.height/2 let offset = isHorizontal ? collectionView.contentOffset.x : collectionView.contentOffset.y let normalizedCenter = (isHorizontal ? attributes.center.x : attributes.center.y) - offset let maxDistance = (isHorizontal ? self.itemSize.width : self.itemSize.height) + self.minimumLineSpacing let distance = min(abs(collectionCenter - normalizedCenter), maxDistance) let ratio = (maxDistance - distance)/maxDistance let alpha = ratio * (1 - self.sideItemAlpha) + self.sideItemAlpha let scale = ratio * (1 - self.sideItemScale) + self.sideItemScale attributes.alpha = alpha attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) return attributes } override public func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { guard let collectionView = collectionView where !collectionView.pagingEnabled, let layoutAttributes = self.layoutAttributesForElementsInRect(collectionView.bounds) else { return super.targetContentOffsetForProposedContentOffset(proposedContentOffset) } let isHorizontal = (self.scrollDirection == .Horizontal) let midSide = (isHorizontal ? collectionView.bounds.size.width : collectionView.bounds.size.height) / 2 let proposedContentOffsetCenterOrigin = (isHorizontal ? proposedContentOffset.x : proposedContentOffset.y) + midSide var targetContentOffset: CGPoint if isHorizontal { let closest = layoutAttributes.sort { abs($0.center.x - proposedContentOffsetCenterOrigin) < abs($1.center.x - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes() targetContentOffset = CGPoint(x: floor(closest.center.x - midSide), y: proposedContentOffset.y) } else { let closest = layoutAttributes.sort { abs($0.center.y - proposedContentOffsetCenterOrigin) < abs($1.center.y - proposedContentOffsetCenterOrigin) }.first ?? UICollectionViewLayoutAttributes() targetContentOffset = CGPoint(x: proposedContentOffset.x, y: floor(closest.center.y - midSide)) } return targetContentOffset } } 

So, how can I make the center cell always overlap the other two side cells?

+5
source share
1 answer

You can translate place your items with a small negative z based on distance from the center.

Replace this line:

 attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) 

with

 let visibleRect = CGRect(origin: self.collectionView!.contentOffset, size: self.collectionView!.bounds.size) let dist = CGRectGetMidX(attributes.frame) - CGRectGetMidX(visibleRect) var transform = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) transform = CATransform3DTranslate(transform, 0, 0, -abs(dist/1000)) attributes.transform3D = transform 

enter image description here

Or you can rotate the elements around the y axis based on the distance from the center and give transform.m34 slight negative value so that it has a perspective and a more realistic look.

Replace this line:

 attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) 

with

 let visibleRect = CGRect(origin: self.collectionView!.contentOffset, size: self.collectionView!.bounds.size) let dist = CGRectGetMidX(attributes.frame) - CGRectGetMidX(visibleRect) let currentAngle = dist / (CGRectGetWidth(visibleRect)/2) var transform = CATransform3DScale(CATransform3DIdentity, scale, scale, 1) transform.m34 = -1.0 / 1000 transform = CATransform3DRotate(transform, -currentAngle, 0, 1, 0) attributes.transform3D = transform 

enter image description here

+6
source

All Articles