SwiftUI 如何摆脱 NavigationView 中通过 NavigationLink 链接的视图的不断初始化?
SwiftUI how to get rid of constant initialization of views linked through NavigationLink in NavigationView?
在下面的示例中,NavigationView 中有 2 个通过 NavigationLink 链接的视图:根 ContentView 和从属 ListView。
struct ContentView: View {
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State private var counter = 0
var body: some View {
NavigationView{
VStack{
Text("counter: \(counter)")
.onReceive(timer) { _ in
counter += 1
}
NavigationLink(destination: ListView()) {
Text("List").padding()
}
}
}
}
}
struct ListView: View {
let items = ["first", "second", "third"]
init() {
print(Date(), "init ListView")
}
var body: some View {
List{
ForEach(items, id: \.self) { item in
NavigationLink(destination: ItemView(item: item)) {
Text(item).padding()
}
}
}
}
}
在ContentView中,counter变量每秒变化一次,导致ContentView重绘,ListView永久初始化。
ListView 被初始化,“init ListView”每秒显示一次,而不管 ContentView 或 ListView 上当前有什么。
从数据模型的角度来看,ListView 不以任何方式依赖于计数器和 ContentView 的状态。
在实际应用中,情况要复杂得多,成本也要高得多。 ContentView显示的是状态不断变化的MapView,ListView显示的是记录的路线列表。
ListView 初始化包含一些相当昂贵的逻辑。
不断初始化 ListView 需要时间,虽然实际上只在切换到 ListView 时才需要,而不是每次 ContentView 更改状态时都需要。
在摆脱 ListView 对 ContentView 状态变化的依赖的同时使 NavigationView 工作的正确方法是什么?
正如 lorem ipsum 所说,内容的初始化和更新是 SwiftUI 工作方式的一部分,并且是可以预期的。为了克服这个问题,只需创建一个处理计时器的新视图,这样只有你的最后一个会被更新。您的 ContentView 应该看起来更像:
struct ContentView: View {
var body: some View {
NavigationView{
VStack{
CounterView()
NavigationLink(destination: ListView()) {
Text("List").padding()
}
}
}
}
}
还有你的 CounterView :
struct CounterView: View {
@State private var counter = 0
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
Text("counter: \(counter)")
.onReceive(timer) { _ in
counter += 1
}
}
}
但是,如果您需要在整个过程中访问您的 Timer 变量而不必更新每个视图,您可以考虑通过简单地调用“Timer.secondsUpdater”来使单调可访问。
extension Timer {
static let secondsUpdater = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
您的 CounterView 最终会像这样调用计时器:
struct CounterView: View {
@State private var counter = 0
var body: some View {
Text("counter: \(counter)")
.onReceive(Timer.secondsUpdater) { _ in
counter += 1
}
}
}
希望对您有所帮助
在下面的示例中,NavigationView 中有 2 个通过 NavigationLink 链接的视图:根 ContentView 和从属 ListView。
struct ContentView: View {
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State private var counter = 0
var body: some View {
NavigationView{
VStack{
Text("counter: \(counter)")
.onReceive(timer) { _ in
counter += 1
}
NavigationLink(destination: ListView()) {
Text("List").padding()
}
}
}
}
}
struct ListView: View {
let items = ["first", "second", "third"]
init() {
print(Date(), "init ListView")
}
var body: some View {
List{
ForEach(items, id: \.self) { item in
NavigationLink(destination: ItemView(item: item)) {
Text(item).padding()
}
}
}
}
}
在ContentView中,counter变量每秒变化一次,导致ContentView重绘,ListView永久初始化。 ListView 被初始化,“init ListView”每秒显示一次,而不管 ContentView 或 ListView 上当前有什么。 从数据模型的角度来看,ListView 不以任何方式依赖于计数器和 ContentView 的状态。
在实际应用中,情况要复杂得多,成本也要高得多。 ContentView显示的是状态不断变化的MapView,ListView显示的是记录的路线列表。 ListView 初始化包含一些相当昂贵的逻辑。 不断初始化 ListView 需要时间,虽然实际上只在切换到 ListView 时才需要,而不是每次 ContentView 更改状态时都需要。
在摆脱 ListView 对 ContentView 状态变化的依赖的同时使 NavigationView 工作的正确方法是什么?
正如 lorem ipsum 所说,内容的初始化和更新是 SwiftUI 工作方式的一部分,并且是可以预期的。为了克服这个问题,只需创建一个处理计时器的新视图,这样只有你的最后一个会被更新。您的 ContentView 应该看起来更像:
struct ContentView: View {
var body: some View {
NavigationView{
VStack{
CounterView()
NavigationLink(destination: ListView()) {
Text("List").padding()
}
}
}
}
}
还有你的 CounterView :
struct CounterView: View {
@State private var counter = 0
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
Text("counter: \(counter)")
.onReceive(timer) { _ in
counter += 1
}
}
}
但是,如果您需要在整个过程中访问您的 Timer 变量而不必更新每个视图,您可以考虑通过简单地调用“Timer.secondsUpdater”来使单调可访问。
extension Timer {
static let secondsUpdater = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
您的 CounterView 最终会像这样调用计时器:
struct CounterView: View {
@State private var counter = 0
var body: some View {
Text("counter: \(counter)")
.onReceive(Timer.secondsUpdater) { _ in
counter += 1
}
}
}
希望对您有所帮助