SwiftUI:强制更新

SwiftUI: Forcing an Update

通常情况下,我们不能讨论 Apple 预发布的东西,但我已经看到很多关于 SwiftUI 的讨论,所以我怀疑这没问题;就这一次。

我正在学习其中一个教程(我这样做)。

我在 "Interfacing With UIKit" 教程中的可滑动屏幕下方添加了一对按钮:https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

这些是 "Next" 和 "Prev" 按钮。在一端或另一端时,相应的按钮会隐藏。我有那个工作正常。

我遇到的问题是访问由 PageViewController 表示的 UIPageViewController 实例。

我更改了 currentPage 属性(通过使 PageViewController 成为 UIPageViewController 的委托),但我需要以编程方式强制更改 UIPageViewController。

我知道我可以 "brute force" 通过重绘 PageView 主体来反映新的 currentPage 来显示,但我不太确定该怎么做。

struct PageView<Page: View>: View {
    var viewControllers: [UIHostingController<Page>]
    @State var currentPage = 0

    init(_ views: [Page]) {
        self.viewControllers = views.map { UIHostingController(rootView: [=10=]) }
    }

    var body: some View {
        VStack {
            PageViewController(controllers: viewControllers, currentPage: $currentPage)

            HStack(alignment: .center) {
                Spacer()

                if 0 < currentPage {
                    Button(action: {
                        self.prevPage()
                    }) {
                        Text("Prev")
                    }

                    Spacer()
                }

                Text(verbatim: "Page \(currentPage)")

                if currentPage < viewControllers.count - 1 {
                    Spacer()

                    Button(action: {
                        self.nextPage()
                    }) {
                        Text("Next")
                    }
                }

                Spacer()
            }
        }
    }

    func nextPage() {
        if currentPage < viewControllers.count - 1 {
            currentPage += 1
        }
    }

    func prevPage() {
        if 0 < currentPage {
            currentPage -= 1
        }
    }
}

我知道答案应该很明显,但我很难弄清楚如何以编程方式刷新 VStack 或正文。

设置currentPage,因为是@State,会重新加载整个body。

2021 SWIFT 1 和 2 都是:

重要的事情!如果你搜索这个 hack,可能你做错了什么!请在阅读 hack 解决方案之前阅读此块!!!!!!!!!!

Your UI wasn't updated automatically because of you miss something important.

  • Your ViewModel must be a class wrapped into ObservableObject/ObservedObject
  • Any field in ViewModel must be a STRUCT. NOT A CLASS!!!! Swift UI does not work with classes!
  • Must be used modifiers correctly (state, observable/observedObject, published, binding, etc)
  • If you need a class property in your View Model (for some reason) - you need to mark it as ObservableObject/Observed object and assign them into View's object !!!!!!!! inside init() of View. !!!!!!!
  • Sometimes is needed to use hacks. But this is really-really-really exclusive situation! In most cases this wrong way! One more time: Please, use structs instead of classes!

Your UI will be refreshed automatically if all of written above was used correctly.

正确用法示例:

struct SomeView : View {
    @ObservedObject var model : SomeViewModel
    @ObservedObject var someClassValue: MyClass
    
    init(model: SomeViewModel) {
        self.model = model
    
        //as this is class we must do it observable and assign into view manually
        self.someClassValue = model.someClassValue
    }

    var body: some View {
         //here we can use model.someStructValue directly

         // or we can use local someClassValue taken from VIEW, BUT NOT value from model

    }

}

class SomeViewModel : ObservableObject {
    @Published var someStructValue: Bool
    var someClassValue: MyClass = NewMyClass //myClass : ObservableObject

}

以及主题问题的答案。

(黑客解决方案 - 最好不要使用它)

方式一:在视图内部声明:

@State var updater: Bool = false

你只需要更新 toogle() 它:updater.toogle()


方式二:从ViewModel刷新

Works on SwiftUI 2

public class ViewModelSample : ObservableObject
    func updateView(){
        self.objectWillChange.send()
    }
}

方式三:从 ViewModel 刷新:

works on SwiftUI 1

import Combine
import SwiftUI

class ViewModelSample: ObservableObject {
    private let objectWillChange = ObservableObjectPublisher()

    func updateView(){
        objectWillChange.send()
    }
}

这是另一个对我有用的解决方案,使用 id() 标识符。基本上,我们并没有真正刷新视图。我们正在用新视图替换视图。

import SwiftUI

struct ManualUpdatedTextField: View {
    @State var name: String
    
    var body: some View {
        VStack {
            TextField("", text: $name)
            Text("Hello, \(name)!")
        }
    }
}

struct MainView: View {
    
    @State private var name: String = "Tim"
    @State private var theId = 0

    var body: some View {
        
        VStack {
            Button {
                name += " Cook"
                theId += 1
            } label: {
                Text("update Text")
                    .padding()
                    .background(Color.blue)
            }
            
            ManualUpdatedTextField(name: name)
                .id(theId)
        }
    }
}