Visual_Novel_iOS/crush/Crush/Src/Components/UI/BaseView/FlowLayoutContainer.swift

186 lines
6.0 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// 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)
}
}