我有一个内部管理@State变量的视图,该变量跟踪当前索引。像这样:
struct ReusableView: View {
@State var index: Int = 0
var body: some View {
Text("The index is \(self.index)"
// A button that changes the index
}
}
此视图将在整个应用中重复使用。有时父视图需要访问索引,因此我将其重构为:
struct ParentView: View {
@State var index: Int = 0
var body: some View {
ReusableView($index)
}
}
struct ReusableView: View {
@Binding var index: Int
var body: some View {
Text("The index is \(self.index)"
// A button that changes the index
}
}
我不想强制父视图始终保持索引状态。换句话说,我希望有选择地允许父视图负责状态变量,但是默认情况下使用可重用视图来维护状态,以防父视图不关心索引。
我尝试通过某种方式在可重用视图上初始化绑定,以防父视图不提供绑定:
struct ReusableView: View {
@Binding var index: Int
init(_ index: Binding<Int>? = nil) {
if index != nil {
self._index = index
} else {
// TODO: Parent didn't provide a binding, init myself.
// ?
}
}
var body: some View {
Text("The index is \(self.index)"
// A button that changes the index
}
}
谢谢!
答案 0 :(得分:1)
您要实现的主要问题是,当索引由父级处理时,您的View需要对其进行@Binding,但是当其处理索引本身时,则需要@State。有两种可能的解决方案。
如果视图在没有索引属性时可以忽略它:
struct ReusableView: View {
@Binding var index: Int?
init(_ index: Binding<Int?>) {
self._index = index
}
init() {
self._index = .constant(nil)
}
var body: some View {
VStack {
index.map { Text("The index is \($0)") }
}
}
}
优点是它非常简单-只有两个初始化程序,但是当索引由ResusableView本身处理时(它是一个常量),您不能更改索引的值。
如果视图在没有索引属性时不能忽略它:
struct ReusableView: View {
private var content: AnyView
init(_ index: Binding<Int>? = nil) {
if let index = index {
self.content = AnyView(DependentView(index: index))
} else {
self.content = AnyView(IndependentView())
}
}
var body: some View {
content
}
private struct DependentView: View {
@Binding var index: Int
var body: some View {
Text("The index is \(index)")
}
}
private struct IndependentView: View {
@State private var index: Int = 0
var body: some View {
Text("The index is \(index)")
}
}
}
显而易见的优点是,您可以将视图绑定到值或将其作为自己的状态进行管理。如您所见,ReusableView只是两个不同视图的包装,一个视图管理自己的@State,另一个视图绑定到其父视图的@State。
答案 1 :(得分:1)
(我认为)最好的解决方案是使用包装器视图的自定义绑定。
这将给您两个选择:
实际上是很棘手的事情。
这是您的示例,已更改为可用。 请记住,实际的更改是包装器视图,您必须从父级(而不是子级)调用包装器视图。
父视图(与子视图相同,除了子视图)
struct ContentView: View {
@State var index: Int? = nil
var body: some View {
VStack {
Text("Parent view state: \(index ?? 0)")
WrapperOfReusableView(index: self.$index)
// if you add .constant(nil) it will use the internal storage
}
}
}
包装视图
struct WrapperOfReusableView: View {
// The external index
@Binding var index: Int?
// The internal index
@State private var localIndex = 0
var body: some View {
// Custom binding
let localIndexBinding = Binding(
get:{
// if the parent sends nil, use the internal property
self.index ?? self.localIndex
},
set: {
// Set both internal and external
self.localIndex = $0
self.index = $0
}
)
// return the actual View
return ReusableView(index: localIndexBinding)
}
}
实际视图(与您的视图相同)
struct ReusableView: View {
@Binding var index: Int
var body: some View {
VStack {
Text("The Reusable index is \(index)")
Button("Change Index") {
self.index = self.index + 1
}
}
}
}
答案 2 :(得分:0)
如果使用目标注入进行绑定不是目标,则内部仅绑定一件事-内部@State
。因此,这里是使用内部绑定到内部状态的另一种方法,您可以尝试。
struct ReusableView: View {
@Binding private var externalIndex: Int
@State private var internalIndex: Int = 0
private var hasExternal = false
init(_ index: Binding<Int>? = nil) {
if index != nil {
self._externalIndex = index!
self.hasExternal = true
} else {
self._externalIndex = Binding<Int>(get: {0}, set: {_ in}) // initialization stub
}
}
private var index: Binding<Int> {
hasExternal ? $externalIndex : $internalIndex
}
var body: some View {
VStack {
Text("The index is \(self.index.wrappedValue)")
// A button that changes the index
}
}
}
答案 3 :(得分:0)
当您建议更改init
时,我认为您的方向是正确的。
在UI世界中,当您尝试init
时,人们会喜欢添加视图。因此,您需要做的一件事就是在Parent和原始可重用视图之间添加一个中间视图。您甚至不需要更改视图代码
struct ParentView: View {
@State var index: Int?
var body: some View {
ReusableView(index: $index)
}
}
struct ReusableView_Original: View {
@Binding var index: Int
var body: some View {
VStack{
Text("The index is \(self.index)")
Button.init("click", action:{
self.index += 1
})}
}
}
//下面是中间视图。
struct ReusableView: View{
@Binding var index: Int?
var body: some View {
ReusableView_Original(index: Binding<Int>(get: {
return self.index == nil ? 0 : self.index!
}, set: { (int) in
self.index = int
}))
}
}
您可以像这样扩展和测试结果:
struct ParentView: View {
@State var index: Int? = 2
var body: some View {
VStack{
ReusableView(index: $index)
ReusableView(index: $index, useDefault: true)
}
}
}
struct ReusableView: View{
@Binding var index: Int?
@State var useDefault = false
@State var defaultValue = 0
var body: some View {
ReusableView_Original(index: Binding<Int>(get: {
return self.useDefault ? self.defaultValue : self.index!
}, set: { (int) in
self.useDefault = false
self.index = int
}))
}
}
答案 4 :(得分:0)
根据我的经验,将index
包裹在ObservableObject
中会更干净,更方便。
class IndexStore: ObservableObject {
@Published index = 0
}
然后在您的ReusableView
struct ReusableView: View {
var indexStore = IndexStore()
var body: some View {
Text("The index is \(self.indexStore.index)"
// A button that changes the index
}
}
然后,您可以将其实例化为用于“内部”索引的ReusableView()
,用于实例化父索引的ReusableView(indexStore: parentIndexStore)
,甚至可以在不同的ReusableView
之间共享索引。
答案 5 :(得分:-1)
想法是为可重用视图使用@State
,并在需要时将其与父视图同步。因为每个视图应该只有一个状态管理器。但是它可以与外部管理器同步。
因此,您可以使其optional
并添加另一个内部@State
来进行本地更改:
struct ReusableView: View {
@Binding var parentIndex: Int?
@State private var defaultIndex = 0 { didSet { guard parentIndex != nil else { return }; parentIndex! += 1 } }
var index: Int { parentIndex ?? defaultIndex }
var body: some View {
VStack {
Text("The index is \(self.index)")
Button("Reuseable") { self.defaultIndex += 1 }
}
}
}
然后,当您不希望这样由父母处理时,您可以轻松地传递.constant(nil)
:
struct ParentView: View {
@State var index: Int? = 0
var body: some View {
VStack {
Text("Parent: \(index!)")
ReusableView(parentIndex: $index)
Button("Parent") { self.index! += 1 }
Divider()
ReusableView(parentIndex: .constant(nil))
}
}
}
请注意,我添加了一些按钮,操作和帮助器以使其易于显示,但是您当然可以根据需要更改所有内容