Skip to content

Instantly share code, notes, and snippets.

@niaeashes
Created November 26, 2022 07:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niaeashes/3e004f6f03633aea529953c3851f57e8 to your computer and use it in GitHub Desktop.
Save niaeashes/3e004f6f03633aea529953c3851f57e8 to your computer and use it in GitHub Desktop.
import SwiftUI
// MARK: - Basic
public struct CellLayout<Icon: View, Content: View, Accessory: View>: View {
@State var cellHeight: CGFloat = 0
var horizontalGap: CGFloat
var verticalGap: CGFloat
let iconView: Icon
let contentView: Content
let accessoryView: Accessory
public init(horizontalGap: CGFloat = 8, verticalGap: CGFloat = 4, @CellLayoutBuilder builder: () -> Self) where Accessory == EmptyView {
self = builder()
self.horizontalGap = horizontalGap
self.verticalGap = verticalGap
}
init(iconView: Icon, contentView: Content) where Accessory == EmptyView {
self.iconView = iconView
self.contentView = contentView
self.accessoryView = .init()
self.horizontalGap = 8
self.verticalGap = 4
}
init(base: CellLayout<Icon, Content, EmptyView>, accessory: Accessory) {
self.iconView = base.iconView
self.contentView = base.contentView
self.accessoryView = accessory
self.horizontalGap = base.horizontalGap
self.verticalGap = base.verticalGap
}
public var body: some View {
HStack(alignment: .top, spacing: horizontalGap) {
iconView
VStack(alignment: .leading, spacing: verticalGap) {
contentView
}
.frame(maxWidth: .infinity, alignment: .leading)
accessoryView
.frame(height: cellHeight)
}
.background(GeometryReader { geometry in
Color.clear
.onChange(of: geometry.size.height) { height in cellHeight = height }
.onAppear { cellHeight = geometry.size.height }
})
}
}
@resultBuilder
public struct CellLayoutBuilder {
public static func buildBlock<V1>(_ v1: V1) -> (V1) where V1: View {
(v1)
}
public static func buildBlock<V1, V2>(_ v1: V1, _ v2: V2) -> (V1, V2) where V1: View, V2: View {
(v1, v2)
}
public static func buildBlock<V1, V2, V3>(_ v1: V1, _ v2: V2, _ v3: V3) -> (V1, V2, V3) where V1: View, V2: View, V3: View {
(v1, v2, v3)
}
public static func buildBlock<V1, V2, V3, V4>(_ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4) -> (V1, V2, V3, V4) where V1: View, V2: View, V3: View, V4: View {
(v1, v2, v3, v4)
}
public static func buildBlock<V1, V2, V3, V4, V5>(_ v1: V1, _ v2: V2, _ v3: V3, _ v4: V4, _ v5: V5) -> (V1, V2, V3, V4, V5) where V1: View, V2: View, V3: View, V4: View, V5: View {
(v1, v2, v3, v4, v5)
}
public static func buildIf<V>(_ view: (V)?) -> V? where V: View {
view
}
public static func buildEither<T, F>(first component: T) -> _ConditionalContent<T, F> where T: View, F: View {
ViewBuilder.buildEither(first: component)
}
public static func buildEither<T, F>(second component: F) -> _ConditionalContent<T, F> where T: View, F: View {
ViewBuilder.buildEither(second: component)
}
public static func buildFinalResult<V1>(_ c: (V1)) -> CellLayout<V1, EmptyView, EmptyView> where V1: View {
.init(
iconView: c,
contentView: .init()
)
}
public static func buildFinalResult<V1, V2>(_ c: (V1, V2)) -> CellLayout<V1, V2, EmptyView> where V1: View, V2: View {
.init(
iconView: c.0,
contentView: c.1
)
}
public static func buildFinalResult<V1, V2, V3>(_ c: (V1, V2, V3)) -> CellLayout<V1, TupleView<(V2, V3)>, EmptyView> where V1: View, V2: View, V3: View {
.init(
iconView: c.0,
contentView: TupleView((c.1, c.2))
)
}
public static func buildFinalResult<V1, V2, V3, V4>(_ c: (V1, V2, V3, V4)) -> CellLayout<V1, TupleView<(V2, V3, V4)>, EmptyView> where V1: View, V2: View, V3: View, V4: View {
.init(
iconView: c.0,
contentView: TupleView((c.1, c.2, c.3))
)
}
public static func buildFinalResult<V1, V2, V3, V4, V5>(_ c: (V1, V2, V3, V4, V5)) -> CellLayout<V1, TupleView<(V2, V3, V4, V5)>, EmptyView> where V1: View, V2: View, V3: View, V4: View, V5: View {
.init(
iconView: c.0,
contentView: TupleView((c.1, c.2, c.3, c.4))
)
}
}
// MARK: - Accessory
extension CellLayout {
public func accessory<V>(alignment: VerticalAlignment = .center, @ViewBuilder content: @escaping () -> V) -> CellLayout<Icon, Content, some View> where V: View, Accessory == EmptyView {
.init(base: self, accessory: CellAccessoryView(base: content(), alignment: alignment))
}
public func accessory<V>(alignment: VerticalAlignment = .center, _ view: V) -> CellLayout<Icon, Content, some View> where V: View, Accessory == EmptyView {
.init(base: self, accessory: CellAccessoryView(base: view, alignment: alignment))
}
}
private struct CellAccessoryView<BaseAccessory>: View where BaseAccessory: View {
let base: BaseAccessory
let alignment: VerticalAlignment
var body: some View {
base
.frame(idealWidth: 0, maxHeight: .infinity, alignment: .init(horizontal: .center, vertical: alignment))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment