
本文共 7226 字,大约阅读时间需要 24 分钟。
在讲到了一个mosaic布局,类似于常见的瀑布流
最终的效果如下:
Mosaic布局(类似于瀑布流)
文档摘录
官方文档内容
理解核心布局的过程
在布局处理的过程中,collectionview调用布局对象指定方法,其调用的顺序如下:
1.使用方法执行提供布局信息所需的前期计算
2.使用方法返回内容区域的大小 3.使用方法方法返回指定矩形中的单元格和视图的属性创建布局属性
布局属性是的实例。使用如下的方法创建:
如果标准属性类不符合您的应用需求,则可以将其子类化并扩展,以存储有关每个视图的其他信息。当对布局属性进行子类化时,需要实现用于比较自定义属性的isEqual:
方法,因为collectionview在其某些操作中使用此方法。另外还需要遵守NSCopying
协议,实现copyWithZone:
方法。
attributes
子类之外,你的UICollectionReusableView
对象还需要实现apply(_:)
方法,以便它们可以在布局时应用任何自定义属性。 类中的包含的属性:
var frame: CGRect
var bounds: CGRect
var center: CGPoint
var size: CGSize
var transform3D: CATransform3D
var transform: CGAffineTransform
var alpha: CGFloat
var zIndex: Int
var isHidden: Bool
为给定矩形中的item提供布局属性
在布局过程的最后一步,集合视图调用您的布局对象的layoutAttributesForElementsInRect:
方法。该方法的目的是为每个cell以及与指定矩形相交的每个supplementary或decoration视图提供布局属性。对于大的可滚动内容区域,集合视图可能只是要求当前可见的该内容区域的部分中的item的属性。
由于layoutAttributesForElementsInRect:
方法在prepareLayout
方法后调用,所以你应该已拥有创建或return 所需attributes的信息。layoutAttributesForElementsInRect:
方法的实现遵循如下的步骤:
- 遍历由
prepareLayout
方法生成的数据,以访问缓存的attributes或创建新的attributes。 - 检查每个item的frame,看是否与
layoutAttributesForElementsInRect:
中的矩形相交 - 对于每个相交item,将相应的
UICollectionViewLayoutAttributes
对象添加到数组。 - 将布局属性数组返回
根据你如何管理布局信息,你可以在prepareLayout
方法中创建UICollectionViewLayoutAttributes
对象,或者等到在layoutAttributesForElementsInRect:
方法中创建。但需要记住的是,缓存布局信息有许多的好处,为cell重复计算新的布局属性是一项昂贵的操作,可能会对应用程序的性能造成明显的不利影响。
根据需求提供布局的属性
collection view定期询问你的布局对象为正式布局流程之外的单个item提供属性。例如,在为项目配置插入和删除动画时,collection view会询问此信息。你的布局对象必须准备好为每个cell,supplementary view和decoration view提供布局属性。你可以通过覆盖以下方法来执行此操作:
每个自定义的布局类都被期望实现layoutAttributesForItemAtIndexPath:
方法
Mosaic布局实现过程
为什么要自定义布局,而不使用UICollectionViewFlowLayout
?
UICollectionViewFlowLayout
的行高按一行最大的item计算,其余的cell则居中,而且还要注意line space。所以自定义布局,继承自UICollectionViewLayout
类似于上一章中将的多列布局
在上面的效果图中,图片的高度和描述文字的高度是动态,可通过代理对象来获取
定义一个代理对象MosaicLayoutDelegate
protocol MosaicLayoutDelegate { //image图片的高度 func collectionView(_ collectionView: UICollectionView, heightForImageAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat //描述文字label的高度 func collectionView(_ collectionView: UICollectionView, heightForDescriptionAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat }
另外需继承UICollectionViewLayoutAttributes
,创建一个MosaicLayoutAttributes
自定义属性,在其中添加一个变量imageHeight
,来保存图片的动态高度
class MosaicLayoutAttributes: UICollectionViewLayoutAttributes { //图片的高度 var imageHeight: CGFloat = 0 //重写copy方法 override func copy(with zone: NSZone?) -> Any { let copy = super.copy(with: zone) as! MosaicLayoutAttributes copy.imageHeight = imageHeight return copy } //重写isEqual方法 override func isEqual(_ object: Any?) -> Bool { if let attributes = object as? MosaicLayoutAttributes { if attributes.imageHeight == imageHeight { return super.isEqual(object) } } return false }}
有了图片高度,就要在定义的cell中改变图片高度的约束。
如下在RoundedCharacterCell
中,重写apply(_:)
方法,使用imageHeight
class RoundedCharacterCell: UICollectionViewCell { @IBOutlet weak var characterImage: UIImageView! @IBOutlet weak var characterTitle: UILabel! @IBOutlet weak var characterInfo: UILabel! //图片高度约束 @IBOutlet weak var imageViewHeightConstraint: NSLayoutConstraint! var character: Characters? { didSet { if let theCharacter = character { characterImage.image = UIImage(named: theCharacter.name) characterTitle.text = theCharacter.title characterInfo.text = theCharacter.description } } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.layer.cornerRadius = 12.0 self.layer.borderWidth = 3 self.layer.borderColor = UIColor(red: 0.5, green: 0.47, blue: 0.25, alpha: 1.0).cgColor } override func prepareForReuse() { super.prepareForReuse() characterImage.image = nil characterTitle.text = "" characterInfo.text = "" } //重写apply方法 override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { super.apply(layoutAttributes) let attributes = layoutAttributes as! MosaicLayoutAttributes imageViewHeightConstraint.constant = attributes.imageHeight }}
在定义的布局MosaicViewLayout
中,重写如下的方法和属性:
class var layoutAttributesClass
,表示使用的attribute为MosaicLayoutAttributes
类型 2.重写prepare()
方法,做初始化计算 3.重写var collectionViewContentSize
,返回内容区域的大小 4.重写layoutAttributesForElements(in rect: CGRect)
方法 class MosaicViewLayout: UICollectionViewLayout { //代理对象 var delegate: MosaicLayoutDelegate! //几列 var numberOfColumns = 0 var cellPadding: CGFloat = 0 //保存计算的属性 var cache = [MosaicLayoutAttributes]() //内容区域的高度 fileprivate var contentHeight: CGFloat = 0 //宽度 fileprivate var width: CGFloat { get { let insets = collectionView!.contentInset return collectionView!.bounds.width - (insets.left + insets.right) } } //内容区域的大小 override var collectionViewContentSize : CGSize { return CGSize(width: width, height: contentHeight) } //表示attribute是MosaicLayoutAttributes类 override class var layoutAttributesClass : AnyClass { return MosaicLayoutAttributes.self } //初始化计算 override func prepare() { if cache.isEmpty { let columnWidth = width / CGFloat(numberOfColumns) var xOffsets = [CGFloat]() for column in 0..= (numberOfColumns - 1) ? 0 : column + 1 } } } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var layoutAttributes = [UICollectionViewLayoutAttributes]() for attributes in cache { if attributes.frame.intersects(rect) {//相交 layoutAttributes.append(attributes) } } return layoutAttributes }}
在控制器中设置layout的代理对象等其它属性
let layout = collectionViewLayout as! MosaicViewLayoutlayout.delegate = selflayout.numberOfColumns = 2layout.cellPadding = 5
实现MosaicLayoutDelegate
方法,返回图片和文字的高度
AVFoundation
的AVMakeRect(aspectRatio: CGSize, insideRect boundingRect: CGRect)
方法 extension MasterViewController: MosaicLayoutDelegate { func collectionView(_ collectionView: UICollectionView, heightForImageAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat { let character = charactersData[indexPath.item] let image = UIImage(named: character.name) let boundingRect = CGRect(x: 0, y: 0, width: width, height: CGFloat(MAXFLOAT)) let rect = AVMakeRect(aspectRatio: image!.size, insideRect: boundingRect) return rect.height } func collectionView(_ collectionView: UICollectionView, heightForDescriptionAtIndexPath indexPath: IndexPath, withWidth width: CGFloat) -> CGFloat { let character = charactersData[indexPath.item] let descriptionHeight = heightForText(character.description, width: width-24) let height = 4 + 17 + 4 + descriptionHeight + 12 return height } func heightForText(_ text: String, width: CGFloat) -> CGFloat { let font = UIFont.systemFont(ofSize: 10) let rect = NSString(string: text).boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil) return ceil(rect.height) }}
转载地址:https://windzen.blog.csdn.net/article/details/70230998 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
关于作者
