Skip to content

Instantly share code, notes, and snippets.

@nerdsupremacist
Last active November 14, 2022 14:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nerdsupremacist/ab8d7909ec38eab5a06fb1ee0508ae38 to your computer and use it in GitHub Desktop.
Save nerdsupremacist/ab8d7909ec38eab5a06fb1ee0508ae38 to your computer and use it in GitHub Desktop.
Grid View with View Builder SwiftUI
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)])
}
}
}
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