.onAppear运行SwiftUI时,视图未刷新

时间:2020-05-10 02:13:19

标签: swift swiftui

我在.onAppear中有一些代码,它可以运行,问题是我必须转到菜单上的另一个视图,然后返回以刷新UI视图。该代码有点冗长,但是下面是主要组件,其中,饭食数据来自CoreData并具有一些对象:

转到底部的更新注释以获取更简单的代码示例

VStack(alignment: .leading) {

            ScrollView(.vertical, showsIndicators: false) {
                VStack(spacing: 2) {
                    ForEach(mealData, id: \.self) { meal in
                        VStack(alignment: .leading) { ... }
                        .onAppear {

                            // If not first button and count < total amt of objects
                            if((self.settingsData.count != 0) && (self.settingsData.count < self.mealData.count)){
                                let updateSettings = Settings(context: self.managedObjectContext)

                                // If will_eat time isn't nill and it's time is overdue and meal status isn't done
                                if ((meal.will_eat != nil) && (IsItOverDue(date: meal.will_eat!) == true) && (meal.status! != "done")){
                                    self.mealData[self.settingsData.count].status = "overdue"

                                    print(self.mealData[self.settingsData.count])

                                        if(self.settingsData.count != self.mealData.count-1) {
                                            // "Breakfast": "done" = active - Add active to next meal
                                            self.mealData[self.settingsData.count+1].status = "active"
                                        }

                                        updateSettings.count += 1

                                    if self.managedObjectContext.hasChanges {
                                        // Save the context whenever is appropriate
                                        do {
                                            try self.managedObjectContext.save()
                                        } catch let error as NSError {
                                            print("Error loading: \(error.localizedDescription), \(error.userInfo)")
                                        }
                                    }
                                }
                            }
                        }
                    }
                 }
             }
         }

很可能是因为UI不会自动刷新,所以我做错了什么,但是怎么办?

更新:

我举了一个小例子来复制正在发生的事情,如果运行它,然后单击set future date,然后5秒钟,您将看到该框没有变色,然后,单击Go to view 2,然后返回到视图1,您将看到框的颜色如何变化……这也是上面发生的情况:

import SwiftUI

struct ContentView: View {

@State var past = Date()
@State var futuredate = Date()


var body: some View {

    NavigationView {
        VStack {
            NavigationLink(destination: DetailView())
            { Text("Go to view 2") }

            Button("set future date") {
                self.futuredate = self.past.addingTimeInterval(5)
            }

            VStack {
                if (past < futuredate) {
                    Button(action: {
                    }) {
                        Text("")
                    }
                    .padding()
                    .background(Color.blue)
                } else {
                    Button(action: {
                    }) {
                        Text("")
                    }
                    .padding()
                    .background(Color.black)
                }
            }
        }
        .onAppear {
            self.past = Date()
        }
    }
}

}


struct DetailView: View {

@Environment(\.presentationMode) var presentationMode: Binding

var body: some View {
   Text("View 2")
}
}

1 个答案:

答案 0 :(得分:0)

您需要了解,您在body{...}中输入的内容仅是如何显示此视图的说明。在运行时系统中,使用body闭包创建此View,将其像图片一样存储在内存中,并将此图片固定在其中,并使用您在其中定义的所有存储属性进行构造。 Body不是存储的属性。它是如何创建图片的唯一说明。

在您的代码中,第ini t首位。您没有编写它,但是它运行并将所有structs属性设置为默认值= Date()。之后运行.onAppear闭包,更改past的值。然后系统才运行body闭包以使图像以新的past值显示。就这样。它不是每秒都在重新创建自己。您需要触发它以检查条件past < futuredate是否已更改。

当您转到另一个视图并返回时,您完全可以执行此操作-强制系统重新创建视图,这就是它再次检查条件的原因。

如果您希望在经过一定的固定时间后自动更改视图,请使用

DispatchQueue.main.asyncAfter(deadline: .now() + Double(2)) {
    //change @State variable to force View to update
}

对于更复杂的情况,您可以像这样使用Timer:

class MyTimer: ObservableObject {
    @Published var currentTimePublisher: Timer.TimerPublisher
    var cancellable: Cancellable?
    let interval: Double
    var isOn: Bool{
        get{if self.cancellable == nil{
                return false
            }else{
                return true
            }
        }
        set{if newValue == false{
                self.stop()
            }else{
                self.start()
            }
        }
    }

    init(interval: Double, autoStart: Bool = true) {
        self.interval = interval
        let publisher = Timer.TimerPublisher(interval: interval, runLoop: .main, mode: .default)
        self.currentTimePublisher = publisher
        if autoStart{
            self.start()
        }
    }
    func start(){
        if self.cancellable == nil{
            self.currentTimePublisher = Timer.TimerPublisher(interval: self.interval, runLoop: .main, mode: .default)
            self.cancellable = self.currentTimePublisher.connect()
        }else{
            print("timer is already started")
        }
    }
    func stop(){
        if self.cancellable != nil{
            self.cancellable!.cancel()
            self.cancellable = nil
        }else{
            print("timer is not started (tried to stop)")
        }
    }
    deinit {
        if self.cancellable != nil{
            self.cancellable!.cancel()
            self.cancellable = nil
        }
    }
}
struct TimerView: View {
    @State var counter: Double
    let timerStep: Double
    @ObservedObject var timer: TypeTimer
    init(timerStep: Double = 0.1, counter: Double = 10.0){
        self.timerStep = timerStep
        self._counter = State<Double>(initialValue: counter)
        self.timer = MyTimer(interval: timerStep, autoStart: false)
    }
    var body: some View {
        Text("\(counter)")
            .onReceive(timer.currentTimePublisher) {newTime in
                print(newTime.description)
                if self.counter > self.timerStep {
                    self.counter -=  self.timerStep
                }else{
                    self.timer.stop()
                }
            }
        .onTapGesture {
            if self.timer.isOn {
                self.timer.stop()
            }else{
                self.timer.start()
            }
         }
    }
}

看到主意了吗?您启动计时器,它将通过发送带有View的通知来强制Publisher更新。 View使用.onReceive获取该通知,并更改@State变量counter,并导致View更新。您需要执行类似的操作。