我经常读到,我可以使用环境变量注入数据,而不是通过带有参数的视图层次结构向上传递数据。但我遇到了以下不对称(问题在最后)......
我创建了一个包含分页 TabView 的视图,其中包含两个“并排”视图,因此我可以在它们之间滑动或通过按(红色)按钮以编程方式选择另一个视图。我通过将选择作为参数传递来完成此操作,并且这按预期工作。我使用随机背景颜色来验证这两个视图没有重新绘制,而只是“滑入 iPhone 显示屏”。
struct SwipeTabView: View {
@State var selectedTab = 1
var body: some View {
VStack {
Text("Main View")
// A paged tabview containing two vies side by side.
// Swipe within this main view to view the "off-screen" other view, or
// click the red button to select it dynamically.
TabView(selection: $selectedTab) {
tab(tabID: 1, selectedTab: $selectedTab)
.tag(1)
tab(tabID: 2, selectedTab: $selectedTab)
.tag(2)
}
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
.background(Color.rndCol())
}
.background(Color.rndCol())
}
}
struct tab: View {
var tabID: Int
@Binding var selectedTab: Int
var body: some View {
VStack {
Text("Tab \(tabID) ").foregroundColor(.black)
Button(action: {
withAnimation{
selectedTab = tabID == 1 ? 2 : 1
}
}, label: {
ZStack {
circle
label
}
})
}
}
var circle: some View {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
}
var label: some View {
Text("\(tabID == 1 ? ">>" : "<<")").font(.title).foregroundColor(.black)
}
}
extension Color {
static func rndCol() -> Color {
Color(
red: .random(in: 0.5...1),
green: .random(in: 0.5...1),
blue: .random(in: 0.5...1)
)
}
}
然后我更改了代码以将选择作为注入的环境变量传递,但现在每次我通过滑动或按下按钮切换显示内容时,视图都会完全重绘。我可以看出是这种情况,因为视图的背景颜色发生了变化。
@Observable class envTab {
var selectedTab = 1
}
struct SwipeTabEnvView: View {
@State var varEnvTab = envTab()
var body: some View {
SwipeTabView()
.environment(varEnvTab)
}
}
struct SwipeTabView: View {
@Environment(envTab.self) var viewEnvTab
var body: some View {
VStack {
Text("Main View")
TabView(selection: Bindable(viewEnvTab).selectedTab) {
tab(tabID: 1)
.tag(1)
tab(tabID: 2)
.tag(2)
}
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
.background(Color.rndCol())
}
.background(Color.rndCol())
}
}
struct tab: View {
var tabID: Int
@Environment(envTab.self) var viewEnvTab
var body: some View {
VStack {
Text("Tab \(tabID) ").foregroundColor(.black)
Button(action: {
withAnimation{
viewEnvTab.selectedTab = tabID == 1 ? 2 : 1
}
}, label: {
ZStack {
circle
label
}
})
}
}
...
}
如果重新绘制完整视图,这会降低性能,应该避免。
我担心这不仅仅是 TabView 的一个怪癖,而是注入的一个更普遍的方面。我原以为注入数据而不是作为参数传递数据是构造代码以实现陡峭层次结构的有效方法,但看到这种行为后,我现在想知道注入方法是否不等同于传递参数并且应该避免?
顺便说一句:制作
selectedTab
@ObservationIgnored
会停止重绘行为,但也会阻止以编程方式切换显示的选项卡。
我担心这不仅仅是 TabView 的一个怪癖,而是注入的一个更普遍的方面
这是只是
TabView
的一个怪癖。 SwiftUI 无法识别 selectedTab
状态是 SwipeTabView
的依赖项,因此当它发生变化时不会调用 SwipeTabView.body
。
例如A
Picker
,就没有这个问题。尝试用 Picker
, 替换选项卡视图
Picker("Picker", selection: $selectedTab) {
Text("1").tag(1)
Text("2").tag(2)
}
.pickerStyle(.segmented)
.background(Color.rndCol())
当您更改选择器选择时,背景颜色会发生变化,这是预期的行为。毕竟,
selectedTab
是 SwipeTabView
的依赖项,因此当 SwipeTabView.body
发生变化时,应该调用 selectedTab
。这与环境无关。要获得预期的行为,您所需要做的就是让 SwiftUI 正确识别
selectedTab
是一个依赖项。使用
@Observable
可以实现这一点,因为 SwiftUI 以不同的方式处理这些对象。它不一定在环境中:
@Observable
class SomeObservable {
var selectedTab = 1
}
struct SwipeTabView: View {
@State var observable = SomeObservable()
var body: some View {
VStack {
Text("Main View")
TabView(selection: $observable.selectedTab) {
tab(tabID: 1, selectedTab: $observable.selectedTab)
.tag(1)
tab(tabID: 2, selectedTab: $observable.selectedTab)
.tag(2)
}
// ...
另一种方法是简单地在 selectedTab
中调用
SwipeTabView.body
的 getter。
struct SwipeTabView: View {
@State var selectedTab = 1
var body: some View {
let x = selectedTab // this calls the getter of State.wrappedValue
VStack {
Text("Main View")
TabView(selection: $selectedTab) {
// ...