// // StackView.swift // App // // Created by James Magahern on 9/22/20. // import UIKit class StackView: UIView { public enum LayoutType { case intrinsicSize case equalSize } var arrangedSubviews: [T] = [] { didSet { setNeedsLayout() } } var layoutDimension: UIAxis = .vertical var layoutType: LayoutType = .intrinsicSize convenience init(dimension: UIAxis = .vertical, layoutType: LayoutType = .intrinsicSize) { self.init(frame: .zero) self.layoutDimension = dimension self.layoutType = layoutType } // Convenience public func addArrangedSubview(_ view: T) { addSubview(view) arrangedSubviews.append(view) setNeedsLayout() } public func removeArrangedSubview(_ view: T) { 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() 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() { 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 } } 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) ) } } }