将 SwiftUI 数据源放在其他地方

Posted

技术标签:

【中文标题】将 SwiftUI 数据源放在其他地方【英文标题】:Placing SwiftUI Data Sources Somewhere Else 【发布时间】:2021-03-14 19:30:31 【问题描述】:

我正在尝试在项目中使用 SwiftUI,但超出了在每个教程中都可以找到的使用 @States 和 @Bindings 的非常基本的版本,所以我需要一些帮助来解决我在这里做错的地方。

环境设置: 我有以下与此问题有关的文件:

CustomTextField:它是一个 SwiftUI 视图,包含一个内部 TextField 以及其他一些东西(根据设计) CustomTextFieldConfiguration:包含我需要在自定义文本字段视图上配置的内容 RootView:这是一个使用 CustomTextField 作为其子视图之一的 SwiftUI 视图 RootPresenter:这是 UI 逻辑和表示逻辑所在的位置(在视图和业务逻辑之间) RootPresentationModel:它是 ViewModel,Presenter 可以通过它修改视图的状态 RootBuilder:它包含使用构建器模式将组件连接在一起的构建器类

问题

rootPresentationModeltextValue 属性中的 textField 值未更新

这是我所做的(部分)实现,但不知道我哪里出错了:

自定义文本字段

struct CustomTextField: View 
@Binding var config: CustomTextFieldConfiguration

var body: some View 
    ZStack 
        VStack 
            VStack 
                ZStack 
                    HStack 
                        TextField($config.placeHolder,
                                  value: $config.textValue,
                                  formatter: NumberFormatter(),
                                  onEditingChanged: _ in ,
                                  onCommit: )
                            .frame(height: 52.0)
                            .padding(EdgeInsets(top:    0, leading:  16 + ($config.detailActionImage != nil ? 44 : 0),
                                                bottom: 0, trailing: 16 + ($config.contentAlignment == .center && $config.detailActionImage != nil ? 44 : 0)))
                            .background($config.backgroundColor)
                            .cornerRadius($config.cornerRedius)
                            .font($config.font)
            ...
            ...
            ...
            ...

CustomTextFieldConfiguration

struct CustomTextFieldConfiguration 
    @Binding var textValue: String
    ...
    ...
    ...
    ...

RootView

struct RootView: View 
    @State var configuration: CustomTextFieldConfiguration
    var interactor: RootInteractorProtocol!
    @Environment(\.colorScheme) private var colorScheme
    
    var body: some View 
        HStack 
            Spacer(minLength: 40)
            VStack(alignment: .trailing) 
                CustomTextField(config: $configuration)
                Text("\(configuration.textValue)")
            
            Spacer(minLength: 40)
        
    

RootPresenter

class RootPresenter: BasePresenter 

    @ObservedObject var rootPresentationModel: RootPresentationModel

    init(presentationModel: RootPresentationModel) 
        rootPresentationModel = presentationModel
    
    ...
    ...
    ...

RootPresentationModel

class RootPresentationModel: ObservableObject 
    var textValue: String = ""  
        didSet 
            print(textValue)
        
    

RootBuilder

class RootBuilder: BaseBuilder 
    class func build() -> (RootView, RootInteractor) 
        let interactor = RootInteractor()
        
        let presenter = RootPresenter(presentationModel: RootPresentationModel())
        let view: RootView = RootView(configuration: CustomTextFieldConfiguration.Presets.priceInput(textValue: presenter.$rootPresentationModel.textValue, placeholder: "", description: ""), interactor: interactor)
        let router = RootRouter()
        
        interactor.presenter = presenter
        interactor.router = router
        
        return (view, interactor)
    

(Presets 方法没有做任何重要的事情,只是为了确保它不会引发不相关的问题,这是实现):

static func priceInput(textValue: Binding<String>, placeholder: String, description: String) -> CustomTextFieldConfiguration 
      return CustomTextFieldConfiguration(textValue: textValue,
                                         placeHolder: placeholder,
                                         description: description,
                                         defaultDescription: description,
                                         textAlignment: .center,
                                         descriptionAlignment: .center,
                                         contentAlignment: .center,
                                         font: CustomFont.headline1))

【问题讨论】:

我担心你有一个@Binding 持有另一个@Binding。我从未见过这样做,如果它导致有趣的结果,我也不会感到惊讶。 我建议对 SwiftUI 数据源使用ObservableObject/ObservedObject 模式。 我也不太明白 RootPresentationModel 在做什么以及为什么它是 ObservableObject,但它的 textValue 属性不是 @Published。好像如果你在某个地方观察它,你会想要它。 @jnpdx 很高兴您提到,将状态传递给它们之间有多个层的绑定的适当方法是什么? 我尝试了使用@Published 和不使用它。结果没有差异 【参考方案1】:
import SwiftUI
struct CustomTextField: View 
    @EnvironmentObject var config: CustomTextFieldConfiguration
    @Binding var textValue: Double
    var body: some View 
        ZStack 
            VStack 
                VStack 
                    ZStack 
                        HStack 
                            //Number formatter forces the need for Double
                            TextField(config.placeHolder,
                                      value: $textValue,
                                      formatter: NumberFormatter(),
                                      onEditingChanged: _ in ,
                                      onCommit: )
                                .frame(height: 52.0)
                                //.padding(EdgeInsets(top:    0, leading:  16 + (Image(systemName: config.detailActionImageName) != nil ? 44 : 0),bottom: 0, trailing: 16 + (config.contentAlignment == .center && Image(systemName: config.detailActionImageName) != nil ? 44 : 0)))
                                .background(config.backgroundColor)
                                .cornerRadius(config.cornerRedius)
                                .font(config.font)
                        
                    
                
            
        
    

class CustomTextFieldConfiguration: ObservableObject 
    @Published var placeHolder: String = "place"
    @Published var detailActionImageName: String = "checkmark"
    @Published var contentAlignment: UnitPoint = .center
    @Published var backgroundColor: Color = Color(UIColor.secondarySystemBackground)
    @Published var font: Font = .body
    @Published var cornerRedius: CGFloat = CGFloat(5)
    @Published var description: String = ""
    @Published var defaultDescription: String = ""
    @Published var textAlignment: UnitPoint = .center
    @Published var descriptionAlignment: UnitPoint = .center
    
    init() 
        
    
    init(placeHolder: String, description: String, defaultDescription: String, textAlignment: UnitPoint,descriptionAlignment: UnitPoint,contentAlignment: UnitPoint, font:Font) 
        self.placeHolder = placeHolder
        self.description = description
        self.defaultDescription = defaultDescription
        self.textAlignment = textAlignment
        self.descriptionAlignment = descriptionAlignment
        self.contentAlignment = contentAlignment
        self.font = font
    
    struct Presets 
        static func priceInput(placeholder: String, description: String) -> CustomTextFieldConfiguration 
            return CustomTextFieldConfiguration(placeHolder: placeholder, description: description,defaultDescription: description,textAlignment: .center,descriptionAlignment: .center,contentAlignment: .center, font:Font.headline)
        
    

struct RootView: View 
    @ObservedObject var configuration: CustomTextFieldConfiguration
    //var interactor: RootInteractorProtocol!
    @Environment(\.colorScheme) private var colorScheme
    @Binding var textValue: Double
    
    var body: some View 
        HStack 
            Spacer(minLength: 40)
            VStack(alignment: .trailing) 
                CustomTextField(textValue: $textValue).environmentObject(configuration)
                Text("\(textValue)")
            
            Spacer(minLength: 40)
        
    

//RootPresenter is a class @ObservedObject only works properly in SwiftUI Views/struct
class RootPresenter//: BasePresenter

    //Won't work can't chain ObservableObjects
//        var rootPresentationModel: RootPresentationModel
//
//        init(presentationModel: RootPresentationModel) 
//            rootPresentationModel = presentationModel
//        

class RootPresentationModel: ObservableObject 
    @Published var textValue: Double = 12  
        didSet 
            print(textValue)
        
    


struct NewView: View 
    //Must be observed directly
    @StateObject var vm: RootPresentationModel = RootPresentationModel()
    //This cannot be Observed
    let presenter: RootPresenter = RootPresenter()

    var body: some View 
        RootView(configuration: CustomTextFieldConfiguration.Presets.priceInput(placeholder: "", description: ""), textValue: $vm.textValue//, interactor: interactor
        )
    


struct NewView_Previews: PreviewProvider 
    static var previews: some View 
        NewView()
    

【讨论】:

谢谢,做了一些修改。

以上是关于将 SwiftUI 数据源放在其他地方的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 列表/表单中禁用滚动

SwiftUI 在视图之间共享模型数据

SwiftUI 在哪里加载 MVVM 中的数据

从 SwiftUI 列表中的每个元素获取用户输入

如何使用核心数据将对象传递给 SwiftUI 中的其他视图

将css文件的引入放在和放在其他地方有啥区别