Я настроил пример Apple MosaicLayout, чтобы он соответствовал ожидаемому результату.
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[..= 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
При желании вам потребуется настроить последующие сегменты.