我从 Swift(来自前端)开始测试项目,并对视图和视图构建器有疑问
供参考:在前端框架 Vue.js 中,有一个 slot 和 slot props 的概念 https://vuejs.org/guide/components/slots.html#scoped-slots
Svelte https://svelte.dev/docs/special-elements#slot 也是如此,并且在 React 中也可以实现相同的功能
例如,我有一些通用列表,它具有一些基本布局并在内部进行一些逻辑(过滤、排序等),我想用它来传递列表并显示列表项,但是我想要,如果我不提供构建器,该列表中将指定一些默认视图
在 SwiftUI/UIKit 中有什么方法可以实现这样的功能吗?
我尝试做的类似于下一个代码,我简化了它以显示总体思路
import SwiftUI
struct ListTestView\<T: Hashable, Content: View\>: View {
private let items: \[T\]
private let listItemBuilder: (ListItemBuilderParams) -\> Content
init(
_ list: [T],
@ViewBuilder listItem: @escaping (_ params: ListItemBuilderParams) -> Content
) {
self.items = list
self.listItemBuilder = listItem
}
var body: some View {
VStack {
ForEach(filteredItems, id: \.self) { item in
listItemBuilder(.init(item1: item, item2: item, item3: item)) // ?? Text(String(item))
}
}
}
private var filteredItems: [T] {
// ...some filtration
return items
}
struct ListItemBuilderParams {
let item1: T
let item2: T
let item3: T
// ... and possible more
}
}
struct ViewWithList: View {
var body: some View {
ListTestView(\[1, 2, 3, 4\]) { params in
Text(String(params.item1))
Button(String(params.item2)) {}
Text(String(params.item3))
}
}
}
但它不会编译,因为在
ViewWithList
中我收到错误 Generic parameter 'Content' could not be inferred
但是如果我将所有参数作为普通参数列表传递,没有结构,它会编译,但它可能会变得非常混乱,因为没有办法省略一些特定的参数,我必须在闭包中将它们全部列出
import SwiftUI
struct ListTestView<T: Hashable, Content: View>: View {
private let items: [T]
private let listItemBuilder: (T, T, T) -> Content
init(
_ list: [T],
@ViewBuilder listItem: @escaping (T, T, T) -> Content
) {
self.items = list
self.listItemBuilder = listItem
}
var body: some View {
VStack {
ForEach(filteredItems, id: \.self) { item in
listItemBuilder(item, item, item) // ?? Text(String(item))
}
}
}
private var filteredItems: [T] {
// ...some filtration
return items
}
}
struct ViewWithList: View {
var body: some View {
ListTestView([1, 2, 3, 4]) { item1, item2, item3 in
Text(String(item1))
Button(String(item2)) {}
Text(String(item3))
}
}
}
我建议遵循“单一责任”原则,这会让事情变得更简单/更容易实现:
// Notice that T was changed to Identifiable as recommended for ForEach
struct ListTestView<T: Identifiable, Item: View>: View {
// Items provided and linked to a parent
@Binding var items: [T]
// Functions can be a constant
@ViewBuilder let itemView: (T) -> Item
let filter: () -> Void
let sort: () -> Void
var body: some View {
VStack {
HStack {
Button("Filter", action: filter)
Button("Sort", action: sort)
}
LazyVStack {
ForEach(items) { item in
itemView(item)
}
}
}
}
}
struct MyItem: Identifiable {
let id = UUID() // example, could be set to something more consistent in reality
let field1: String
let field2: String
let field3: String
}
struct MyItemView: View {
let myItem: MyItem
let buttonAction: (MyItem) -> ()
var body: some View {
HStack {
Text(myItem.field1)
Button(myItem.field2, action: callAction)
Text(myItem.field3)
}
}
// Calls buttonAction with the MyItem
func callAction() {
buttonAction(myItem)
}
}
struct ContentView: View {
// Those can also move to ViewModel
@State var items: [MyItem]
@State var clickedText: String = ""
var body: some View {
VStack {
Text("Clicked \(clickedText)")
ListTestView(
items: $items,
itemView: itemViewBuilder,
filter: filter,
sort: sort
)
}
}
func itemViewBuilder(_ item: MyItem) -> some View {
MyItemView(
myItem: item,
buttonAction: performAction
)
}
func performAction(_ item: MyItem) {
clickedText = "\(item.field1), \(item.field3)"
}
func filter() {
// For example
items = [items[0]]
}
func sort() {
// For example
items = items.reversed()
}
}
此外,您还可以实现过滤、排序等可重用过程,但要在单独的类中实现,甚至是
Array
的扩展