Swift UI Binding TextField in Collection

╄→гoц情女王★ 提交于 2021-02-11 12:31:32

问题


I have two columns with nested data(Parent/child). Each item in first column is parent. When selecting anyone of them then it shows its child in second column as list.

When selecting any item from second column then it must show "clipAttr" attribute in third column as text editor where we can edit it.

Now I need help how to do that when edit the 'ClipAttr' then it automatically update in SampleDataModel collection. Below is the complete code.

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:


First use custom Binding.

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

Second, update your view on child update button.

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]
}

Inside the SampleEditorCoordinator class and SampleTextEditor struct use optional binding. And change your textDidChange methods.

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")
        }
    }
}



来源:https://stackoverflow.com/questions/65900982/swift-ui-binding-textfield-in-collection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!