| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- //
- // 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..<lines.count {
- let lineView = buildLine()
- lineViews.append(lineView)
- addArrangedSubview(lineView)
- }
- }
- lineViews.forEach { line in
- line.arrangedSubviews.forEach {
- line.removeArrangedSubview($0)
- $0.removeFromSuperview()
- }
- }
- for (index, lineView) in lineViews.enumerated() {
- let views = lines[index]
- views.forEach {
- lineView.addArrangedSubview($0)
- }
- }
- }
-
- private func buildLine() -> 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
|