问题
iOS UICollectionView how to Create Horizontal Scrolling rectangular layout with different size of items inside.
I want to create a Rectangular layout using UICollectionView like below. how can i achieve?
When i scroll horizontally using CollectionView 1,2,3,4,5,6 grid will scroll together to bring 7.
The Below are the dimensions of 320*480 iPhone Resolution. Updated Screen below.
First 6 items have below dimensions for iPhone 5s.
Item 1 Size is - (213*148)
Item 2 Size is - (106*75)
Item 3 Size is - (106*74)
Item 4 Size is - (106*88)
Item 5 Size is - (106*88)
Item 6 Size is - (106*88)
After item6 have same dimensions as collection View width and height like below.
Item 7 Size is - (320*237)
Item 8 Size is - (320*237)
Item 9 Size is - (320*237)
How to create a simple custom Layout Using collection view, that has horizontal scrolling?
Must appreciate for a quick solution. Thanks in advance.
回答1:
I would suggest using a StackView inside CollectionViewCell(of fixed dimension) to create a grid layout as shown in your post.
Below GridStackView
creates a dynamic grid layout based on the number of views added using method addCell(view: UIView)
.
Add this GridStackView
as the only subview of your CollectionViewCell pinning all the edges to the sides so that it fills the CollectionViewCell completely.
while preparing your CollectionViewCell, add tile views to it using the method addCell(view: UIView)
.
If only one view added, then it will show a single view occupying whole GridStackView
and so as whole CollectionViewCell.
If there is more than one view added, it will automatically layout them in the inside the CollectionViewCell.
You can tweak the code below to get the desired layout calculating the row and column. Current implementation needed rowSize
to be supplied while initializing which I used for one of my project, you need to modify it a bit to get your desired layout.
class GridStackView: UIStackView {
private var cells: [UIView] = []
private var currentRow: UIStackView?
var rowSize: Int = 3
var defaultSpacing: CGFloat = 5
init(rowSize: Int) {
self.rowSize = rowSize
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
axis = .vertical
spacing = defaultSpacing
distribution = .fillEqually
}
required init(coder: NSCoder) {
super.init(coder: coder)
translatesAutoresizingMaskIntoConstraints = false
axis = .vertical
spacing = defaultSpacing
distribution = .fillEqually
}
private func preapreRow() -> UIStackView {
let row = UIStackView(arrangedSubviews: [])
row.spacing = defaultSpacing
row.translatesAutoresizingMaskIntoConstraints = false
row.axis = .horizontal
row.distribution = .fillEqually
return row
}
func removeAllCell() {
for item in arrangedSubviews {
item.removeFromSuperview()
}
cells.removeAll()
currentRow = nil
}
func addCell(view: UIView) {
let firstCellInRow = cells.count % rowSize == 0
if currentRow == nil || firstCellInRow {
currentRow = preapreRow()
addArrangedSubview(currentRow!)
}
view.translatesAutoresizingMaskIntoConstraints = false
cells.append(view)
currentRow?.addArrangedSubview(view)
setNeedsLayout()
}
}
回答2:
I have tried with Mahan's Answer and i am getting the partially Correct output. But the issue is, index1 having full width of two items.
How to split index 1 into index1 and Index2?
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
// Do any additional setup after loading the view.
}
func setUpCollectionView() {
self.view.backgroundColor = .white
let layout = UICollectionViewFlowLayout()
// layout.minimumInteritemSpacing = 1
// layout.minimumLineSpacing = 1
layout.scrollDirection = .horizontal
let collectionView = CollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.bounces = false
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.heightAnchor.constraint(equalToConstant: 240).isActive = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
}
}
class CollectionView: UICollectionView {
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.identifier)
self.dataSource = self
self.delegate = self
self.isPagingEnabled = true
}
required init?(coder: NSCoder) {
fatalError()
}
}
extension CollectionView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
cell.backgroundColor = .blue
cell.label.text = "\(indexPath.row)"
let row = indexPath.row
switch row {
case 0:
cell.backgroundColor = .red
case 1:
cell.backgroundColor = .blue
case 2:
cell.backgroundColor = .purple
case 3:
cell.backgroundColor = .orange
case 4:
cell.backgroundColor = .cyan
case 5:
cell.backgroundColor = .green
case 6:
cell.backgroundColor = .magenta
case 7:
cell.backgroundColor = .white
case 8:
cell.backgroundColor = .blue
case 9:
cell.backgroundColor = .green
default:
cell.backgroundColor = .systemPink
}
return cell
}
}
extension CollectionView: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let row = indexPath.row
let width = collectionView.frame.width
let other = width / 3
let height = collectionView.frame.height
let o_height = height / 3
switch row {
case 0:
return CGSize(width: other * 2, height: o_height * 2)
case 1:
return CGSize(width: other * 2, height: o_height)
case 2:
return CGSize(width: other, height: o_height)
case 3:
return CGSize(width: other, height: o_height)
case 4:
return CGSize(width: other, height: o_height)
case 5, 6, 7:
return CGSize(width: other, height: o_height)
default:
return CGSize(width: width, height: height)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return .leastNormalMagnitude
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return .leastNormalMagnitude
}
}
class CollectionViewCell: UICollectionViewCell {
static let identifier = "CollectionViewCell"
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
label.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder: NSCoder) {
fatalError()
}
}
How to devide index 1 into index1 and Index2 like below?
Thanks in advance!
回答3:
- Create a new cell that contains two views. Views have equal width.
- Contstruct your data accordingly
Data
struct ItemData {
var color: [UIColor]
}
// NOTICE: 2nd item contains two colors and the rest one.
let data = [ItemData(color: [.red]), ItemData(color: [.blue, .purple]), ItemData(color: [.orange]),
ItemData(color: [.cyan]), ItemData(color: [.green]), ItemData(color: [.magenta]),
ItemData(color: [.systemPink]), ItemData(color: [.link]), ItemData(color: [.purple])]
Cell
class CollectionViewCellOne: UICollectionViewCell {
static let identifier = "CollectionViewCellOne"
var item: ItemData? {
didSet {
if let item = item {
self.leadingLabel.backgroundColor = item.color.first!
self.trailingLabel.backgroundColor = item.color.last!
}
}
}
let leadingLabel = UILabel()
let trailingLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.addSubview(leadingLabel)
self.contentView.addSubview(trailingLabel)
let width = self.frame.width / 2
leadingLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
leadingLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
leadingLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
leadingLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
leadingLabel.translatesAutoresizingMaskIntoConstraints = false
trailingLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
trailingLabel.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
trailingLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
trailingLabel.widthAnchor.constraint(equalToConstant: width).isActive = true
trailingLabel.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder: NSCoder) {
fatalError()
}
}
dequeueReusableCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCellOne.identifier, for: indexPath) as! CollectionViewCellOne
cell.item = data[indexPath.row]
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
if let color = data[indexPath.row].color.first {
cell.backgroundColor = color
}
return cell
}
}
来源:https://stackoverflow.com/questions/62130759/ios-uicollectionview-horizontal-scrolling-rectangular-layout-with-different-size