| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- //
- // LNMultiLineStackView.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/14.
- //
- import Foundation
- import UIKit
- import SnapKit
- class LNMultiLineStackView: UIStackView {
- var columns: Int = 0
- var itemDistribution: UIStackView.Distribution = .fillEqually
- var itemSpacing: CGFloat = 0
-
- private var lineViews: [UIStackView] = []
- private(set) var curItemViews: [UIView] = []
- private var emptyView: [UIView] = []
-
- override init(frame: CGRect) {
- super.init(frame: frame)
-
- axis = .vertical
- }
-
- func update(_ itemViews: [UIView]) {
- arrangedSubviews.forEach {
- removeArrangedSubview($0)
- $0.removeFromSuperview()
- }
- lineViews.removeAll()
- curItemViews.removeAll()
- emptyView.removeAll()
-
- if itemViews.isEmpty { return }
-
- var line = buildLine()
- itemViews.forEach {
- if $0.isHidden {
- return
- }
- if line.arrangedSubviews.count == columns {
- line = buildLine()
- }
- line.addArrangedSubview($0)
- curItemViews.append($0)
- }
- fillLastLine()
- }
-
- func append(_ itemViews: [UIView]) {
- var last = lineViews.last ?? buildLine()
- emptyView.forEach {
- last.removeArrangedSubview($0)
- $0.removeFromSuperview()
- }
- itemViews.forEach {
- if $0.isHidden {
- return
- }
- if last.arrangedSubviews.count == columns {
- last = buildLine()
- }
- last.addArrangedSubview($0)
- curItemViews.append($0)
- }
- fillLastLine()
- }
-
- func remove(_ itemViews: [UIView]) {
- curItemViews.removeAll {
- itemViews.contains($0)
- }
- update(curItemViews)
- }
-
- required init(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- }
- extension LNMultiLineStackView {
- private func fillLastLine() {
- if lineViews.isEmpty { return }
- guard let last = lineViews.last else { return }
- guard last.arrangedSubviews.count < columns else { return }
-
- for _ in last.arrangedSubviews.count..<columns {
- let empty = UIView()
- last.addArrangedSubview(empty)
- if itemDistribution == .equalSpacing
- || itemDistribution == .equalCentering {
- empty.snp.makeConstraints { make in
- make.width.equalTo(last.arrangedSubviews[0])
- }
- }
-
- emptyView.append(empty)
- }
- }
-
- private func buildLine() -> UIStackView {
- let stackView = UIStackView()
- stackView.axis = .horizontal
- stackView.distribution = itemDistribution
- stackView.spacing = itemSpacing
-
- lineViews.append(stackView)
- addArrangedSubview(stackView)
-
- return stackView
- }
- }
- #if DEBUG
- import SwiftUI
- struct LNMultiLineStackViewPreview: UIViewRepresentable {
- func makeUIView(context: Context) -> some UIView {
- let container = UIView()
- container.backgroundColor = .lightGray
-
- let view = LNMultiLineStackView()
- 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: {
- LNMultiLineStackViewPreview()
- })
- #endif
|