Last active
November 14, 2022 14:25
-
-
Save nerdsupremacist/ab8d7909ec38eab5a06fb1ee0508ae38 to your computer and use it in GitHub Desktop.
Grid View with View Builder SwiftUI
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 | |
struct GridView: View { | |
let columns: Int | |
let columnAlignment: VerticalAlignment | |
let rowAlignment: HorizontalAlignment | |
let rowSpacing: CGFloat? | |
let columnSpacing: CGFloat? | |
let content: [AnyView] | |
var body: some View { | |
let rows = content.chunked(into: columns) | |
return VStack(alignment: self.rowAlignment, spacing: self.rowSpacing) { | |
ForEach(0..<rows.count) { row in | |
HStack(alignment: self.columnAlignment, spacing: self.columnSpacing) { | |
ForEach(0..<rows[row].count) { column in | |
rows[row][column] | |
} | |
} | |
} | |
} | |
} | |
} | |
extension GridView { | |
init<Content: View>(columns: Int, | |
columnAlignment: VerticalAlignment = .center, | |
rowAlignment: HorizontalAlignment = .center, | |
rowSpacing: CGFloat? = nil, | |
columnSpacing: CGFloat? = nil, | |
@ViewBuilder content: () -> Content) { | |
self.init(columns: columns, | |
columnAlignment: columnAlignment, | |
rowAlignment: rowAlignment, | |
rowSpacing: rowSpacing, | |
columnSpacing: columnSpacing, | |
content: components(in: content())) | |
} | |
} | |
extension Array { | |
func chunked(into size: Int) -> [[Element]] { | |
stride(from: 0, to: count, by: size).map { | |
Array(self[$0 ..< Swift.min($0 + size, count)]) | |
} | |
} | |
} |
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 Runtime | |
import SwiftUI | |
protocol AnyViewStorageBase { | |
func render() -> AnyView | |
} | |
struct AnyViewStorage<Content : View>: AnyViewStorageBase { | |
let view: Content | |
func render() -> AnyView { | |
return AnyView(view) | |
} | |
} | |
protocol Decomposable { | |
func decompose() -> [AnyView] | |
} | |
extension TupleView: Decomposable { | |
func decompose() -> [AnyView] { | |
let mirror = Mirror(reflecting: self) | |
let tuple = mirror.children.first!.value | |
let tupleMirror = Mirror(reflecting: tuple) | |
return tupleMirror.children.flatMap { anyViews(from: $0.value) } | |
} | |
} | |
extension ForEach: Decomposable where Content: View { | |
func decompose() -> [AnyView] { | |
// TODO: Figure out how the updates should happen... | |
return data.map { AnyView(content($0)) } | |
} | |
} | |
extension HStack: Decomposable { | |
func decompose() -> [AnyView] { | |
return [AnyView(self)] | |
} | |
} | |
extension ZStack: Decomposable { | |
func decompose() -> [AnyView] { | |
return [AnyView(self)] | |
} | |
} | |
extension VStack: Decomposable { | |
func decompose() -> [AnyView] { | |
return [AnyView(self)] | |
} | |
} | |
func components<Content: View>(in view: Content) -> [AnyView] { | |
if let decomposable = view as? Decomposable { | |
return decomposable.decompose() | |
} | |
return [AnyView(view)] | |
} | |
func anyViews(from view: Any) -> [AnyView] { | |
if let view = view as? Decomposable { | |
return view.decompose() | |
} | |
return [anyViewStorage(from: view).render()] | |
} | |
func anyViewStorage(from view: Any) -> AnyViewStorageBase { | |
let genericType = type(of: view) | |
let pointer = unsafeBitCast(genericType, to: UnsafeRawPointer.self) | |
var symbolInfo = Dl_info() | |
dladdr(pointer, &symbolInfo) | |
guard let mangledGenericTypeName = symbolInfo.dli_sname.map({ String(cString: $0) })?.dropFirst(2).dropLast() | |
else { fatalError() } | |
// Warning this is the module name. You would have to change it to yours to run | |
let mangledName = "8GridTest14AnyViewStorageVy\(mangledGenericTypeName)G" | |
let typePointer = mangledName.cString(using: .utf8)?.withUnsafeBytes { buffer -> UnsafeRawPointer? in | |
let casted = buffer.baseAddress!.assumingMemoryBound(to: Int8.self) | |
return swift_getTypeByMangledNameInContext(casted, Int32(mangledName.count), nil, nil) | |
} | |
guard let type = typePointer.map({ unsafeBitCast($0, to: Any.Type.self) }) else { | |
fatalError() | |
} | |
return try! createInstance(of: type) { _ in view } as! AnyViewStorageBase | |
} | |
@_silgen_name("swift_getTypeByMangledNameInContext") | |
func swift_getTypeByMangledNameInContext( | |
_ typeNameStart: UnsafePointer<Int8>, | |
_ typeNameLength: Int32, | |
_ context: UnsafeRawPointer?, | |
_ genericArgs: UnsafePointer<UnsafeRawPointer?>? | |
) -> UnsafeRawPointer? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment