SwiftUI中@Published变量不更新问题排查(Swift 5、Combine)
问题描述
我正在开发一个使用@Published属性包装器更新后端拉取数据的ViewModel。定义了继承自ObservableObject的ViewResult类来管理数据状态,ViewModel中的guidelines是@Published修饰的ViewResult<[Guideline]>类型。
执行CreateGuideline、DeleteGuideline、ReorderGuidelines等操作时,API调用成功且已修改guidelines内的数据,但SwiftUI的ListView必须退出编辑模式后才会更新,具体流程:
- 点击右上角进入编辑模式
- 输入文本并执行
VM.CreateGuideline() - API调用成功,修改
@Published的guidelines变量 - 视图未更新
- 点击右上角“Done”退出编辑模式
- ListView才显示新条目
我怀疑是对ObservableObject和@Published的使用有误,也可能是EditableView与APIResultView的嵌套结构导致问题。尝试过给VM方法添加@escaping闭包、使用inout参数,但都没解决。
ViewResult类定义
class ViewResult<T> : ObservableObject { @Published var data: T? var state: ViewResultState = .loading var errorCode: APIResponseCode? = nil init(){ self.state = .loading self.data = nil } ///OTHER COVIENIENCE INITIALIZERS NOT SHOWN ///Sets object and state variables if the APIResponse was successful. ///If it was unsuccessful, it sets error code and sets the ViewResult State to code. func SetData(_ result: APIResult<T>) -> Void { switch result { case .success(let data): self.data = data.data self.state = .object case .failure(let error): self.errorCode = APIResponseCode.Error(error) self.state = .code } } }
GuidelinesVM类定义
class GuidelinesVM : ObservableObject { var community_id: Int @Published var guidelines: ViewResult<[Guideline]> = ViewResult(defaultValue: []) private var dispatcher : ViewModelAPIDispatcher = ViewModelAPIDispatcher() init(community_id: Int){ self.community_id = community_id //Get guidelines before view is initialized. //**NOTE: I AM CHANGING THIS TO EXECUTE IN THE ONAPPEAR CLOSURE dispatcher.SendRequest(.getCommunityGuidlines(community_id: community_id)){ result in self.guidelines.SetData(result) } } func ReorderGuidelines(_ fromOffsets : IndexSet, _ toOffset : Int){ //Conversions from Swift's List system to mine let oldOffset = fromOffsets.first! + 1 let guideline_id = self.guidelines.data![oldOffset - 1].id let newOffset = toOffset > oldOffset ? toOffset : toOffset + 1 //Dispatch API Call dispatcher.SendRequest(.reOrderGuidelines(community_id: community_id, guideline_id: guideline_id, fromOffset: oldOffset, toOffset: toOffset)) { apiResult in if IsSuccessful(apiResult) { self.guidelines.data!.move(fromOffsets: fromOffsets, toOffset: toOffset) } } } func DeleteGuideline(_ offsets: IndexSet){ let index = offsets.first! dispatcher.SendRequest(.deleteGuideline(community_id: community_id, guideline_id: index)){ result in if IsSuccessful(result) { self.guidelines.data!.remove(at: index) } } } func CreateGuidline(orderNumber: Int, text: String) { dispatcher.SendRequest(.createGuideline(community_id: community_id, orderNumber: orderNumber, text: text)){ result in if(IsSuccessful(result)){ //Right now just generate random int. I know this is terrible and lazy, but MVP self.guidelines.data!.append(Guideline(id: Int.random(in: -1000...0), order_number: self.guidelines.data!.count + 1, text: text)) } } } }
CommunityGuidelinesView视图定义
struct CommunityGuidelinesView: View { var psCommunityData : psCommunityClass @State private var newGuideline : String = "" @ObservedObject private var VM : GuidelinesVM init(community : psCommunityClass){ self.psCommunityData = community self.VM = GuidelinesVM(community_id: community.id) } var body: some View { EditableView(userRole: psCommunityData.role){ inEditMode in VStack{ APIResultView(apiResult: $VM.guidelines){ guidelines in List { // LIST DISPLAY CODE } .onDelete { indexSet in VM.DeleteGuideline(indexSet) } .onMove{ fromOffsets, newOffset in VM.ReorderGuidelines(fromOffsets, newOffset) } //NEED TO PASS A BINDING FOR NESTED VIEW CONTROL if inEditMode.wrappedValue { EditingView( //VIEW IF NOT EDITING }, EditingContent: { //VIEW IF EDITING }, completion: { //FUNCTION EXECTUED ONCE DONE EDITING VM.CreateGuidline(orderNumber: guidelines.count + 1, text: newGuideline) } ) } } } } } } }
APIResultView视图定义
struct APIResultView<SuccessDisplay : View, T>: View { @Binding var apiResult : ViewResult<T> var content : (T) -> SuccessDisplay var body: some View { switch apiResult.state{ case .object: content(apiResult.data!) case .loading: ProgressView("Loading...") case .code: if let errorCode = apiResult.errorCode { HttpStatusView(statusCode: errorCode) } else { HttpStatusView(statusCode: apiResult.data as! APIResponseCode) } } } }
内容的提问来源于stack exchange,提问作者RadiantReasons
相关产品推荐
相关产品推荐

