删除每个 UICollectionViewCell 之间的间距并将其移动到 UICollectionView 的外部单元格
Posted
技术标签:
【中文标题】删除每个 UICollectionViewCell 之间的间距并将其移动到 UICollectionView 的外部单元格【英文标题】:Remove spacing between each UICollectionViewCell and shift it to the outer cells of the UICollectionView 【发布时间】:2018-07-19 20:11:38 【问题描述】:抱歉,如果我的问题的标题不清楚,但本质上我想制作一个 UICollectionView,如下面的 Medium 应用程序:
我已经制作了一个 UICollectionView,到目前为止它是这样的:
我想减小每个单元格之间的间距(红线),以便它们更靠近在一起,并且在 collectionview 的两侧和边界上的单元格之间有空间。我使用了minimumInteritemSpacing
和minimumLineSpacing
,但它们对红色空间完全没有影响。
这是我的代码:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
let data = ["Autos", "Cleaning", "Technology", "Business", "Sports", "Childcare", "Airsoft", "Cycling", "Fitness", "Baseball", "Basketball", "Bird Watching", "Bodybuilding", "Camping", "Dowsing", "Driving", "Fishing", "Flying", "Flying Disc", "Foraging", "Freestyle Football", "Gardeing", "Geocaching", "Ghost hunting", "Grafitti", "Handball", "High-power rocketry", "Hooping", "Horseback riding", "Hunting"]
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let layout = UICollectionViewFlowLayout.init()
layout.scrollDirection = .vertical
layout.minimumInteritemSpacing = 5.0
layout.minimumLineSpacing = 20.0
let collectionView = UICollectionView.init(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(collectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.backgroundColor = .white
self.view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 18.0).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -18.0).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor).isActive = true
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return data.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! collectionViewCell
cell.textLabel.text = data[indexPath.row]
return cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
let messageText = data[indexPath.row]
let size = CGSize.init(width: collectionView.frame.size.width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString.init(string: messageText).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 15.0, weight: .regular)], context: nil)
return CGSize.init(width: estimatedFrame.width + 20.0, height: estimatedFrame.height + 20.0)
class collectionViewCell: UICollectionViewCell
var textLabel: UILabel =
let label = UILabel.init()
label.font = UIFont.systemFont(ofSize: 15.0, weight: .regular)
label.textColor = UIColor.black
label.textAlignment = .center
return label
()
override init(frame: CGRect)
super.init(frame: frame)
self.contentView.addSubview(textLabel)
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
textLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
setupShadow()
func setupShadow()
self.contentView.backgroundColor = .white
self.contentView.layer.cornerRadius = 2.0
self.contentView.clipsToBounds = true
let shadowSize : CGFloat = 1.0
let shadowPath = UIBezierPath(rect: CGRect(x: -shadowSize / 2,
y: -shadowSize / 2,
width: self.contentView.frame.size.width + shadowSize,
height: self.contentView.frame.size.height + shadowSize))
self.contentView.layer.masksToBounds = false
self.contentView.layer.shadowColor = UIColor.black.cgColor
self.contentView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
self.contentView.layer.shadowOpacity = 0.5
self.contentView.layer.shadowPath = shadowPath.cgPath
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
【问题讨论】:
【参考方案1】:这可以通过从UICollectionViewFlowLayout
继承的自定义布局来实现。这是一个布局和演示 CollectionView/Cell 实现。
更新了 ViewController.swift
class ViewController: UIViewController
private var collectionView: UICollectionView!
private let demoLabel = UILabel()
private let minCellSpacing: CGFloat = 16.0
private var maxCellWidth: CGFloat!
var data: [String] = ["Tech", "Design", "Humor", "Travel", "Music", "Writing", "Social Media", "Life", "Education", "Edtech", "Education Reform", "Photography", "Startup", "Poetry", "Women In Tech", "Female Founders", "Business", "Fiction", "Love", "Food", "Sports", "Autos", "Cleaning", "Technology", "Business", "Sports", "Childcare", "Airsoft", "Cycling", "Fitness", "Baseball", "Basketball", "Bird Watching", "Bodybuilding", "Camping", "Dowsing", "Driving", "Fishing", "Flying", "Flying Disc", "Foraging", "Freestyle Football", "Gardeing", "Geocaching", "Ghost hunting", "Grafitti", "Handball", "High-power rocketry", "Hooping", "Horseback riding", "Hunting"]
override func viewDidLoad()
super.viewDidLoad()
self.maxCellWidth = UIScreen.main.bounds.width - (minCellSpacing * 2)
self.view.backgroundColor = .white
self.demoLabel.font = CollectionViewCell().label.font
let layout = FlowLayout()
layout.sectionInset = UIEdgeInsets(top: self.minCellSpacing, left: 2.0, bottom: self.minCellSpacing, right: 2.0)
layout.minimumInteritemSpacing = self.minCellSpacing
layout.minimumLineSpacing = 16.0
collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .clear
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor).isActive = true
// Leading/Trailing gutter CellSpacing+ShadowWidth
collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: minCellSpacing + layout.sectionInset.left).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -(minCellSpacing + layout.sectionInset.right)).isActive = true
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return self.data.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CollectionViewCell
cell.label.text = self.data[indexPath.item]
return cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
self.demoLabel.text = self.data[indexPath.item]
self.demoLabel.sizeToFit()
return CGSize(width: min(self.demoLabel.frame.width + 16, self.maxCellWidth), height: 36.0)
FlowLayout.swift
class FlowLayout: UICollectionViewFlowLayout
private var attribs = [IndexPath: UICollectionViewLayoutAttributes]()
override func prepare()
self.attribs.removeAll()
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
var updatedAttributes = [UICollectionViewLayoutAttributes]()
let sections = self.collectionView?.numberOfSections ?? 0
var indexPath = IndexPath(item: 0, section: 0)
while (indexPath.section < sections)
guard let items = self.collectionView?.numberOfItems(inSection: indexPath.section) else continue
while (indexPath.item < items)
if let attributes = layoutAttributesForItem(at: indexPath), attributes.frame.intersects(rect)
updatedAttributes.append(attributes)
let headerKind = UICollectionElementKindSectionHeader
if let headerAttributes = layoutAttributesForSupplementaryView(ofKind: headerKind, at: indexPath)
updatedAttributes.append(headerAttributes)
let footerKind = UICollectionElementKindSectionFooter
if let footerAttributes = layoutAttributesForSupplementaryView(ofKind: footerKind, at: indexPath)
updatedAttributes.append(footerAttributes)
indexPath.item += 1
indexPath = IndexPath(item: 0, section: indexPath.section + 1)
return updatedAttributes
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
if let attributes = attribs[indexPath]
return attributes
var rowCells = [UICollectionViewLayoutAttributes]()
var collectionViewWidth: CGFloat = 0
if let collectionView = collectionView
collectionViewWidth = collectionView.bounds.width - collectionView.contentInset.left
- collectionView.contentInset.right
var rowTestFrame: CGRect = super.layoutAttributesForItem(at: indexPath)?.frame ?? .zero
rowTestFrame.origin.x = 0
rowTestFrame.size.width = collectionViewWidth
let totalRows = self.collectionView?.numberOfItems(inSection: indexPath.section) ?? 0
// From this item, work backwards to find the first item in the row
// Decrement the row index until a) we get to 0, b) we reach a previous row
var startIndex = indexPath.row
while true
let lastIndex = startIndex - 1
if lastIndex < 0
break
let prevPath = IndexPath(row: lastIndex, section: indexPath.section)
let prevFrame: CGRect = super.layoutAttributesForItem(at: prevPath)?.frame ?? .zero
// If the item intersects the test frame, it's in the same row
if prevFrame.intersects(rowTestFrame)
startIndex = lastIndex
else
// Found previous row, escape!
break
// Now, work back UP to find the last item in the row
// For each item in the row, add it's attributes to rowCells
var cellIndex = startIndex
while cellIndex < totalRows
let cellPath = IndexPath(row: cellIndex, section: indexPath.section)
if let cellAttributes = super.layoutAttributesForItem(at: cellPath),
cellAttributes.frame.intersects(rowTestFrame),
let cellAttributesCopy = cellAttributes.copy() as? UICollectionViewLayoutAttributes
rowCells.append(cellAttributesCopy)
cellIndex += 1
else
break
let flowDelegate = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout
let selector = #selector(UICollectionViewDelegateFlowLayout.collectionView(_:layout:minimumInteritemSpacingForSectionAt:))
let delegateSupportsInteritemSpacing = flowDelegate?.responds(to: selector) ?? false
var interitemSpacing = minimumInteritemSpacing
// Check for minimumInteritemSpacingForSectionAtIndex support
if let collectionView = collectionView, delegateSupportsInteritemSpacing && rowCells.count > 0
interitemSpacing = flowDelegate?.collectionView?(collectionView,
layout: self,
minimumInteritemSpacingForSectionAt: indexPath.section) ?? 0
let aggregateInteritemSpacing = interitemSpacing * CGFloat(rowCells.count - 1)
var aggregateItemWidths: CGFloat = 0
for itemAttributes in rowCells
aggregateItemWidths += itemAttributes.frame.width
let alignmentWidth = aggregateItemWidths + aggregateInteritemSpacing
let alignmentXOffset: CGFloat = (collectionViewWidth - alignmentWidth) / 2
var previousFrame: CGRect = .zero
for itemAttributes in rowCells
var itemFrame = itemAttributes.frame
if previousFrame.equalTo(.zero)
itemFrame.origin.x = alignmentXOffset
else
itemFrame.origin.x = previousFrame.maxX + interitemSpacing
itemAttributes.frame = itemFrame
previousFrame = itemFrame
attribs[itemAttributes.indexPath] = itemAttributes
return attribs[indexPath]
CollectionViewCell.swift
class CollectionViewCell: UICollectionViewCell
let label = UILabel()
override init(frame: CGRect)
super.init(frame: frame)
self.label.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(label)
self.contentView.addConstraints([
NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: contentView,
attribute: .leading, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: contentView,
attribute: .top, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: contentView, attribute: .trailing, relatedBy: .equal, toItem: label,
attribute: .trailing, multiplier: 1.0, constant: 8.0),
NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: label,
attribute: .bottom, multiplier: 1.0, constant: 8.0)])
self.backgroundColor = .white
self.label.textColor = UIColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1)
self.layer.cornerRadius = 3.0
self.layer.shadowColor = UIColor.darkGray.cgColor
self.layer.shadowOffset = CGSize(width: 0.1, height: 0.2)
self.layer.shadowOpacity = 0.28
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
这是最终的用户界面
【讨论】:
哇,这真是一个优雅的答案,不敢相信我没有想到使用自定义 FlowLayout! @AamirR - 参考您的回答,我可以问一个与标签 collectionView items 相关的问题吗?以上是关于删除每个 UICollectionViewCell 之间的间距并将其移动到 UICollectionView 的外部单元格的主要内容,如果未能解决你的问题,请参考以下文章
获取我正在触摸的文本视图在哪个 UIcollectionviewcell
如何通过 UIButton 删除 UICollectionViewCell
删除最后一个 UICollectionViewCell 时出现 NSRangeException