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
struct
s 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)
}
}
}
通常情况下,我们不能讨论 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 intoObservableObject
/ObservedObject
- Any field in ViewModel must be a
STRUCT
. NOT ACLASS
!!!! 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 asObservableObject
/Observed
object and assign them into View's object !!!!!!!! insideinit()
ofView
. !!!!!!!- 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
struct
s 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)
}
}
}