// // FlowContainer.swift // Crush // // Created by Leon on 2025/7/19. // import UIKit class FlowAutoLayoutContainer: UIView { // MARK: - Properties /// Item 之间的水平间距 var itemSpacing: CGFloat = 8.0 { didSet { setNeedsLayout() } } /// 行之间的垂直间距 var lineSpacing: CGFloat = 8.0 { didSet { setNeedsLayout() } } /// 存储子视图 private var arrangedSubviews: [UIView] = [] // MARK: - Initialization override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { // 设置默认背景色 backgroundColor = .clear } // MARK: - Public Methods /// 添加单个子视图 func addArrangedSubview(_ view: UIView) { arrangedSubviews.append(view) addSubview(view) setNeedsLayout() } /// 添加多个子视图 func addArrangedSubviews(_ views: [UIView]) { arrangedSubviews.append(contentsOf: views) views.forEach { addSubview($0) } setNeedsLayout() } /// 清空所有子视图 func removeAllArrangedSubviews() { arrangedSubviews.forEach { $0.removeFromSuperview() } arrangedSubviews.removeAll() setNeedsLayout() } // MARK: - Layout override func layoutSubviews() { super.layoutSubviews() // 清空现有约束 arrangedSubviews.forEach { $0.snp.removeConstraints() } // 当前布局参数 let containerWidth = bounds.width var currentX: CGFloat = 0 var currentY: CGFloat = 0 var currentRowHeight: CGFloat = 0 var currentRowViews: [UIView] = [] // 当前行的所有views var previousView: UIView? // 遍历子视图进行布局 for view in arrangedSubviews { view.translatesAutoresizingMaskIntoConstraints = false let viewSize = view.intrinsicContentSize == .zero ? view.bounds.size : view.intrinsicContentSize // 检查是否需要换行 if currentX + viewSize.width > containerWidth && !currentRowViews.isEmpty { // 布局当前行(底部对齐) layoutRow(views: currentRowViews, rowHeight: currentRowHeight, startY: currentY, previousView: &previousView) // 重置参数开始新行 currentX = 0 currentY += currentRowHeight + lineSpacing currentRowHeight = 0 currentRowViews.removeAll() } // 更新当前行信息 currentRowViews.append(view) currentX += viewSize.width + itemSpacing currentRowHeight = max(currentRowHeight, viewSize.height) } // 布局最后一行 if currentRowViews.isEmpty == false { // has views in lastViews layoutRow(views: currentRowViews, rowHeight: currentRowHeight, startY: currentY, previousView: &previousView) if let last = currentRowViews.last { //dlog("💤currentY:\(currentY)") last.snp.makeConstraints { make in make.top.equalToSuperview().offset(currentY) } } invalidateIntrinsicContentSize() // 🔥重要 } //dlog("💤currentRowHeight \(currentRowHeight)") bounds = CGRect(x: 0, y: 0, width: intrinsicContentSize.width, height: intrinsicContentSize.height) //dlog("💤currentRowHeight:\(currentRowHeight) intrinsicContentSize:\(intrinsicContentSize) sizeHeight:\(bounds.size.height)") } /// 布局一行中的视图,底部对齐 private func layoutRow(views: [UIView], rowHeight: CGFloat, startY: CGFloat, previousView: inout UIView?) { for (index, view) in views.enumerated() { view.snp.makeConstraints { make in // 底部对齐:bottom = startY + rowHeight make.bottom.equalTo(snp.top).offset(startY + rowHeight) if index == 0 { // 每行第一个视图,靠容器左边 make.leading.equalToSuperview() } else if let prev = previousView { // 非第一个视图,紧接前一个视图 make.leading.equalTo(prev.snp.trailing).offset(itemSpacing) } // 高度基于 intrinsicContentSize if view.intrinsicContentSize.height != UIView.noIntrinsicMetric { make.height.equalTo(view.intrinsicContentSize.height) } } previousView = view } } /// 计算容器所需的高度 override var intrinsicContentSize: CGSize { // layoutIfNeeded() // // var totalHeight: CGFloat = 0 // var currentX: CGFloat = 0 // var currentRowHeight: CGFloat = 0 // // for view in arrangedSubviews { // let viewSize = view.intrinsicContentSize == .zero ? view.bounds.size : view.intrinsicContentSize // // if currentX + viewSize.width > bounds.width && currentX > 0 { // totalHeight += currentRowHeight + lineSpacing // currentX = 0 // currentRowHeight = 0 // } // // currentX += viewSize.width + itemSpacing // currentRowHeight = max(currentRowHeight, viewSize.height) // } // // // 加上最后一行的行高 // if currentRowHeight > 0 { // totalHeight += currentRowHeight // } // // dlog("💤Totalheight: \(totalHeight)") // return CGSize(width: bounds.width, height: totalHeight) layoutIfNeeded() if subviews.count > 0{ let view = subviews.last! // dlog("💤 last:\(view)") let height = max(view.frame.maxY, 30) return CGSize(width: bounds.width, height: height) } return CGSize(width: bounds.width, height: 30) } }