具有动态缩放单元格的 UICollectionView
UICollectionView with dynamically-scaling cells
我的目标是创建如下所示的布局:
我知道如何创建这些自定义 UICollectionViewCells,但我在布局方面遇到了麻烦。所有显示的单元格的宽度都不同,因此可以有,例如:第一行有四个,第二行有两个,剩下的最后一个 - 在第三行。这只是一种可能的配置,根据标签的宽度,还有更多配置(包括图像上显示的配置)。
我正在以编程方式构建所有内容。我也觉得使用 UICollectionView
是最好的选择,但我愿意接受任何建议。
提前致谢!
我已经尝试过的:
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: TagLayout()
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
private func setupCollectionView() {
collectionView.backgroundColor = .systemGray5
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(SubjectCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
//adding a view to subview and constraining it programmatically using Stevia
}
extension ProfileVC: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubjectCollectionViewCell else { return UICollectionViewCell() }
cell.data = SubjectTagData(emoji: "", subjectName: "Item I")
return cell
}
}
使用以下 collectionViewLayout
// MARK: - TagLayoutDelegate
protocol TagLayoutDelegate: class {
func widthForItem(at indexPath: IndexPath) -> CGFloat
func rowHeight() -> CGFloat
}
// MARK: - TagLayout
class TagLayout: UICollectionViewLayout {
// MARK: Variables
weak var delegate : TagLayoutDelegate?
var cellPadding : CGFloat = 5.0
var deafultRowHeight : CGFloat = 35.0
var scrollDirection : UICollectionView.ScrollDirection = .vertical
private var contentWidth: CGFloat = 0
private var contentHeight: CGFloat = 0
private var cache: [UICollectionViewLayoutAttributes] = []
// MARK: Public Functions
func reset() {
cache.removeAll()
contentHeight = 0
contentWidth = 0
}
// MARK: Override
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
super.prepare()
if scrollDirection == .vertical {
prepareForVerticalScroll()
}
else {
prepareForHorizontalScroll()
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributeElements: [UICollectionViewLayoutAttributes] = []
for attribute in cache {
if attribute.frame.intersects(rect) {
visibleLayoutAttributeElements.append(attribute)
}
}
return visibleLayoutAttributeElements
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
// MARK: Private Functions
private func prepareForVerticalScroll() {
guard cache.isEmpty, let collectionView = collectionView else {
return
}
let noOfItems = collectionView.numberOfItems(inSection: 0)
var xOffset = [CGFloat](repeating: 0.0, count: noOfItems)
var yOffset = [CGFloat](repeating: 0.0, count: noOfItems)
let insets = collectionView.contentInset
contentWidth = collectionView.bounds.width - (insets.left + insets.right)
var rowWidth: CGFloat = 0
for i in 0 ..< noOfItems {
let indexPath = IndexPath(item: i, section: 0)
let textWidth = delegate?.widthForItem(at: indexPath) ?? 75.0
let width = textWidth + cellPadding
let height = delegate?.rowHeight() ?? 30.0
let frame = CGRect(
x: xOffset[i],
y: yOffset[i],
width: width,
height: height
)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
rowWidth += frame.width
if i < noOfItems-1 {
let nextIP = IndexPath(item: i+1, section: 0)
let nextWidth = delegate?.widthForItem(at: nextIP) ?? 75.0
if rowWidth + nextWidth + cellPadding <= contentWidth {
xOffset[i+1] = xOffset[i] + width
yOffset[i+1] = yOffset[i]
}
else {
rowWidth = 0
yOffset[i+1] = yOffset[i] + (delegate?.rowHeight() ?? 30.0)
}
}
}
}
private func prepareForHorizontalScroll() {
guard cache.isEmpty, let collectionView = collectionView else {
return
}
let insets = collectionView.contentInset
contentHeight = collectionView.bounds.height - (insets.top + insets.bottom)
let rowHeight = delegate?.rowHeight() ?? deafultRowHeight
let noOfRows: Int = 2
var yOffset: [CGFloat] = []
for row in 0 ..< noOfRows {
yOffset.append(CGFloat(row) * rowHeight)
}
var row = 0
var xOffset: [CGFloat] = [CGFloat](repeating: 0.0, count: noOfRows)
for i in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: i, section: 0)
let textWidth = delegate?.widthForItem(at: indexPath) ?? 75.0
let width = textWidth + cellPadding
let frame = CGRect(
x : xOffset[row],
y : yOffset[row],
width : width,
height : rowHeight)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentWidth = max(contentWidth, frame.maxX)
xOffset[row] = xOffset[row] + width
row = row < (noOfRows - 1) ? row + 1 : 0
}
}
}
并实现如下
let tagLayout = TagLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: tagLayout)
private func setupCollectionView() {
tagLayout.delegate = self
//Your other code goes here
}
extension ProfileVC: TagLayoutDelegate {
func widthForItem(at indexPath: IndexPath) -> CGFloat {
return 100.0
}
func rowHeight() -> CGFloat {
return 30.0
}
}
我的目标是创建如下所示的布局:
我知道如何创建这些自定义 UICollectionViewCells,但我在布局方面遇到了麻烦。所有显示的单元格的宽度都不同,因此可以有,例如:第一行有四个,第二行有两个,剩下的最后一个 - 在第三行。这只是一种可能的配置,根据标签的宽度,还有更多配置(包括图像上显示的配置)。
我正在以编程方式构建所有内容。我也觉得使用 UICollectionView
是最好的选择,但我愿意接受任何建议。
提前致谢!
我已经尝试过的:
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: TagLayout()
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
private func setupCollectionView() {
collectionView.backgroundColor = .systemGray5
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(SubjectCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
//adding a view to subview and constraining it programmatically using Stevia
}
extension ProfileVC: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubjectCollectionViewCell else { return UICollectionViewCell() }
cell.data = SubjectTagData(emoji: "", subjectName: "Item I")
return cell
}
}
使用以下 collectionViewLayout
// MARK: - TagLayoutDelegate
protocol TagLayoutDelegate: class {
func widthForItem(at indexPath: IndexPath) -> CGFloat
func rowHeight() -> CGFloat
}
// MARK: - TagLayout
class TagLayout: UICollectionViewLayout {
// MARK: Variables
weak var delegate : TagLayoutDelegate?
var cellPadding : CGFloat = 5.0
var deafultRowHeight : CGFloat = 35.0
var scrollDirection : UICollectionView.ScrollDirection = .vertical
private var contentWidth: CGFloat = 0
private var contentHeight: CGFloat = 0
private var cache: [UICollectionViewLayoutAttributes] = []
// MARK: Public Functions
func reset() {
cache.removeAll()
contentHeight = 0
contentWidth = 0
}
// MARK: Override
override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
super.prepare()
if scrollDirection == .vertical {
prepareForVerticalScroll()
}
else {
prepareForHorizontalScroll()
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributeElements: [UICollectionViewLayoutAttributes] = []
for attribute in cache {
if attribute.frame.intersects(rect) {
visibleLayoutAttributeElements.append(attribute)
}
}
return visibleLayoutAttributeElements
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
// MARK: Private Functions
private func prepareForVerticalScroll() {
guard cache.isEmpty, let collectionView = collectionView else {
return
}
let noOfItems = collectionView.numberOfItems(inSection: 0)
var xOffset = [CGFloat](repeating: 0.0, count: noOfItems)
var yOffset = [CGFloat](repeating: 0.0, count: noOfItems)
let insets = collectionView.contentInset
contentWidth = collectionView.bounds.width - (insets.left + insets.right)
var rowWidth: CGFloat = 0
for i in 0 ..< noOfItems {
let indexPath = IndexPath(item: i, section: 0)
let textWidth = delegate?.widthForItem(at: indexPath) ?? 75.0
let width = textWidth + cellPadding
let height = delegate?.rowHeight() ?? 30.0
let frame = CGRect(
x: xOffset[i],
y: yOffset[i],
width: width,
height: height
)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
rowWidth += frame.width
if i < noOfItems-1 {
let nextIP = IndexPath(item: i+1, section: 0)
let nextWidth = delegate?.widthForItem(at: nextIP) ?? 75.0
if rowWidth + nextWidth + cellPadding <= contentWidth {
xOffset[i+1] = xOffset[i] + width
yOffset[i+1] = yOffset[i]
}
else {
rowWidth = 0
yOffset[i+1] = yOffset[i] + (delegate?.rowHeight() ?? 30.0)
}
}
}
}
private func prepareForHorizontalScroll() {
guard cache.isEmpty, let collectionView = collectionView else {
return
}
let insets = collectionView.contentInset
contentHeight = collectionView.bounds.height - (insets.top + insets.bottom)
let rowHeight = delegate?.rowHeight() ?? deafultRowHeight
let noOfRows: Int = 2
var yOffset: [CGFloat] = []
for row in 0 ..< noOfRows {
yOffset.append(CGFloat(row) * rowHeight)
}
var row = 0
var xOffset: [CGFloat] = [CGFloat](repeating: 0.0, count: noOfRows)
for i in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: i, section: 0)
let textWidth = delegate?.widthForItem(at: indexPath) ?? 75.0
let width = textWidth + cellPadding
let frame = CGRect(
x : xOffset[row],
y : yOffset[row],
width : width,
height : rowHeight)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentWidth = max(contentWidth, frame.maxX)
xOffset[row] = xOffset[row] + width
row = row < (noOfRows - 1) ? row + 1 : 0
}
}
}
并实现如下
let tagLayout = TagLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: tagLayout)
private func setupCollectionView() {
tagLayout.delegate = self
//Your other code goes here
}
extension ProfileVC: TagLayoutDelegate {
func widthForItem(at indexPath: IndexPath) -> CGFloat {
return 100.0
}
func rowHeight() -> CGFloat {
return 30.0
}
}