2020-09-22 15:37:13 -07:00
|
|
|
//
|
|
|
|
|
// StackView.swift
|
|
|
|
|
// App
|
|
|
|
|
//
|
|
|
|
|
// Created by James Magahern on 9/22/20.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
class StackView<T: UIView>: UIView
|
2020-09-22 15:37:13 -07:00
|
|
|
{
|
2021-02-11 12:26:13 -08:00
|
|
|
public enum LayoutType {
|
|
|
|
|
case intrinsicSize
|
|
|
|
|
case equalSize
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var arrangedSubviews: [T] = []
|
2020-09-22 15:37:13 -07:00
|
|
|
{ didSet { setNeedsLayout() } }
|
|
|
|
|
|
|
|
|
|
var layoutDimension: UIAxis = .vertical
|
2021-02-11 12:26:13 -08:00
|
|
|
var layoutType: LayoutType = .intrinsicSize
|
2020-09-22 15:37:13 -07:00
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
convenience init(dimension: UIAxis = .vertical, layoutType: LayoutType = .intrinsicSize) {
|
2020-09-22 15:37:13 -07:00
|
|
|
self.init(frame: .zero)
|
|
|
|
|
self.layoutDimension = dimension
|
2021-02-11 12:26:13 -08:00
|
|
|
self.layoutType = layoutType
|
2020-09-22 15:37:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convenience
|
2021-02-11 12:26:13 -08:00
|
|
|
public func addArrangedSubview(_ view: T) {
|
2020-09-22 15:37:13 -07:00
|
|
|
addSubview(view)
|
|
|
|
|
arrangedSubviews.append(view)
|
|
|
|
|
setNeedsLayout()
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
public func removeArrangedSubview(_ view: T) {
|
2020-09-22 15:37:13 -07:00
|
|
|
if view.superview == self {
|
|
|
|
|
view.removeFromSuperview()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arrangedSubviews.removeAll { $0 == view }
|
|
|
|
|
setNeedsLayout()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func removeAllArrangedSubviews() {
|
|
|
|
|
arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
|
arrangedSubviews.removeAll()
|
|
|
|
|
setNeedsLayout()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func sizeThatFits(_ containerSize: CGSize) -> CGSize {
|
|
|
|
|
var size: CGSize = .zero
|
|
|
|
|
|
|
|
|
|
if layoutDimension == .horizontal {
|
|
|
|
|
size.height = containerSize.height
|
|
|
|
|
} else {
|
|
|
|
|
size.width = containerSize.width
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var spaceRemaining = containerSize
|
|
|
|
|
for view in arrangedSubviews {
|
|
|
|
|
let viewSize = view.sizeThatFits(spaceRemaining)
|
|
|
|
|
if layoutDimension == .horizontal {
|
|
|
|
|
size.width += viewSize.width
|
|
|
|
|
spaceRemaining.width -= viewSize.width
|
|
|
|
|
} else {
|
|
|
|
|
size.height += viewSize.height
|
|
|
|
|
spaceRemaining.height -= viewSize.height
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func layoutSubviews() {
|
|
|
|
|
super.layoutSubviews()
|
|
|
|
|
|
2021-02-11 12:26:13 -08:00
|
|
|
if layoutType == .intrinsicSize {
|
|
|
|
|
layoutWithIntrinsicSize()
|
|
|
|
|
} else if layoutType == .equalSize {
|
|
|
|
|
layoutWithEqualSize()
|
|
|
|
|
} else {
|
|
|
|
|
fatalError("StackView: I don't know how to lay out this type")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fileprivate func layoutWithIntrinsicSize() {
|
2020-09-22 15:37:13 -07:00
|
|
|
var origin: CGPoint = CGPoint(x: safeAreaInsets.left, y: safeAreaInsets.top)
|
|
|
|
|
var spaceRemaining = bounds.size
|
|
|
|
|
for view in arrangedSubviews {
|
|
|
|
|
var viewSize = view.sizeThatFits(spaceRemaining)
|
|
|
|
|
|
|
|
|
|
var offset: CGPoint = .zero
|
|
|
|
|
if layoutDimension == .horizontal {
|
|
|
|
|
offset.x = viewSize.width
|
|
|
|
|
viewSize.height = bounds.height
|
|
|
|
|
spaceRemaining.width -= viewSize.width
|
|
|
|
|
} else {
|
|
|
|
|
offset.y = viewSize.height
|
|
|
|
|
viewSize.width = bounds.width
|
|
|
|
|
spaceRemaining.height -= viewSize.height
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view.frame = CGRect(origin: origin, size: viewSize)
|
|
|
|
|
origin.x += offset.x
|
|
|
|
|
origin.y += offset.y
|
|
|
|
|
}
|
2021-02-11 12:26:13 -08:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fileprivate func layoutWithEqualSize() {
|
|
|
|
|
let numberOfItems = arrangedSubviews.count
|
|
|
|
|
|
|
|
|
|
let structureDimension: CGFloat = (
|
|
|
|
|
(layoutDimension == .horizontal) ? bounds.height : bounds.width
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
let itemDimension: CGFloat = (
|
|
|
|
|
(layoutDimension == .horizontal) ? (bounds.width / CGFloat(numberOfItems)) : (bounds.height / CGFloat(numberOfItems))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var origin: CGPoint = CGPoint(x: safeAreaInsets.left, y: safeAreaInsets.top)
|
|
|
|
|
for view in arrangedSubviews {
|
|
|
|
|
let size: CGSize = (
|
|
|
|
|
(layoutDimension == .horizontal)
|
|
|
|
|
? CGSize(width: itemDimension, height: structureDimension)
|
|
|
|
|
: CGSize(width: structureDimension, height: itemDimension)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
view.frame = CGRect(origin: origin, size: size)
|
|
|
|
|
|
|
|
|
|
origin = (
|
|
|
|
|
(layoutDimension == .horizontal)
|
|
|
|
|
? CGPoint(x: origin.x + size.width, y: origin.y)
|
|
|
|
|
: CGPoint(x: origin.x, y: origin.y + size.height)
|
|
|
|
|
)
|
|
|
|
|
}
|
2020-09-22 15:37:13 -07:00
|
|
|
}
|
|
|
|
|
}
|