I am customizing collection view layout into 3 columns [left side 2 cell & right side 1 portrait cell]. I am unable to remove top space of let side 2 cells.
Code:
import UIKit
extension UICollectionView{
func getSize(noOfCellsInRow: Int, isPotrait: Bool = true)->CGSize{
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let totalSpace = flowLayout.sectionInset.left
+ flowLayout.sectionInset.right
+ (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1))
let size = Int((self.bounds.width - totalSpace) / CGFloat(noOfCellsInRow))
return CGSize(width: size, height: isPotrait ? size+size : size)
}
}
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.backgroundColor = .red
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.row == 1{
return collectionView.getSize(noOfCellsInRow: 2,isPotrait: true)
}else{
return collectionView.getSize(noOfCellsInRow: 2,isPotrait: false)
}
}
}
Output of the corresponding code:
Expected output:
How can I make it work? Any idea will help me lot. Thanks in advance...
I've customized Apple's MosaicLayout example to fit your expected output.
Here is the custom UICollectionViewLayout, with twoFiftyFifty
added as this segment style.
enum MosaicSegmentStyle {
case twoFiftyFifty
case fullWidth
case fiftyFifty
case twoThirdsOneThird
case oneThirdTwoThirds
}
class MosaicLayout: UICollectionViewLayout {
var contentBounds = CGRect.zero
var cachedAttributes = [UICollectionViewLayoutAttributes]()
/// - Tag: PrepareMosaicLayout
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
// Reset cached information.
cachedAttributes.removeAll()
contentBounds = CGRect(origin: .zero, size: collectionView.bounds.size)
// For every item in the collection view:
// - Prepare the attributes.
// - Store attributes in the cachedAttributes array.
// - Combine contentBounds with attributes.frame.
let count = collectionView.numberOfItems(inSection: 0)
var currentIndex = 0
var segment: MosaicSegmentStyle = .fiftyFifty
var lastFrame: CGRect = .zero
let cvWidth = collectionView.bounds.size.width
while currentIndex < count {
let segmentFrame = CGRect(x: 0, y: lastFrame.maxY + 1.0, width: cvWidth, height: (self.collectionView?.frame.width)!)
var segmentRects = [CGRect]()
switch segment {
case .twoFiftyFifty:
let horizontalSlices = segmentFrame.dividedIntegral(fraction:0.5, from: .minXEdge)
let verticalSlices = horizontalSlices.first.dividedIntegral(fraction: 0.5, from: .minYEdge)
segmentRects = [verticalSlices.first, verticalSlices.second, horizontalSlices.second]
case .fullWidth:
segmentRects = [segmentFrame]
case .fiftyFifty:
let horizontalSlices = segmentFrame.dividedIntegral(fraction: 0.5, from: .minXEdge)
segmentRects = [horizontalSlices.first, horizontalSlices.second]
case .twoThirdsOneThird:
let horizontalSlices = segmentFrame.dividedIntegral(fraction: (2.0 / 3.0), from: .minXEdge)
let verticalSlices = horizontalSlices.second.dividedIntegral(fraction: 0.5, from: .minYEdge)
segmentRects = [horizontalSlices.first, verticalSlices.first, verticalSlices.second]
case .oneThirdTwoThirds:
let horizontalSlices = segmentFrame.dividedIntegral(fraction: (1.0 / 3.0), from: .minXEdge)
let verticalSlices = horizontalSlices.first.dividedIntegral(fraction: 0.5, from: .minYEdge)
segmentRects = [verticalSlices.first, verticalSlices.second, horizontalSlices.second]
}
// Create and cache layout attributes for calculated frames.
for rect in segmentRects {
let attributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: currentIndex, section: 0))
attributes.frame = rect
cachedAttributes.append(attributes)
// contentBounds = contentBounds.union(lastFrame) contentBounds = contentBounds.union(rect)
currentIndex += 1
lastFrame = rect
}
// // Determine the next segment style.
// switch count - currentIndex {
// case 1:
// segment = .fullWidth
// case 2:
// segment = .fiftyFifty
// default:
// switch segment {
// case .fullWidth:
// segment = .fiftyFifty
// case .fiftyFifty:
// segment = .twoThirdsOneThird
// case .twoThirdsOneThird:
// segment = .oneThirdTwoThirds
// case .oneThirdTwoThirds:
// segment = .fiftyFifty
// }
// }
}
}
/// - Tag: CollectionViewContentSize
override var collectionViewContentSize: CGSize {
return contentBounds.size
}
/// - Tag: ShouldInvalidateLayout
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
guard let collectionView = collectionView else { return false }
return !newBounds.size.equalTo(collectionView.bounds.size)
}
/// - Tag: LayoutAttributesForItem
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cachedAttributes[indexPath.item]
}
/// - Tag: LayoutAttributesForElements
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesArray = [UICollectionViewLayoutAttributes]()
// Find any cell that sits within the query rect.
guard let lastIndex = cachedAttributes.indices.last,
let firstMatchIndex = binSearch(rect, start: 0, end: lastIndex) else { return attributesArray }
// Starting from the match, loop up and down through the array until all the attributes
// have been added within the query rect.
for attributes in cachedAttributes[..<firstMatchIndex].reversed() {
guard attributes.frame.maxY >= rect.minY else { break }
attributesArray.append(attributes)
}
for attributes in cachedAttributes[firstMatchIndex...] {
guard attributes.frame.minY <= rect.maxY else { break }
attributesArray.append(attributes)
}
return attributesArray
}
// Perform a binary search on the cached attributes array.
func binSearch(_ rect: CGRect, start: Int, end: Int) -> Int? {
if end < start { return nil }
let mid = (start + end) / 2
let attr = cachedAttributes[mid]
if attr.frame.intersects(rect) {
return mid
} else {
if attr.frame.maxY < rect.minY {
return binSearch(rect, start: (mid + 1), end: end)
} else {
return binSearch(rect, start: start, end: (mid - 1))
}
}
}
}
The divideIntegral extension:
extension CGRect {
func dividedIntegral(fraction: CGFloat, from fromEdge: CGRectEdge) -> (first: CGRect, second: CGRect) {
let dimension: CGFloat
switch fromEdge {
case .minXEdge, .maxXEdge:
dimension = self.size.width
case .minYEdge, .maxYEdge:
dimension = self.size.height
}
let distance = (dimension * fraction).rounded(.up)
var slices = self.divided(atDistance: distance, from: fromEdge)
switch fromEdge {
case .minXEdge, .maxXEdge:
slices.remainder.origin.x += 1
slices.remainder.size.width -= 1
case .minYEdge, .maxYEdge:
slices.remainder.origin.y += 1
slices.remainder.size.height -= 1
}
return (first: slices.slice, second: slices.remainder)
}
}
The MosaicCell class:
class MosaicCell: UICollectionViewCell {
static let identifer = "kMosaicCollectionViewCell"
var imageView = UIImageView()
var assetIdentifier: String?
override init(frame: CGRect) {
super.init(frame: frame)
self.clipsToBounds = true
self.autoresizesSubviews = true
imageView.frame = self.bounds
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(imageView)
// Use a random background color.
let redColor = CGFloat(arc4random_uniform(255)) / 255.0
let greenColor = CGFloat(arc4random_uniform(255)) / 255.0
let blueColor = CGFloat(arc4random_uniform(255)) / 255.0
self.backgroundColor = UIColor(red: redColor, green: greenColor, blue: blueColor, alpha: 1.0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
assetIdentifier = nil
}
}
Your ViewController class:
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
let mosaicLayout = MosaicLayout()
collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: mosaicLayout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.alwaysBounceVertical = true
collectionView.indicatorStyle = .white
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(MosaicCell.self, forCellWithReuseIdentifier: MosaicCell.identifer)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MosaicCell.identifer, for: indexPath)
cell.contentView.backgroundColor = .red
return cell
}
}
Here is the link to the Apple example: https://developer.apple.com/documentation/uikit/uicollectionview/customizing_collection_view_layouts
You will need to configure later segments as desired.