Skip to content

Instantly share code, notes, and snippets.

@kaqu
Created May 11, 2019 22:06
Show Gist options
  • Save kaqu/0045e632be7b27072c42617ecbbe18eb to your computer and use it in GitHub Desktop.
Save kaqu/0045e632be7b27072c42617ecbbe18eb to your computer and use it in GitHub Desktop.
FluidLayout
public protocol FluidView: UIView {
var id: UUID { get }
}
public protocol FluidViewLayoutModel {
var id: UUID { get }
var prefferedSize: CGSize? { get }
var prefferedInsets: UIEdgeInsets? { get }
var minimumSize: CGSize? { get }
var maximumSize: CGSize? { get }
func prepareView() -> FluidView
func updateView(_ view: FluidView) -> Void
}
public struct FluidViewLayoutState {
public let viewID: UUID
public var frame: CGRect
public init(viewID: UUID, frame: CGRect = .zero) {
self.viewID = viewID
self.frame = frame
}
}
public protocol FluidLayoutContext {
var containerFrame: CGRect { get }
var containerInsets: UIEdgeInsets { get }
var previousFrame: CGRect { get set }
init(containerFrame: CGRect, containerInsets: UIEdgeInsets, model: [FluidViewLayoutModel])
}
public struct FluidLayouting<Context: FluidLayoutContext> {
public var layout: (inout Context, FluidViewLayoutModel) -> FluidViewLayoutState
public init(layout: @escaping (inout Context, FluidViewLayoutModel) -> FluidViewLayoutState) {
self.layout = layout
}
}
public final class FluidLayoutContainer<LayoutContext: FluidLayoutContext>: UIView, FluidView {
public let id: UUID
private let layout: FluidLayouting<LayoutContext>
private var managedViews: [UUID:FluidView] = [:]
public var managedViewModels: [FluidViewLayoutModel] = [] {
willSet {
let newKeys: [UUID] = newValue.map { $0.id }
managedViews.keys
.filter { !newKeys.contains($0) }
.forEach {
managedViews[$0]?.removeFromSuperview()
managedViews[$0] = nil
}
newValue
.filter { managedViews.keys.contains($0.id) }
.forEach {
guard let view = managedViews[$0.id] else { return }
$0.updateView(view)
}
newValue
.filter { !managedViews.keys.contains($0.id) }
.forEach {
let newView = $0.prepareView()
managedViews[$0.id] = newView
addSubview(newView)
}
}
didSet { setNeedsLayout() }
}
public init(id: UUID = .init(), layout: FluidLayouting<LayoutContext>) {
self.id = id
self.layout = layout
super.init(frame: .zero)
}
@available(*, unavailable)
required public init?(coder aDecoder: NSCoder) { fatalError() }
public override func layoutSubviews() {
var ctx: LayoutContext = .init(containerFrame: bounds, containerInsets: .zero, model: managedViewModels)
managedViewModels
.map { layout.layout(&ctx, $0) }
.forEach {
guard let view = managedViews[$0.viewID] else { return }
view.frame = $0.frame
view.layoutSubviews()
}
}
}
/// SAMPLE
public struct BoxLayoutContext: FluidLayoutContext {
public var containerFrame: CGRect
public var containerInsets: UIEdgeInsets
public var previousFrame: CGRect = .zero
public var counter: UInt = 0
public init(containerFrame: CGRect, containerInsets: UIEdgeInsets, model: [FluidViewLayoutModel]) {
self.containerFrame = containerFrame
self.containerInsets = containerInsets
}
}
extension FluidLayouting where Context == BoxLayoutContext {
public static var columnLayout: FluidLayouting {
return .init(layout: { (ctx, model) -> FluidViewLayoutState in
let size: CGSize = model.prefferedSize ?? .zero
let frame: CGRect = .init(x: ctx.containerInsets.left,
y: ctx.counter == 0 ? ctx.containerInsets.top : ctx.previousFrame.origin.y + ctx.previousFrame.height,
width: ctx.containerFrame.size.width - ctx.containerInsets.left - ctx.containerInsets.right,
height: size.height)
ctx.previousFrame = frame
ctx.counter += 1
return FluidViewLayoutState(viewID: model.id, frame: frame)
})
}
}
extension FluidLayouting where Context == BoxLayoutContext {
public static var rowLayout: FluidLayouting {
return .init(layout: { (ctx, model) -> FluidViewLayoutState in
let size: CGSize = model.prefferedSize ?? .zero
let frame: CGRect = .init(x: ctx.counter == 0 ? ctx.containerInsets.left : ctx.previousFrame.origin.x + ctx.previousFrame.width,
y: ctx.containerInsets.top,
width: size.width,
height: ctx.containerFrame.size.height - ctx.containerInsets.top - ctx.containerInsets.bottom)
ctx.previousFrame = frame
ctx.counter += 1
return FluidViewLayoutState(viewID: model.id, frame: frame)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment