Swift UI 在集合中绑定 TextField

Posted

技术标签:

【中文标题】Swift UI 在集合中绑定 TextField【英文标题】:Swift UI Binding TextField in Collection 【发布时间】:2021-01-26 12:00:33 【问题描述】:

我有两列包含嵌套数据(父/子)。第一列中的每个项目都是父项。选择其中的任何人时,它将其子于第二列显示为列表。

当从第二列中选择任何项目时,它必须在第三列中显示“clipAttr”属性作为文本编辑器,我们可以在其中编辑它。

现在我需要帮助如何在编辑“ClipAttr”时执行此操作,然后它会在 SampleDataModel 集合中自动更新。下面是完整的代码。

struct SampleClip: Identifiable, Hashable 
    
    var uid = UUID()
    var id :String
    var itemType:String?
    var clipTitle: String?
    var creationDate: Date?
    var clipAttr:NSAttributedString?


struct SampleClipset: Identifiable, Hashable 
    
    var id = UUID()
    var clipsetName :String
    var isEditAble:Bool

    init( clipsetName:String, isEditAble:Bool)
        self.clipsetName = clipsetName
        self.isEditAble = isEditAble
    


struct SampleClipItem: Identifiable, Hashable 
    var id = UUID()
    var clipsetObject: SampleClipset
    var clipObjects: [SampleClip]


class SampleDataModel: ObservableObject 
    @Published var dict:[SampleClipItem] = []
    
    @Published var selectedItem: SampleClipItem? 
        didSet 
            if self.selectedItem != nil 
                
                if( self.selectedItem!.clipObjects.count > 0)
                    self.selectedItemClip = self.selectedItem!.clipObjects[0]
                
            
        
    
    
    @Published var selectedItemClip: SampleClip? 
        didSet 
            if self.selectedItemClip != nil 
                
            
        
    


struct SampleApp: View 
    
    @ObservedObject var vm = SampleDataModel()
    @State var clipText = NSAttributedString(string: "Enter your text")
    
    var body: some View 
        VStack 
            //Button
            HStack
                //Clipset button
                VStack
                    Text("Add Parent data")
                        .padding(10)
                   
                    Button("Add") 
                        let clipset1 = SampleClipset(clipsetName: "Example clipset\(self.vm.dict.count)", isEditAble: false)
                        
                        var clip1 = SampleClip(id: "0", itemType: "", clipTitle: "Clip 1")
                        clip1.clipAttr = NSAttributedString(string: clip1.clipTitle!)
                        clip1.creationDate = Date()
                        
                        var clip2 = SampleClip(id: "1", itemType: "", clipTitle: "Clip 2")
                        clip2.clipAttr = NSAttributedString(string: clip2.clipTitle!)
                        clip2.creationDate = Date()
                       
                        let item = SampleClipItem(clipsetObject: clipset1, clipObjects: [clip1, clip2] )
                        self.vm.dict.append(item)
                    
                    
                    Button("Update") 
                        let index = self.vm.dict.count - 1
                        self.vm.dict[index].clipsetObject.clipsetName = "Modifying"
                    
                
               
                Divider()
                
                //Clip button
                VStack
                    Text("Add Child data")
                        .padding(10)
                   
                    Button("Add") 
                       
                       let object = self.vm.dict.firstIndex(of: self.vm.selectedItem!)
                       if( object != nil)
                           
                            let index = self.vm.selectedItem?.clipObjects.count
                            var clip1 = SampleClip(id: "\(index)", itemType: "", clipTitle: "Clip  \(index)")
                            clip1.clipAttr = NSAttributedString(string: clip1.clipTitle!)
                            clip1.creationDate = Date()
                            self.vm.dict[object!].clipObjects.append(clip1)
                            self.vm.selectedItem = self.vm.dict[object!]
                       
                    
                   
                    Button("Update") 
                        let index = (self.vm.selectedItem?.clipObjects.count)! - 1
                        self.vm.selectedItem?.clipObjects[index].clipAttr = NSAttributedString(string:"Modifying")
                       
                    
                
            .frame(height: 100)
            //End button frame
            
            //Start Column frame
            Divider()
            NavigationView
                HStack
                    
                    //Clipset list
                    List(selection: self.$vm.selectedItem)
                        ForEach(Array(self.vm.dict), id: \.self)  key in
                            Text("\(key.clipsetObject.clipsetName)...")
                        
                    
                    .frame(width:200)
                    .listStyle(SidebarListStyle())
                    
                    Divider()
                    VStack
                        //Clip list
                        if(self.vm.selectedItem?.clipObjects.count ?? 0 > 0)
                            List(selection: self.$vm.selectedItemClip)
                                ForEach(self.vm.selectedItem!.clipObjects, id: \.self)  key in
                                    Text("\(key.clipTitle!)...")
                                
                            
                            .frame(minWidth:200)
                        
                    
                    
                    //TextEditor
                    Divider()
                    
                    SampleTextEditor(text: self.$clipText)
                        .frame(minWidth: 300, minHeight: 300)
                
            
        
    


struct SampleApp_Previews: PreviewProvider 
    static var previews: some View 
        SampleApp()
    



//New TextView
struct SampleTextEditor: View, NSViewRepresentable 
    
    typealias Coordinator = SampleEditorCoordinator
    typealias NSViewType = NSScrollView

    let text : Binding<NSAttributedString>

    func makeNSView(context: NSViewRepresentableContext<SampleTextEditor>) -> SampleTextEditor.NSViewType 
        return context.coordinator.scrollView
    

    func updateNSView(_ nsView: NSScrollView, context: NSViewRepresentableContext<SampleTextEditor>) 
        if ( context.coordinator.textView.textStorage != text.wrappedValue)
            context.coordinator.textView.textStorage?.setAttributedString(text.wrappedValue)
        
    

    func makeCoordinator() -> SampleEditorCoordinator 
        let coordinator =  SampleEditorCoordinator(binding: text)
        return coordinator
    


class SampleEditorCoordinator : NSObject, NSTextViewDelegate 
    
    let textView: NSTextView;
    let scrollView : NSScrollView
    let text : Binding<NSAttributedString>

    init(binding: Binding<NSAttributedString>) 
        text = binding

        textView = NSTextView(frame: .zero)
        textView.autoresizingMask = [.height, .width]
        textView.textStorage?.setAttributedString(text.wrappedValue)
        textView.textColor = NSColor.textColor
        
        //Editor min code
        textView.isContinuousSpellCheckingEnabled = true
        textView.usesFontPanel = true
        textView.usesRuler = true
        textView.isRichText     = true
        textView.importsGraphics = true
        textView.usesInspectorBar = true
        textView.drawsBackground = true
        textView.allowsUndo = true
        textView.isRulerVisible = true
        textView.isEditable = true
        textView.isSelectable = true
        textView.backgroundColor = NSColor.white
        //
        scrollView = NSScrollView(frame: .zero)
        scrollView.hasVerticalScroller = true
        scrollView.autohidesScrollers = false
        scrollView.autoresizingMask = [.height, .width]
        scrollView.documentView = textView

        super.init()
        textView.delegate = self
    

    func textDidChange(_ notification: Notification) 
        
        switch  notification.name 
            case NSText.didChangeNotification :
                text.wrappedValue = (notification.object as? NSTextView)?.textStorage ?? NSAttributedString(string: "")
            default:
                print("Coordinator received unwanted notification")
                //os_log(.error, log: uiLog, "Coordinator received unwanted notification")
        
    


【问题讨论】:

【参考方案1】:

首先使用自定义Binding。

SampleTextEditor(text: Binding(get: 
    return self.vm.selectedItemClip?.clipAttr
, set: 
    self.vm.selectedItemClip?.clipAttr = $0
))

其次,更新您对子更新按钮的看法。

Button("Update") 
    guard let mainIndex = self.vm.dict.firstIndex(where:  (data) -> Bool in
        if let selectedId = self.vm.selectedItem?.id 
            return data.id == selectedId
        
        return false
    ),
    
    let subIndex = self.vm.dict[mainIndex].clipObjects.firstIndex(where:  (data) -> Bool in
        if let selectedId = self.vm.selectedItemClip?.id 
            return data.id == selectedId
        
        return false
    ),
    
    let obj = self.vm.selectedItemClip
    
    else 
        return
    
    
    self.vm.dict[mainIndex].clipObjects[subIndex] = obj
    self.vm.selectedItem = self.vm.dict[mainIndex]

在 SampleEditorCoordinator 类和 SampleTextEditor 结构中使用可选绑定。并更改您的 textDidChange 方法。

struct SampleTextEditor: View, NSViewRepresentable 
    
    typealias Coordinator = SampleEditorCoordinator
    typealias NSViewType = NSScrollView

    let text : Binding<NSAttributedString?>

    func makeNSView(context: NSViewRepresentableContext<SampleTextEditor>) -> SampleTextEditor.NSViewType 
        return context.coordinator.scrollView
    

    func updateNSView(_ nsView: NSScrollView, context: NSViewRepresentableContext<SampleTextEditor>) 
        if ( context.coordinator.textView.textStorage != text.wrappedValue)
            if let value = text.wrappedValue 
                context.coordinator.textView.textStorage?.setAttributedString(value)
            
        
    

    // Other code


class SampleEditorCoordinator : NSObject, NSTextViewDelegate 
    
    let textView: NSTextView;
    let scrollView : NSScrollView
    var text : Binding<NSAttributedString?>

    init(binding: Binding<NSAttributedString?>) 
        text = binding

        // Other code
    

    func textDidChange(_ notification: Notification) 
        
        switch  notification.name 
            case NSText.didChangeNotification :
                self.text.wrappedValue = NSAttributedString(attributedString: textView.attributedString())
            default:
                print("Coordinator received unwanted notification")
                //os_log(.error, log: uiLog, "Coordinator received unwanted notification")
        
    


【讨论】:

谢谢,但它没有更新收藏。 @MajidKhan 你需要按下更新按钮。 是的,我尝试按更新按钮,但应用程序崩溃了。测试 Xcode 12.3

以上是关于Swift UI 在集合中绑定 TextField的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Swift 5.5 中使用 List 中的新绑定?

Swift UI 委托方法不起作用

Swift 中的 Xcode:导航栏未显示在 UI 集合视图(模拟器)中

Swift UI集合视图 - 如何在Cell中显示“segued”用户输入?

Swift UI 集合视图布局

如何在swift ui中使用autoid将带有字段的新文档添加到firebase集合