为什么定时器更改会触发 LazyVGrid 视图更新?

Why Timer Change Will Trigger LazyVGrid View Update?

import SwiftUI

struct ContentView: View {
    @State private var set = Set<Int>()
    @State private var count = "10"
    private let columns:[GridItem] = Array(repeating: .init(.flexible()), count: 3)
    
    @State private var timer:Timer? = nil
    @State private var time = 0
    
    
    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(Array(set)) { num in
                        Text(String(num))
                    }
                }
            }
            .frame(width: 400, height: 400, alignment: .center)
            
            HStack{
                TextField("Create \(count) items", text: $count)
                
                Button {
                    createSet(count: Int(count)!)
                } label: {
                    Text("Create")
                }
            }
            
            if let _ = timer {
                Text(String(time))
                    .font(.title2)
                    .foregroundColor(.green)
            }
            
            HStack {
                Button {
                    time = 100
                    
                    let timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { _ in
                        time -= 10
                        
                        if time == 0 {
                            self.timer?.invalidate()
                            self.timer = nil
                        }
                    }
                    
                    self.timer = timer
                } label: {
                    Text("Start Timer")
                }
                
                Button {
                    self.timer?.invalidate()
                    self.timer = nil
                } label: {
                    Text("Stop Timer")
                }
            }
        }
        .padding()
    }
    
    private func createSet(count:Int) {
        set.removeAll(keepingCapacity: true)
        
        repeat {
            let num = Int.random(in: 1...10000)
            set.insert(num)
        } while set.count < count
    }
}

extension Int:Identifiable {
    public var id:Self { self }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我在Text(String(num))上做了一个断点。每次触发计时器时,GridView 都会更新。为什么会这样?由于网格的模型没有改变。

已更新

如果我将计时器放在另一个视图中,则不会触发网格视图。

import SwiftUI

struct ContentView: View {
    @State private var set = Set<Int>()
    @State private var count = "10"
    private let columns:[GridItem] = Array(repeating: .init(.flexible()), count: 3)
    
    var body: some View {
        VStack {
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(Array(set)) { num in
                        Text(String(num))
                    }
                }
            }
            .frame(width: 400, height: 400, alignment: .center)
            
            HStack{
                TextField("Create \(count) items", text: $count)
                
                Button {
                    createSet(count: Int(count)!)
                } label: {
                    Text("Create")
                }
            }
            
            TimerView()
        }
        .padding()
    }
    
    private func createSet(count:Int) {
        set.removeAll(keepingCapacity: true)
        
        repeat {
            let num = Int.random(in: 1...10000)
            set.insert(num)
        } while set.count < count
    }
}

extension Int:Identifiable {
    public var id:Self { self }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import SwiftUI

struct TimerView: View {
    @State private var timer:Timer? = nil
    @State private var time = 0
    
    var body: some View {
        if let _ = timer {
            Text(String(time))
                .font(.title2)
                .foregroundColor(.green)
        }
        
        HStack {
            Button {
                time = 100
                
                let timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { _ in
                    time -= 10
                    
                    if time == 0 {
                        self.timer?.invalidate()
                        self.timer = nil
                    }
                }
                
                self.timer = timer
            } label: {
                Text("Start Timer")
            }
            
            Button {
                self.timer?.invalidate()
                self.timer = nil
            } label: {
                Text("Stop Timer")
            }
        }
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        TimerView()
    }
}

这就是 SwiftUI 的工作原理。对 @State var 的每次更改都会触发视图重新评估。如果您将 ForEach 放在另一个视图中,它只会在您更改更改该视图的 var 时重新评估。例如。 setcolumns.

struct ExtractedView: View {
    var columns: [GridItem]
    var set: Set<Int>
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns) {
                ForEach(Array(set)) { num in
                    Text(String(num))
                }
            }
        }
        .frame(width: 400, height: 400, alignment: .center)
    }
}

SwiftUI鼓励多做小Views。驱动它的系统非常擅长识别需要更改的内容和不需要更改的内容。有一个非常好的 WWDC 视频对此进行了描述。

WWDC