Strange behavior of SwiftUI TextField (MacOS)

Viewed 541

I'm experiencing a strange behaviour of TextFields using SwiftUI on a MacOS application (XCode 11.0 stable, CoreData), probably due to some misconceptions on my side regarding SwiftUI dataflow.

Implementation

Please consider this basic Master/Detail View setup:

  • Model: Account
  • Container: ContentView
  • Master: AccountListView
  • Detail: AccountEditView
final class Account: NSManagedObject, Identifiable {
            @NSManaged var id: UUID
            @NSManaged var name: String
        }

struct ContentView: View {
    var body: some View {
        NavigationView {
            AccountListView()
                .listStyle(SidebarListStyle())
        }
        .navigationViewStyle(DoubleColumnNavigationViewStyle())
        .frame(minWidth: 300, idealWidth: nil, maxWidth: nil, minHeight: 300, idealHeight: nil, maxHeight: nil, alignment: .topLeading)
    }
}

struct AccountListView: View {

    @FetchRequest(entity: Account.entity(), sortDescriptors: [])
    var accounts: FetchedResults<Account>

    var body: some View {
        VStack {
            List(accounts) { account in
                NavigationLink (destination: AccountEditView(account: account)) {
                    Text(account.name)
                }
            }
        }
    }
}

struct AccountEditView: View {

    @ObservedObject var account: Account

    var body: some View {
        VStack {
            TextField("Account Name", text: $account.name)
            Spacer()
        }
    }
}

Issue

The views are populated correctly. Selecting an account in list view properly updates the textfield in detail view.

However as soon as I edit the textfield value, things go strange:

  • When the detail view is newly updated, the textfield loses focus after the first entered character. The account name in list view is properly updated (showing the single character entered)
  • When I'm entering the textfied again, I can type more characters without losing focus (again, the account name in the list view is update)
  • As soon as I press enter, the TextField's value restores to the old value, while the list view's value remains at the updated value.
  • When switching back and forth between different account entries, the detail view shows the correct values. So the model object was properly updated.

My questions:

  1. Why does the TextField lose focus when entering a single character for the first time
  2. Why does the TextField restore it's previous value (and not the model value) when commiting the change

Sorry for the lengthy question...

1 Answers

The coredata attributes are a different kind of optionals. So we need to define getter and setter inside the Model class to convert their behavior.

If your Entity's code gen is not manual/none, add this following code in contentview

extension Account {
    var wrappedName: String {
        get { name ?? "Unknown"}
        set { name = newValue }
    }
}

then replace all the account.name with account.wrappedName

P.S. This is tested and worked in XCode 11.4 on MacOS 10.5

Related