// // LNAutoFillStackView.swift // Lanu // // Created by OneeChan on 2025/12/12. // import Foundation import UIKit import SnapKit class LNAutoFillStackView: UIStackView { var itemSpacing: CGFloat = 0 { didSet { if oldValue != itemSpacing { sortItemViews() } } } private var lineViews: [UIStackView] = [] private(set) var curItemViews: [UIView] = [] override init(frame: CGRect) { super.init(frame: frame) axis = .vertical alignment = .leading } func update(_ itemViews: [UIView]) { arrangedSubviews.forEach { removeArrangedSubview($0) $0.removeFromSuperview() } lineViews.removeAll() curItemViews.removeAll() itemViews.forEach { $0.layoutIfNeeded() curItemViews.append($0) } sortItemViews() } func append(_ itemViews: [UIView]) { itemViews.forEach { $0.layoutIfNeeded() curItemViews.append($0) } sortItemViews() } func remove(_ itemViews: [UIView]) { curItemViews.removeAll { itemViews.contains($0) } sortItemViews() } override func layoutSubviews() { super.layoutSubviews() sortItemViews() } required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNAutoFillStackView { private func sortItemViews() { let maxWidth = bounds.width var lines: [[UIView]] = [] var curLine: [UIView] = [] var totalWidth = 0.0 curItemViews.forEach { if curLine.isEmpty { curLine.append($0) totalWidth = $0.frame.width } else { if totalWidth + $0.bounds.width + itemSpacing > maxWidth { lines.append(curLine) curLine = [] totalWidth = 0.0 } if curLine.isEmpty { totalWidth = $0.bounds.width } else { totalWidth += $0.bounds.width + itemSpacing } curLine.append($0) } } lines.append(curLine) if lineViews.count != lines.count { lineViews.forEach { removeArrangedSubview($0) $0.removeFromSuperview() } lineViews.removeAll() for _ in 0.. UIStackView { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = itemSpacing return stackView } } #if DEBUG import SwiftUI struct LNMultiAutoLineStackViewPreview: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { let container = UIView() container.backgroundColor = .lightGray let view = LNAutoFillStackView() view.backgroundColor = .orange container.addSubview(view) view.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.centerY.equalToSuperview() } return container } func updateUIView(_ uiView: UIViewType, context: Context) { } } #Preview(body: { LNMultiAutoLineStackViewPreview() }) #endif