【问题标题】:SwiftUI: Forcing an UpdateSwiftUI:强制更新
【发布时间】:2019-10-26 22:25:21
【问题描述】:

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

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

我在“与 UIKit 交互”教程中的可滑动屏幕下方添加一对按钮:https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

这些是“下一个”和“上一个”按钮。当在一端或另一端时,相应的按钮会隐藏。我的工作正常。

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

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

我知道我可以通过重绘 PageView 主体来“强制”显示,以反映新的 currentPage,但我不完全确定该怎么做。

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

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

    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 或正文。

【问题讨论】:

  • 什么是PageViewController
  • 只需设置currentPage 就足以重新加载正文
  • @Fogmeister - 这是来自教程,所以 PageViewController 是他们创建的示例类。它包装了一个 UIPageViewController 实例。我遇到的问题是我在声明式和命令式语言之间切换时经常遇到的问题。在这种情况下,Swift 两者兼而有之。我几乎拥有它。 Lu_的回答是正确的。我只需要调整一个化妆品
  • @Lu_ > 只需设置 currentPage 就足以重新加载正文
  • 仅供参考,我很确定事情的“正常”已经改变了。您不像 10 年前那样处于同一类型的 NDA 之下。事实上,到下个月,他们所有的测试版操作系统都将是公开测试版。

标签: swift swiftui


【解决方案1】:

2021 SWIFT 1 和 2:

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

您的 UI 没有自动更新,因为您错过了某些内容 很重要。

  • 您的 ViewModel 必须是 class 包装到 ObservableObject/Observed 对象中
  • ViewModel 中的任何字段都必须是STRUCT。不是CLASS!!!! Swift UI 不适用于类!
  • 必须正确使用修饰符(状态、可观察/可观察对象、已发布、绑定等)
  • 如果您的视图模型中需要class 属性(出于某种原因) - 您需要将其标记为ObservableObject/Observed 对象并将它们分配给视图的对象!!!!!!!!!在Viewinit() 内。 !!!!!!!
  • 有时需要使用 hack。但这是非常非常非常独特的情况! 在大多数情况下这是错误的方式!再一次:请使用structs 而不是类!

如果以上所有内容都正确使用,您的 UI 将自动刷新。

正确用法示例:

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 in 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()


方式2:从ViewModel刷新

适用于 SwiftUI 2

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

方式 3:从 ViewModel 刷新:

适用于 SwiftUI 1

import Combine
import SwiftUI

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

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

【讨论】:

  • 如果我们不在 body 变量中使用更新器状态,方法 1 不起作用
  • 方式 1 对我有用,即使没有在正文中使用布尔值(看起来 @Published 在 ObservedObject 中就足够了)
  • 并非如此。仅在 ObservedObject 是一个类的情况下,但在 Observable 内部发布的是一个结构 - 这是在 SwiftUI 中使用 ViewModels 的重要部分
  • @Andrew:非常好的答案 “如果你搜索这个 hack,可能你做错了什么!”
  • "如果您出于某种原因在视图模型中需要类属性 - 您需要将其标记为 ObservableObject/Observed 对象并将其单独连接到您的视图中。在视图的 init() 中执行。 " - 我今天学到了一个新东西,可惜为时已晚。我在项目的许多地方解决了我的黑客问题。不过,我现在要修复它们。这些天来,我只是不喜欢写黑客,觉得我别无选择,但现在我知道我做错了什么。感谢分享知识。
【解决方案2】:

这是另一个对我有用的解决方案,使用 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)
        }
    }
}

【讨论】:

  • 除了使用递增值,还可以使用日期(时间戳)作为 id
【解决方案3】:

设置currentPage,因为它是@State,将重新加载整个主体。

【讨论】:

  • 如果我们只想重新加载Text(verbatim: "Page \(currentPage)")段,应该怎么做?
  • @Sajjad 您可以专门为该标签使用状态变量,如果没有其他依赖项,则它应该重新加载
  • 我有一个绑定到 @State $variable 的 TextField 但是当我输入时,整个正文没有重新加载?
  • 并非总是如此,如果 currentPage 自上次以来没有更改,则不会。
  • @turingtested ...如果数据没有改变,为什么有人要重新加载页面,你可能会问?我在这里评论我自己的评论是因为我想明确存在这样的情况,即外部数据发生变化时,例如时间,页面需要更新以反映这一点。
猜你喜欢
  • 2020-12-29
  • 2022-12-07
  • 2021-06-27
  • 2018-12-26
  • 2018-05-08
  • 2019-02-09
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多