有什么方法可以在 SwiftUI 中使用 @ViewBuilder create/extract 一个视图数组
Is there any way to create/extract an array of Views using @ViewBuilder in SwiftUI
我正在尝试创建一个简单的 struct
,它接受一个视图数组,returns 一个普通的 VStack
包含那些视图,除了它们都是对角堆叠的。
代码:
struct LeaningTower<Content: View>: View {
var views: [Content]
var body: some View {
VStack {
ForEach(0..<views.count) { index in
self.views[index]
.offset(x: CGFloat(index * 30))
}
}
}
}
现在这很好用,但每当我不得不调用它时我总是很生气:
LeaningTower(views: [Text("Something 1"), Text("Something 2"), Text("Something 3")])
在这样的数组中列出视图对我来说似乎非常奇怪,所以我想知道是否有一种方法可以使用 @ViewBuilder
来调用我的 LeaningTower
结构,如下所示:
LeaningTower { // Same way how you would create a regular VStack
Text("Something 1")
Text("Something 2")
Text("Something 3")
// And then have all of these Text's in my 'views' array
}
如果有办法使用 @ViewBuilder
到 create/extract 视图数组,请告诉我。
(即使无法使用 @ViewBuilder
字面上任何能让它看起来更整洁的东西都会有很大帮助)
literally anything that will make it look neater will help out a lot
嗯,这会让你更接近:
将此 init
添加到您的 LeaningTower
:
init(_ views: Content...) {
self.views = views
}
然后像这样使用它:
LeaningTower(
Text("Something 1"),
Text("Something 2"),
Text("Something 3")
)
您可以将偏移量添加为可选参数:
struct LeaningTower<Content: View>: View {
var views: [Content]
var offset: CGFloat
init(offset: CGFloat = 30, _ views: Content...) {
self.views = views
self.offset = offset
}
var body: some View {
VStack {
ForEach(0..<views.count) { index in
self.views[index]
.offset(x: CGFloat(index) * self.offset)
}
}
}
}
用法示例:
LeaningTower(offset: 20,
Text("Something 1"),
Text("Something 2"),
Text("Something 3")
)
我自己在尝试找出类似的东西时遇到了这个问题。因此,为了后代和其他人的利益,他们正在努力解决类似的问题。
我发现获得子偏移量的最佳方法是使用 Swift 的键入机制,如下所示。例如
struct LeaningTowerAnyView: View {
let inputViews: [AnyView]
init<V0: View, V1: View>(
@ViewBuilder content: @escaping () -> TupleView<(V0, V1)>
) {
let cv = content().value
inputViews = [AnyView(cv.0), AnyView(cv.1)]
}
init<V0: View, V1: View, V2: View>(
@ViewBuilder content: @escaping () -> TupleView<(V0, V1, V2)>) {
let cv = content().value
inputViews = [AnyView(cv.0), AnyView(cv.1), AnyView(cv.2)]
}
init<V0: View, V1: View, V2: View, V3: View>(
@ViewBuilder content: @escaping () -> TupleView<(V0, V1, V2, V3)>) {
let cv = content().value
inputViews = [AnyView(cv.0), AnyView(cv.1), AnyView(cv.2), AnyView(cv.3)]
}
var body: some View {
VStack {
ForEach(0 ..< inputViews.count) { index in
self.inputViews[index]
.offset(x: CGFloat(index * 30))
}
}
}
}
用法是
struct ContentView: View {
var body: some View {
LeaningTowerAnyView {
Text("Something 1")
Text("Something 2")
Text("Something 3")
}
}
}
它也适用于任何视图,而不仅仅是文本,例如
struct ContentView: View {
var body: some View {
LeaningTowerAnyView {
Capsule().frame(width: 50, height: 20)
Text("Something 2").border(Color.green)
Text("Something 3").blur(radius: 1.5)
}
}
}
缺点是每个额外的视图都需要一个自定义初始化程序,但听起来像是
我怀疑基于 Mirror 并说服 Swift 动态调用可变数量的不同命名方法的东西可能会起作用。但这变得很可怕,大约一周前我 运行 没时间了 ;-) 。如果有人有更优雅的东西,我会非常感兴趣。
很少需要从数组中提取视图。如果您只是想将 @ViewBuilder
内容传递到视图中,您可以简单地执行以下操作:
struct ContentView: View {
var body: some View {
VStackReplica {
Text("1st")
Text("2nd")
Text("3rd")
}
}
}
struct VStackReplica<Content: View>: View {
@ViewBuilder let content: () -> Content
var body: some View {
VStack(content: content)
}
}
如果这对您的用例来说还不够,请参阅下文。
我有一个通用版本,所以不需要为不同长度的元组创建多个初始化器。此外,视图可以是任何你想要的(你不限制每个 View
是相同的类型)。
您可以在 GeorgeElsham/ViewExtractor 找到我为此制作的 Swift 包。这比这个答案包含的内容更多,因为这个答案只是一个简化的基本版本。由于代码与此答案略有不同,因此请先阅读 README.md
作为示例。
返回答案,用法示例:
struct ContentView: View {
var body: some View {
LeaningTower {
Text("Something 1")
Text("Something 2")
Text("Something 3")
Image(systemName: "circle")
}
}
}
您的观点定义:
struct LeaningTower: View {
private let views: [AnyView]
init<Views>(@ViewBuilder content: @escaping () -> TupleView<Views>) {
views = content().getViews
}
var body: some View {
VStack {
ForEach(views.indices) { index in
views[index]
.offset(x: CGFloat(index * 30))
}
}
}
}
TupleView
扩展名(也就是所有魔法发生的地方):
extension TupleView {
var getViews: [AnyView] {
makeArray(from: value)
}
private struct GenericView {
let body: Any
var anyView: AnyView? {
AnyView(_fromValue: body)
}
}
private func makeArray<Tuple>(from tuple: Tuple) -> [AnyView] {
func convert(child: Mirror.Child) -> AnyView? {
withUnsafeBytes(of: child.value) { ptr -> AnyView? in
let binded = ptr.bindMemory(to: GenericView.self)
return binded.first?.anyView
}
}
let tupleMirror = Mirror(reflecting: tuple)
return tupleMirror.children.compactMap(convert)
}
}
结果:
我正在尝试创建一个简单的 struct
,它接受一个视图数组,returns 一个普通的 VStack
包含那些视图,除了它们都是对角堆叠的。
代码:
struct LeaningTower<Content: View>: View {
var views: [Content]
var body: some View {
VStack {
ForEach(0..<views.count) { index in
self.views[index]
.offset(x: CGFloat(index * 30))
}
}
}
}
现在这很好用,但每当我不得不调用它时我总是很生气:
LeaningTower(views: [Text("Something 1"), Text("Something 2"), Text("Something 3")])
在这样的数组中列出视图对我来说似乎非常奇怪,所以我想知道是否有一种方法可以使用 @ViewBuilder
来调用我的 LeaningTower
结构,如下所示:
LeaningTower { // Same way how you would create a regular VStack
Text("Something 1")
Text("Something 2")
Text("Something 3")
// And then have all of these Text's in my 'views' array
}
如果有办法使用 @ViewBuilder
到 create/extract 视图数组,请告诉我。
(即使无法使用 @ViewBuilder
字面上任何能让它看起来更整洁的东西都会有很大帮助)
literally anything that will make it look neater will help out a lot
嗯,这会让你更接近:
将此 init
添加到您的 LeaningTower
:
init(_ views: Content...) {
self.views = views
}
然后像这样使用它:
LeaningTower(
Text("Something 1"),
Text("Something 2"),
Text("Something 3")
)
您可以将偏移量添加为可选参数:
struct LeaningTower<Content: View>: View {
var views: [Content]
var offset: CGFloat
init(offset: CGFloat = 30, _ views: Content...) {
self.views = views
self.offset = offset
}
var body: some View {
VStack {
ForEach(0..<views.count) { index in
self.views[index]
.offset(x: CGFloat(index) * self.offset)
}
}
}
}
用法示例:
LeaningTower(offset: 20,
Text("Something 1"),
Text("Something 2"),
Text("Something 3")
)
我自己在尝试找出类似的东西时遇到了这个问题。因此,为了后代和其他人的利益,他们正在努力解决类似的问题。
我发现获得子偏移量的最佳方法是使用 Swift 的键入机制,如下所示。例如
struct LeaningTowerAnyView: View {
let inputViews: [AnyView]
init<V0: View, V1: View>(
@ViewBuilder content: @escaping () -> TupleView<(V0, V1)>
) {
let cv = content().value
inputViews = [AnyView(cv.0), AnyView(cv.1)]
}
init<V0: View, V1: View, V2: View>(
@ViewBuilder content: @escaping () -> TupleView<(V0, V1, V2)>) {
let cv = content().value
inputViews = [AnyView(cv.0), AnyView(cv.1), AnyView(cv.2)]
}
init<V0: View, V1: View, V2: View, V3: View>(
@ViewBuilder content: @escaping () -> TupleView<(V0, V1, V2, V3)>) {
let cv = content().value
inputViews = [AnyView(cv.0), AnyView(cv.1), AnyView(cv.2), AnyView(cv.3)]
}
var body: some View {
VStack {
ForEach(0 ..< inputViews.count) { index in
self.inputViews[index]
.offset(x: CGFloat(index * 30))
}
}
}
}
用法是
struct ContentView: View {
var body: some View {
LeaningTowerAnyView {
Text("Something 1")
Text("Something 2")
Text("Something 3")
}
}
}
它也适用于任何视图,而不仅仅是文本,例如
struct ContentView: View {
var body: some View {
LeaningTowerAnyView {
Capsule().frame(width: 50, height: 20)
Text("Something 2").border(Color.green)
Text("Something 3").blur(radius: 1.5)
}
}
}
缺点是每个额外的视图都需要一个自定义初始化程序,但听起来像是
我怀疑基于 Mirror 并说服 Swift 动态调用可变数量的不同命名方法的东西可能会起作用。但这变得很可怕,大约一周前我 运行 没时间了 ;-) 。如果有人有更优雅的东西,我会非常感兴趣。
很少需要从数组中提取视图。如果您只是想将 @ViewBuilder
内容传递到视图中,您可以简单地执行以下操作:
struct ContentView: View {
var body: some View {
VStackReplica {
Text("1st")
Text("2nd")
Text("3rd")
}
}
}
struct VStackReplica<Content: View>: View {
@ViewBuilder let content: () -> Content
var body: some View {
VStack(content: content)
}
}
如果这对您的用例来说还不够,请参阅下文。
我有一个通用版本,所以不需要为不同长度的元组创建多个初始化器。此外,视图可以是任何你想要的(你不限制每个 View
是相同的类型)。
您可以在 GeorgeElsham/ViewExtractor 找到我为此制作的 Swift 包。这比这个答案包含的内容更多,因为这个答案只是一个简化的基本版本。由于代码与此答案略有不同,因此请先阅读 README.md
作为示例。
返回答案,用法示例:
struct ContentView: View {
var body: some View {
LeaningTower {
Text("Something 1")
Text("Something 2")
Text("Something 3")
Image(systemName: "circle")
}
}
}
您的观点定义:
struct LeaningTower: View {
private let views: [AnyView]
init<Views>(@ViewBuilder content: @escaping () -> TupleView<Views>) {
views = content().getViews
}
var body: some View {
VStack {
ForEach(views.indices) { index in
views[index]
.offset(x: CGFloat(index * 30))
}
}
}
}
TupleView
扩展名(也就是所有魔法发生的地方):
extension TupleView {
var getViews: [AnyView] {
makeArray(from: value)
}
private struct GenericView {
let body: Any
var anyView: AnyView? {
AnyView(_fromValue: body)
}
}
private func makeArray<Tuple>(from tuple: Tuple) -> [AnyView] {
func convert(child: Mirror.Child) -> AnyView? {
withUnsafeBytes(of: child.value) { ptr -> AnyView? in
let binded = ptr.bindMemory(to: GenericView.self)
return binded.first?.anyView
}
}
let tupleMirror = Mirror(reflecting: tuple)
return tupleMirror.children.compactMap(convert)
}
}
结果: