UICollectionView 自动调整大小和动态行数
UICollectionView autosize and dynamic number of rows
我正在尝试做这样的事情:
基本上,我使用的是 UICollectionView
和单元格(3 个不同的 .xib
)。
到目前为止,它有效。
我想做的事情是:
- 设置一个
autoheight
- 如果旋转,向
UIColectionView
添加 1 行
2.1 如果是平板电脑,纵向有 2 行,横向有 3 行。 (与第2点基本相同,只增加了1行。
我有这样的东西:
extension ViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator){
setSizeSize()
}
func setSizeSize(){
if(DeviceType.IS_IPAD || UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight){
if let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = CGSize(width: 1, height: 1)
layout.invalidateLayout()
}
}else{
if let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
layout.invalidateLayout()
}
}
myCollectionView.collectionViewLayout.invalidateLayout()
}
}
无效。此外,它会冻结设备。在模拟器上特别有效。 (我信任更多设备)
我也试过this,但有时它会起作用...
如果您需要更多信息,请告诉我。
提前感谢大家的帮助。
我可以建议您创建自己的 UICollectionViewFlowLayout
子类,它会生成所需的布局。这是您可以使用的简单流程布局。如果我遗漏了什么,您可以根据自己的需要进行调整。
public protocol CollectionViewFlowLayoutDelegate: class {
func numberOfColumns() -> Int
func height(at indexPath: IndexPath) -> CGFloat
}
public class CollectionViewFlowLayout: UICollectionViewFlowLayout {
private var cache: [IndexPath : UICollectionViewLayoutAttributes] = [:]
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
public weak var flowDelegate: CollectionViewFlowLayoutDelegate?
public override var collectionViewContentSize: CGSize {
return CGSize(width: self.contentWidth, height: self.contentHeight)
}
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributesArray = [UICollectionViewLayoutAttributes]()
if cache.isEmpty {
self.prepare()
}
for (_, layoutAttributes) in self.cache {
if rect.intersects(layoutAttributes.frame) {
layoutAttributesArray.append(layoutAttributes)
}
}
return layoutAttributesArray
}
public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return self.cache[indexPath]
}
public override func prepare() {
guard let collectionView = self.collectionView else {
return
}
let numberOfColumns = self.flowDelegate?.numberOfColumns() ?? 1
let cellPadding: CGFloat = 8
self.contentHeight = 0
let columnWidth = UIScreen.main.bounds.width / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let photoHeight = self.flowDelegate?.height(at: indexPath) ?? 1
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
self.cache[indexPath] = attributes
self.contentHeight = max(self.contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
}
现在在您的 UIViewController 中,您可以像这样使用它:
override func viewDidLoad() {
super.viewDidLoad()
let flowLayout = CollectionViewFlowLayout()
flowLayout.flowDelegate = self
self.collectionView.collectionViewLayout = flowLayout
}
在方向改变后使collectionView
布局无效
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let flowLayout = self.collectionView.collectionViewLayout as? CollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
}
现在让你的 ViewController
符合 CollectionViewFlowLayoutDelegate
extension ViewController: CollectionViewFlowLayoutDelegate {
func height(at indexPath: IndexPath) -> CGFloat {
guard let cell = self.collectionView.cellForItem(at: indexPath) else {
return 1
}
cell.layoutIfNeeded()
//get calculated cell height
return cell.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
}
func numberOfColumns() -> Int {
//how much columns would be shown, depends on orientation
return UIDevice.current.orientation == .portrait ? 2 : 3
}
}
这可能是另一种选择:
protocol MyLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath: IndexPath, width: CGFloat) -> CGFloat
func numberOfColumns() -> Int
}
import UIKit
class MyCollectionViewLayout: UICollectionViewLayout {
weak var delegate: MyLayoutDelegate!
public var cellPadding: CGFloat = 5.0
var cellWidth: CGFloat = 190.0
var cachedWidth: CGFloat = 0.0
var minimumInteritemSpacing: CGFloat = 10
var numberOfColumns = 1
fileprivate var cache = [UICollectionViewLayoutAttributes]()
fileprivate var contentHeight: CGFloat = 0.0
fileprivate var contentWidth: CGFloat {
if let collectionView = collectionView {
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
return 0
}
fileprivate var numberOfItems = 0
override public var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
guard let collectionView = collectionView else { return }
self.numberOfColumns = delegate.numberOfColumns()
cellWidth = (contentWidth - minimumInteritemSpacing * 2) / CGFloat(numberOfColumns)
let totalSpaceWidth = contentWidth - CGFloat(numberOfColumns) * cellWidth
let horizontalPadding = totalSpaceWidth / CGFloat(numberOfColumns + 1)
let numberOfItems = collectionView.numberOfItems(inSection: 0)
if (contentWidth != cachedWidth || self.numberOfItems != numberOfItems) {
cache = []
contentHeight = 0
self.numberOfItems = numberOfItems
}
cachedWidth = contentWidth
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * cellWidth + CGFloat(column + 1) * horizontalPadding)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for row in 0 ..< numberOfItems {
let indexPath = IndexPath(row: row, section: 0)
let cellHeight = delegate.collectionView(collectionView, heightForCellAtIndexPath: indexPath, width: cellWidth)
let height = cellPadding * 2 + cellHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: cellWidth, height: height)
let insetFrame = frame.insetBy(dx: 0, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
if column >= (numberOfColumns - 1) {
column = 0
} else {
column = column + 1
}
}
}
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
}
要使用这个:
1) 在情节提要中 select 您的 collectionView ( Es. myCollectionView ) 并搜索 CollectionViewLayout
2) 单击 class 检查器并将 class 更改为 MyCollectionViewLayout。
3) 在你的viewDidLoad中:
if let layout = myCollectionView?.collectionViewLayout as MyCollectionViewLayout {
layout.delegate = self
}
4) 添加两个协议存根:
func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath: IndexPath, width: CGFloat) -> CGFloat {
//default height for different layout
guard let layout = collectionView.collectionViewLayout as? MyCollectionViewLayout else{return 150 // or anything else}
//return what you want, even based on screen size
return 100
}
func numberOfColumns() -> Int{
//return number of columns based on screen size
return 1 // or whatever you want
}
我正在尝试做这样的事情:
UICollectionView
和单元格(3 个不同的 .xib
)。
到目前为止,它有效。
我想做的事情是:
- 设置一个
autoheight
- 如果旋转,向
UIColectionView
添加 1 行 2.1 如果是平板电脑,纵向有 2 行,横向有 3 行。 (与第2点基本相同,只增加了1行。
我有这样的东西:
extension ViewController {
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator){
setSizeSize()
}
func setSizeSize(){
if(DeviceType.IS_IPAD || UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight){
if let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = CGSize(width: 1, height: 1)
layout.invalidateLayout()
}
}else{
if let layout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
layout.invalidateLayout()
}
}
myCollectionView.collectionViewLayout.invalidateLayout()
}
}
无效。此外,它会冻结设备。在模拟器上特别有效。 (我信任更多设备)
我也试过this,但有时它会起作用...
如果您需要更多信息,请告诉我。
提前感谢大家的帮助。
我可以建议您创建自己的 UICollectionViewFlowLayout
子类,它会生成所需的布局。这是您可以使用的简单流程布局。如果我遗漏了什么,您可以根据自己的需要进行调整。
public protocol CollectionViewFlowLayoutDelegate: class {
func numberOfColumns() -> Int
func height(at indexPath: IndexPath) -> CGFloat
}
public class CollectionViewFlowLayout: UICollectionViewFlowLayout {
private var cache: [IndexPath : UICollectionViewLayoutAttributes] = [:]
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
guard let collectionView = collectionView else {
return 0
}
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
public weak var flowDelegate: CollectionViewFlowLayoutDelegate?
public override var collectionViewContentSize: CGSize {
return CGSize(width: self.contentWidth, height: self.contentHeight)
}
public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributesArray = [UICollectionViewLayoutAttributes]()
if cache.isEmpty {
self.prepare()
}
for (_, layoutAttributes) in self.cache {
if rect.intersects(layoutAttributes.frame) {
layoutAttributesArray.append(layoutAttributes)
}
}
return layoutAttributesArray
}
public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return self.cache[indexPath]
}
public override func prepare() {
guard let collectionView = self.collectionView else {
return
}
let numberOfColumns = self.flowDelegate?.numberOfColumns() ?? 1
let cellPadding: CGFloat = 8
self.contentHeight = 0
let columnWidth = UIScreen.main.bounds.width / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * columnWidth)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let indexPath = IndexPath(item: item, section: 0)
let photoHeight = self.flowDelegate?.height(at: indexPath) ?? 1
let height = cellPadding * 2 + photoHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: height)
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
self.cache[indexPath] = attributes
self.contentHeight = max(self.contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
column = column < (numberOfColumns - 1) ? (column + 1) : 0
}
}
}
现在在您的 UIViewController 中,您可以像这样使用它:
override func viewDidLoad() {
super.viewDidLoad()
let flowLayout = CollectionViewFlowLayout()
flowLayout.flowDelegate = self
self.collectionView.collectionViewLayout = flowLayout
}
在方向改变后使collectionView
布局无效
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let flowLayout = self.collectionView.collectionViewLayout as? CollectionViewFlowLayout else {
return
}
flowLayout.invalidateLayout()
}
现在让你的 ViewController
符合 CollectionViewFlowLayoutDelegate
extension ViewController: CollectionViewFlowLayoutDelegate {
func height(at indexPath: IndexPath) -> CGFloat {
guard let cell = self.collectionView.cellForItem(at: indexPath) else {
return 1
}
cell.layoutIfNeeded()
//get calculated cell height
return cell.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
}
func numberOfColumns() -> Int {
//how much columns would be shown, depends on orientation
return UIDevice.current.orientation == .portrait ? 2 : 3
}
}
这可能是另一种选择:
protocol MyLayoutDelegate: class {
func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath: IndexPath, width: CGFloat) -> CGFloat
func numberOfColumns() -> Int
}
import UIKit
class MyCollectionViewLayout: UICollectionViewLayout {
weak var delegate: MyLayoutDelegate!
public var cellPadding: CGFloat = 5.0
var cellWidth: CGFloat = 190.0
var cachedWidth: CGFloat = 0.0
var minimumInteritemSpacing: CGFloat = 10
var numberOfColumns = 1
fileprivate var cache = [UICollectionViewLayoutAttributes]()
fileprivate var contentHeight: CGFloat = 0.0
fileprivate var contentWidth: CGFloat {
if let collectionView = collectionView {
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
}
return 0
}
fileprivate var numberOfItems = 0
override public var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func prepare() {
guard let collectionView = collectionView else { return }
self.numberOfColumns = delegate.numberOfColumns()
cellWidth = (contentWidth - minimumInteritemSpacing * 2) / CGFloat(numberOfColumns)
let totalSpaceWidth = contentWidth - CGFloat(numberOfColumns) * cellWidth
let horizontalPadding = totalSpaceWidth / CGFloat(numberOfColumns + 1)
let numberOfItems = collectionView.numberOfItems(inSection: 0)
if (contentWidth != cachedWidth || self.numberOfItems != numberOfItems) {
cache = []
contentHeight = 0
self.numberOfItems = numberOfItems
}
cachedWidth = contentWidth
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns {
xOffset.append(CGFloat(column) * cellWidth + CGFloat(column + 1) * horizontalPadding)
}
var column = 0
var yOffset = [CGFloat](repeating: 0, count: numberOfColumns)
for row in 0 ..< numberOfItems {
let indexPath = IndexPath(row: row, section: 0)
let cellHeight = delegate.collectionView(collectionView, heightForCellAtIndexPath: indexPath, width: cellWidth)
let height = cellPadding * 2 + cellHeight
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: cellWidth, height: height)
let insetFrame = frame.insetBy(dx: 0, dy: cellPadding)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
attributes.frame = insetFrame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + height
if column >= (numberOfColumns - 1) {
column = 0
} else {
column = column + 1
}
}
}
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if attributes.frame.intersects(rect) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
}
要使用这个:
1) 在情节提要中 select 您的 collectionView ( Es. myCollectionView ) 并搜索 CollectionViewLayout 2) 单击 class 检查器并将 class 更改为 MyCollectionViewLayout。
3) 在你的viewDidLoad中:
if let layout = myCollectionView?.collectionViewLayout as MyCollectionViewLayout {
layout.delegate = self
}
4) 添加两个协议存根:
func collectionView(_ collectionView: UICollectionView, heightForCellAtIndexPath indexPath: IndexPath, width: CGFloat) -> CGFloat {
//default height for different layout
guard let layout = collectionView.collectionViewLayout as? MyCollectionViewLayout else{return 150 // or anything else}
//return what you want, even based on screen size
return 100
}
func numberOfColumns() -> Int{
//return number of columns based on screen size
return 1 // or whatever you want
}