简体   繁体   中英

Swift UI Binding TextField in Collection

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM