Last active
April 5, 2023 17:48
-
-
Save DanielCardonaRojas/fa2a00e8c50cc01021386f78ada189f9 to your computer and use it in GitHub Desktop.
An arranged view container that allow creating all sorts of layouts even UIStackView clones.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
open class ArrangedViewContainer: UIView | |
{ | |
public private(set) var hiddenViews = Set<UIView>() | |
private var observations: Set<NSKeyValueObservation> = Set() | |
open private(set) var arrangedSubviews = [UIView]() | |
private var invalidated = false | |
private var viewConstraints: [NSLayoutConstraint] = [] | |
open func addArrangedSubview(_ view: UIView) | |
{ | |
insertArrangedSubview(view, atIndex: arrangedSubviews.count) | |
} | |
open func addArrangedSubviews(_ views: UIView...) | |
{ | |
views.forEach { addArrangedSubview($0) } | |
} | |
open func removeArrangedSubview(_ view: UIView) | |
{ | |
guard let index = arrangedSubviews.firstIndex(of: view) | |
else | |
{ | |
return | |
} | |
view.removeObserver(self, forKeyPath: "isHidden") | |
arrangedSubviews.remove(at: index) | |
hiddenViews.remove(view) | |
invalidateLayout() | |
} | |
open func removeLastArrangedSubviews(_ count: Int) | |
{ | |
for _ in 0 ..< count | |
{ | |
guard let item = arrangedSubviews.popLast() | |
else | |
{ | |
break | |
} | |
removeArrangedSubview(item) | |
} | |
} | |
open func insertArrangedSubview(_ view: UIView, atIndex stackIndex: Int) | |
{ | |
if let idx = arrangedSubviews.firstIndex(of: view) | |
{ | |
arrangedSubviews.remove(at: idx) | |
} | |
view.translatesAutoresizingMaskIntoConstraints = false | |
arrangedSubviews.insert(view, at: stackIndex) | |
if view.superview != self | |
{ | |
addSubview(view) | |
} | |
if view.isHidden | |
{ | |
setArrangedView(view, hidden: true) | |
} | |
invalidateLayout() | |
addVisibilityObservation(for: view) | |
} | |
private func setVisibilityObservations() | |
{ | |
arrangedSubviews.forEach { addVisibilityObservation(for: $0) } | |
} | |
private func addVisibilityObservation(for view: UIView) | |
{ | |
let observation = view.observe(\.isHidden, changeHandler: { _, _ in | |
self.setArrangedView(view, hidden: view.isHidden) | |
}) | |
observations.insert(observation) | |
} | |
open func setArrangedView(_ view: UIView, hidden: Bool) | |
{ | |
if hidden | |
{ | |
hiddenViews.insert(view) | |
} | |
else | |
{ | |
hiddenViews.remove(view) | |
} | |
invalidateLayout() | |
} | |
open func invalidateLayout() | |
{ | |
if !invalidated | |
{ | |
invalidated = true | |
setNeedsUpdateConstraints() | |
} | |
} | |
func isHidden(_ item: UIView) -> Bool | |
{ | |
return hiddenViews.contains(item) | |
} | |
open func visibleItemLayoutConstraints(_: [UIView]) -> [NSLayoutConstraint] | |
{ | |
return [] | |
} | |
open func hiddenItemLayoutConstraints(_: [UIView]) -> [NSLayoutConstraint] | |
{ | |
return [] | |
} | |
override open func updateConstraints() | |
{ | |
NSLayoutConstraint.deactivate(viewConstraints.filter { $0.isActive }) | |
viewConstraints.removeAll() | |
let visibleItems = arrangedSubviews.filter { !isHidden($0) } | |
let visibleItemConstraints = visibleItemLayoutConstraints(visibleItems) | |
let hiddenItemConstraints = hiddenItemLayoutConstraints(Array(hiddenViews)) | |
viewConstraints.append(contentsOf: visibleItemConstraints) | |
viewConstraints.append(contentsOf: hiddenItemConstraints) | |
NSLayoutConstraint.activate(viewConstraints) | |
super.updateConstraints() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open class VerticalListView: ArrangedViewContainer | |
{ | |
open var spacing: CGFloat | |
{ | |
return 8 | |
} | |
override open func visibleItemLayoutConstraints(_ items: [UIView]) -> [NSLayoutConstraint] | |
{ | |
var previous: UIView? | |
var constraints = [NSLayoutConstraint]() | |
for index in 0 ..< items.count | |
{ | |
let item = items[index] | |
addSubview(item) | |
let itemConstraints = itemConstraints(item, previous: previous, isLast: index == items.count - 1) | |
constraints.append(contentsOf: itemConstraints) | |
previous = item | |
} | |
return constraints | |
} | |
override open func hiddenItemLayoutConstraints(_ items: [UIView]) -> [NSLayoutConstraint] | |
{ | |
return items.map | |
{ item in | |
item.heightAnchor.constraint(equalToConstant: .zero) | |
} | |
} | |
private func itemConstraints(_ groupView: UIView, previous previousGroup: UIView?, isLast: Bool) -> [NSLayoutConstraint] | |
{ | |
var itemConstraints: [NSLayoutConstraint] = [] | |
if let previousGroup = previousGroup | |
{ | |
itemConstraints.append(groupView.topAnchor.constraint(equalTo: previousGroup.bottomAnchor, | |
constant: spacing)) | |
} | |
else | |
{ | |
itemConstraints.append(groupView.topAnchor.constraint(equalTo: topAnchor)) | |
} | |
itemConstraints.append(contentsOf: [groupView.leftAnchor.constraint(equalTo: leftAnchor), | |
groupView.rightAnchor.constraint(equalTo: rightAnchor)]) | |
if isLast | |
{ | |
itemConstraints.append(groupView.bottomAnchor.constraint(equalTo: bottomAnchor)) | |
} | |
return itemConstraints | |
} | |
override open func invalidateLayout() | |
{ | |
setNeedsUpdateConstraints() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment