Created
November 26, 2022 07:35
-
-
Save niaeashes/3e004f6f03633aea529953c3851f57e8 to your computer and use it in GitHub Desktop.
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 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