Issue #605

I use custom TextView in a master detail application.

import SwiftUI

struct TextView: NSViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeNSView(context: Context) -> NSTextView {
        let textView = NSTextView()
        textView.delegate = context.coordinator
        return textView
    }

    func updateNSView(_ nsView: NSTextView, context: Context) {
        guard nsView.string != text else { return }
        nsView.string = text
    }

    class Coordinator: NSObject, NSTextViewDelegate {
        let parent: TextView

        init(_ textView: TextView) {
            self.parent = textView
        }

        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else { return }
            self.parent.text = textView.string
        }
    }
}

No matter which item user selects, textView always updates the first one

struct Book {
   var name: String = ""
}

class Store: ObservableObject {
    @Published var books: [Book] = []
}

struct MainView: View {
    @EnvironmentObject var store: Store

    var body: some View {
        List {
            ForEach(store.books.enumerated().map({ $0 }), id: \.element.id) { index, book in {
                Text(book.name)
                .onTapGesture {
                    self.store.selectedIndex = index
                }
            }
        }

        HStack {
            TextView($store.books[store.selectedIndex].name)
        }
    }
}

The fix is to pass selected object instead of using subscript

struct MainView: View {
    @EnvironmentObject var store: Store

    var body: some View {
        List {
            ForEach(store.books.enumerated().map({ $0 }), id: \.element.id) { index, book in {
                Text(book.name)
                .onTapGesture {
                    self.store.selectedBook = self.store.books[index]
                }
            }
        }

        HStack {
            TextView($store.selectedBook.name)
        }
    }
}

And we need to save selectedBook

class Store: ObservableObject {
    @Published var books: [Book] = []

    @Published var selectedBook: Book = Book(name: "") {
        didSet {
            saveSelected()
        }
    }

    func saveSelected() {
        guard let index = self.books.firstIndex(where: { $0.id == selectedBook.id }) else {
            return
        }

        books[index] = selectedBook
    }
}

Read more


Updated at 2020-06-01 02:13:51