简体   繁体   English

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

[英]View not refreshed when .onAppear runs SwiftUI

I have some code inside .onAppear and it runs, the problem is that I have to go to another view on the menu and then come back for the UI view to refresh.我在.onAppear中有一些代码并且它运行,问题是我必须 go 到菜单上的另一个视图,然后回来刷新 UI 视图。 The code is kind of lengthy, but the main components are below, where mealData comes from CoreData and has some objects:代码有点长,但主要组件如下,其中 mealData 来自 CoreData 并有一些对象:

Go to the updated comment at the bottom for a simpler code example Go 到底部的更新注释以获得更简单的代码示例

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)")
                                        }
                                    }
                                }
                            }
                        }
                    }
                 }
             }
         }

Most likely since the UI is not refreshing automatically I'm doing something wrong, but what?很可能是因为 UI 没有自动刷新,所以我做错了什么,但是什么?

UPDATE:更新:

I made a little example replicating what's going on, if you run it, and click on set future date , and wit 5 seconds, you'll see that the box hasn't changed color, after that, click on Go to view 2 and go back to view 1 and you'll see how the box color changes... that's what's happening above too:我做了一个小例子来复制正在发生的事情,如果你运行它,然后单击set future date ,然后等待 5 秒,你会看到该框没有改变颜色,之后,单击Go to view 2和go 返回视图 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")
}
}

You need to understand that all you put in body{...} is just an instruction how to display this view.您需要了解您在body{...}中输入的所有内容只是如何显示此视图的说明。 In runtime system creates this View using body closure, gets it in memory like picture and pin this picture to struct with all the stored properties you define in it.在运行时系统中,使用body闭包创建此视图,在 memory 中获取它,就像图片一样,并将此图片固定到您在其中定义的所有存储属性的结构中。 Body is not a stored property. Body不是存储属性。 Its only instruction how to create that picture.它唯一的说明如何创建该图片。

In your code, there goes ini t first.在您的代码中,首先进行ini You didn't write it but it runs and sets all the structs property to default values = Date() .您没有编写它,但它运行并将所有 structs 属性设置为默认值= Date() After that runs .onAppear closure which changes past value.之后运行.onAppear闭包,它会改变past的值。 And only then system runs body closure to make an image to display with new past value.然后系统才运行body闭合以使图像以新的past值显示。 And that's all.就这样。 It is not recreating itself every second.它不是每秒钟都在重新创建自己。 You need to trigger it to check if the condition past < futuredate changed.您需要触发它以检查past < futuredate的条件是否发生了变化。

When you go to another view and back you do exactly that - force system to recreate view and that's why it checks condition again.当您将 go 转到另一个视图并返回时,您正是这样做的 - 强制系统重新创建视图,这就是它再次检查条件的原因。

If you want View to change automatically after some fixed time spend, use如果您希望 View 在花费一些固定时间后自动更改,请使用

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

for more complex cases you can use Timer like this:对于更复杂的情况,您可以像这样使用 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()
            }
         }
    }
}

See the idea?看到这个想法了吗? You start timer and it will force View to update by sending notification with the Publisher .您启动计时器,它将通过向Publisher发送通知来强制View更新。 View gets that notifications with .onReceive and changes @State variable counter and that cause View to update itself. View 通过.onReceive获取通知并更改@State变量counter ,这会导致 View 自行更新。 You need to do something like that.你需要做这样的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM