- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我已经研究了几天,搜索了 Swift 和 SwiftUI 文档、SO、论坛等,但似乎找不到答案。
这就是问题所在;
我有一个 SwiftUI 自定义 View ,它对远程资源的自定义 API 请求类进行一些状态确定。 View 处理显示加载状态和失败状态,以及通过 ViewBuilder 传递其主体内容,因此如果来自 API 的状态成功并且资源数据已加载,它将显示页面内容。
问题是,当子类 ObservedObject 更新时,ViewBuilder 内容不会重新呈现。对象会根据 UI 进行更新(按下按钮时等),但 UI 从不重新渲染/更新以反射(reflect)子类 ObservedObject 中的更改,例如,子类 ObservedObject 中数组后面的 ForEach 不会刷新数组内容改变。如果我将其移出自定义 View ,则 ForEach 会按预期工作。
我可以确认代码编译并运行。观察者和 debugPrint()
始终表明 ApiObject
正在正确更新状态,并且 View 反射(reflect)了 ApiState
的更改非常好。它只是 ViewBuilder 的 Content
。我假设是因为 ViewBuilder 只会被调用一次。
EDIT:上面的段落应该是提示,ApiState
正确更新,但是在将大量登录放入应用程序后,UI 没有监听发布ObservedObject 的子类。属性在变化,状态也在变化,但 UI 并没有对此使用react。还有,下一句是假的,我在VStack中再次测试,组件仍然没有重新渲染,说明我找错地方了!
如果是这样,VStack
和其他类似元素如何解决这个问题?还是因为我的 ApiObjectView
在状态更改时重新渲染,导致 subview “重置”?尽管在这种情况下,我希望它能够接收新数据并按预期工作,但它永远不会重新渲染。
有问题的代码在下面的 CustomDataList.swift
和 ApiObjectView.swift
中。我留下评论指出正确的方向。
这里是示例代码;
// ApiState.swift
// Stores the API state for where the request and data parse is currently at.
// This drives the ApiObjectView state UI.
import Foundation
enum ApiState: String
{
case isIdle
case isFetchingData
case hasFailedToFetchData
case isLoadingData
case hasFailedToLoadData
case hasUsableData
}
// ApiObject.swift
// A base class that the Controllers for the app extend from.
// These classes can make data requests to the remote resource API over the
// network to feed their internal data stores.
class ApiObject: ObservableObject
{
@Published var apiState: ApiState = .isIdle
let networkRequest: NetworkRequest = NetworkRequest(baseUrl: "https://api.example.com/api")
public func apiGetJson<T: Codable>(to: String, decodeAs: T.Type, onDecode: @escaping (_ unwrappedJson: T) -> Void) -> Void
{
self.apiState = .isFetchingData
self.networkRequest.send(
to: to,
onComplete: {
self.apiState = .isLoadingData
let json = self.networkRequest.decodeJsonFromResponse(decodeAs: decodeAs)
guard let unwrappedJson = json else {
self.apiState = .hasFailedToLoadData
return
}
onDecode(unwrappedJson)
self.apiState = .hasUsableData
},
onFail: {
self.apiState = .hasFailedToFetchData
}
)
}
}
// DataController.swift
// This is a genericised example of the production code.
// These controllers build, manage and serve their resource data.
// Subclassed from the ApiObject, inheriting ObservableObject
import Foundation
import Combine
class CustomDataController: ApiObject
{
@Published public var customData: [CustomDataStruct] = []
public func fetch() -> Void
{
self.apiGetJson(
to: "custom-data-endpoint ",
decodeAs: [CustomDataStruct].self,
onDecode: { unwrappedJson in
self.customData = unwrappedJson
}
)
}
}
这是在 ObservedObject
更改为其绑定(bind)数组属性时重新渲染其 ForEach
的问题。
// CustomDataList.swift
// This is the SwiftUI View that drives the content to the user as a list
// that displays the CustomDataController.customData.
// The ForEach in this View
import SwiftUI
struct CustomDataList: View
{
@ObservedObject var customDataController: CustomDataController = CustomDataController()
var body: some View
{
ApiObjectView(
apiObject: self.customDataController,
onQuit: {}
) {
List
{
Section(header: Text("Custom Data").padding(.top, 40))
{
ForEach(self.customDataController.customData, id: \.self, content: { customData in
// This is the example that doesn't re-render when the
// customDataController updates its data. I have
// verified via printing at watching properties
// that the object is updating and pushing the
// change.
// The ObservableObject updates the array, but this ForEach
// is not run again when the data is changed.
// In the production code, there are buttons in here that
// change the array data held within customDataController.customData.
// When tapped, they update the array and the ForEach, when placed
// in the body directly does reflect the change when
// customDataController.customData updates.
// However, when inside the ApiObjectView, as by this example,
// it does not.
Text(customData.textProperty)
})
}
}
.listStyle(GroupedListStyle())
}
.navigationBarTitle(Text("Learn"))
.onAppear() {
self.customDataController.fetch()
}
}
}
struct CustomDataList_Previews: PreviewProvider
{
static var previews: some View
{
CustomDataList()
}
}
这是有问题的自定义 View ,不会重新呈现其内容。
// ApiObjectView
// This is the containing View that is designed to assist in the UI rendering of ApiObjects
// by handling the state automatically and only showing the ViewBuilder contents when
// the state is such that the data is loaded and ready, in a non errornous, ready state.
// The ViewBuilder contents loads fine when the view is rendered or the state changes,
// but the Content is never re-rendered if it changes.
// The state renders fine and is reactive to the object, the apiObjectContent
// however, is not.
import SwiftUI
struct ApiObjectView<Content: View>: View {
@ObservedObject var apiObject: ApiObject
let onQuit: () -> Void
let apiObjectContent: () -> Content
@inlinable public init(apiObject: ApiObject, onQuit: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) {
self.apiObject = apiObject
self.onQuit = onQuit
self.apiObjectContent = content
}
func determineViewBody() -> AnyView
{
switch (self.apiObject.apiState) {
case .isIdle:
return AnyView(
ActivityIndicator(
isAnimating: .constant(true),
style: .large
)
)
case .isFetchingData:
return AnyView(
ActivityIndicator(
isAnimating: .constant(true),
style: .large
)
)
case .isLoadingData:
return AnyView(
ActivityIndicator(
isAnimating: .constant(true),
style: .large
)
)
case .hasFailedToFetchData:
return AnyView(
VStack
{
Text("Failed to load data!")
.padding(.bottom)
QuitButton(action: self.onQuit)
}
)
case .hasFailedToLoadData:
return AnyView(
VStack
{
Text("Failed to load data!")
.padding(.bottom)
QuitButton(action: self.onQuit)
}
)
case .hasUsableData:
return AnyView(
VStack
{
self.apiObjectContent()
}
)
}
}
var body: some View
{
self.determineViewBody()
}
}
struct ApiObjectView_Previews: PreviewProvider {
static var previews: some View {
ApiObjectView(
apiObject: ApiObject(),
onQuit: {
print("I quit.")
}
) {
EmptyView()
}
}
}
现在,如果不使用 ApiObjectView
并将内容直接放在 View 中,上述所有代码都可以正常工作。
但是,这对于代码重用和架构来说是可怕的,这样它既漂亮又整洁,但不起作用。
有没有其他方法可以解决这个问题,例如通过 ViewModifier
或 View
扩展?
对此的任何帮助将不胜感激。
正如我所说,我似乎找不到任何有此问题的人或任何在线资源可以为我指明解决此问题的正确方向,或者可能导致它的原因,例如 ViewBuilder 文档中的概述。
编辑:为了提供一些有趣的东西,我已经在 CustomDataList
中添加了一个倒数计时器,它每 1 秒更新一个标签。 如果该计时器对象更新了文本,则 View 会重新呈现,但仅当标签上显示倒计时时间的文本更新时。
最佳答案
在拔掉我的头发一周后想通了,这是一个未记录的问题,将 ObservableObject
子类化,如 SO answer 所示.
这特别烦人,因为 Xcode 显然会提示您删除该类,因为父类提供了对 ObservableObject
的继承,所以在我看来一切都很好。
修复方法是,在子类中通过 @Published 上的
有问题的变量,或者你需要的任何变量。willSet
监听器手动触发通用状态更改 self.objectWillChange.send()
在我提供的示例中,问题中的基类 ApiObject
保持不变。
虽然,CustomDataController
需要修改如下:
// DataController.swift
// This is a genericised example of the production code.
// These controllers build, manage and serve their resource data.
import Foundation
import Combine
class CustomDataController: ApiObject
{
@Published public var customData: [CustomDataStruct] = [] {
willSet {
// This is the generic state change fire that needs to be added.
self.objectWillChange.send()
}
}
public func fetch() -> Void
{
self.apiGetJson(
to: "custom-data-endpoint ",
decodeAs: [CustomDataStruct].self,
onDecode: { unwrappedJson in
self.customData = unwrappedJson
}
)
}
}
我添加手动发布后,问题就解决了。
链接答案中的重要说明:不要在子类上重新声明 objectWillChange
,因为这会再次导致状态无法正确更新。例如。声明默认值
let objectWillChange = PassthroughSubject<Void, Never>()
子类上会再次中断状态更新,这需要保留在直接从 ObservableObject
扩展的父类上,无论是我的手动还是自动默认定义(输入或不输入并保留为继承声明)。
尽管您仍然可以根据需要定义任意数量的自定义 PassthroughSubject
声明,而不会在子类上出现问题,例如
// DataController.swift
// This is a genericised example of the production code.
// These controllers build, manage and serve their resource data.
import Foundation
import Combine
class CustomDataController: ApiObject
{
var customDataWillUpdate = PassthroughSubject<[CustomDataStruct], Never>()
@Published public var customData: [CustomDataStruct] = [] {
willSet {
// Custom state change handler.
self.customDataWillUpdate.send(newValue)
// This is the generic state change fire that needs to be added.
self.objectWillChange.send()
}
}
public func fetch() -> Void
{
self.apiGetJson(
to: "custom-data-endpoint ",
decodeAs: [CustomDataStruct].self,
onDecode: { unwrappedJson in
self.customData = unwrappedJson
}
)
}
}
只要
self.objectWillChange.send()
保留在子类所需的 @Published
属性中PassthroughSubject
声明不会在子类上重新声明它将正常工作并正确传播状态更改。
关于ios - SwiftUI 自定义 View 的 ViewBuilder 不会在子类 ObservedObject 更新时重新渲染/更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60148544/
我们可以给 @ViewBuilder 闭包参数一个默认值吗? 在做一些实验的时候出现了这个问题: import SwiftUI import PlaygroundSupport // MyView s
我们可以给 @ViewBuilder 闭包参数一个默认值吗? 在做一些实验的时候出现了这个问题: import SwiftUI import PlaygroundSupport // MyView s
当我将 displayMode 添加到 VStack 上的 .navigationBarTitle 时,出现编译错误:Generic parameter '无法推断 C0'。如果我删除 display
以下 View 是 List() 的第一行,用于替换隐藏的导航栏。 它的左侧有一个后退导航按钮,中间有一个居中的标题,右侧有一个可选按钮。 父 View 负责通过可选的@ViewBuilder 闭包实
我不能在 swift ui view 块中声明一个变量 var body: some View { let isHuman = false Text("Levels \(isHu
在 SwiftUI 中是否有可能有一个可选的 @ViewBuilder关闭?例如,假设我想开发一个自定义 View ,它需要两个 View 构建器闭包,如下所示: import SwiftUI str
如果我想在 SwiftUI 中呈现项目列表,我可以这样做(使用 XCode 12): struct MyView: View { let texts: [String] var bod
我有一个 View ,表示单元格中的一行,如下所示 这很有效,但三个水平元素(图像、标题/副标题、图像)被硬编码为它们各自的类型。 我想要一个通用的 ThreeItemView这需要 3 Views任
是否可以制作 UIViewRepresentable View 需要 ViewBuilder参数处理动态内容,例如 ForEach循环? 我有以下 UIViewRepresentable我用来下拉到
我试图了解如何在 @ViewBuilder 闭包中更改 @State 变量。以下只是一个简单的例子: struct ContentView: View { @State var width =
我正在尝试使用 SwiftUI 构建一个简单的 List。但是,我无法使用数据数组动态创建行。这是错误消息:Cannot convert value of type '(Setlist) -> Set
有什么区别: 属性上的@ViewBuilder struct SomeView: View { @ViewBuilder var content: () -> Content } 在init中
出于好奇,我将 View 类型作为参数传递给 @ViewBuilder。在 @ViewBuilder 中将模型/原始类型作为参数传递是完全有效的。 如下代码所示。 struct TestView: V
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension ViewBuilder { public stat
SwiftUI 中的某些 View ,如 VStack 和 HStack 支持将多个 View 作为 subview ,如下所示: VStack { Text("hello") Text("w
⚠️ 2020 年 6 月 23 日编辑:从 Xcode 12 开始,ViewBuilder 将支持 switch 和 if let 语句! 我一直在尝试使用 SwiftUI 复制我的一个应用程序。它
我正在尝试创建一个简单的 struct接受一个 View 数组并返回一个普通的 VStack包含这些 View ,但它们都是对角堆叠的。 代码: struct LeaningTower: View {
我有一个自定义 View (DisclosableControl),它有条件地公开 UI 控件。 为了使其尽可能适应,初始化程序接受闭包,闭包生成 subview 以显示用户输入和控件的结果。由于这些
我已经研究了几天,搜索了 Swift 和 SwiftUI 文档、SO、论坛等,但似乎找不到答案。 这就是问题所在; 我有一个 SwiftUI 自定义 View ,它对远程资源的自定义 API 请求类进
我是一名优秀的程序员,十分优秀!