Wak
Wak

Reputation: 878

Unselecting an item on a SwiftUI list crashes the app

I’m having an issue with SwiftUI with optional bindings, basically it’s a List on macOS, where I add a DetailView once an item is selected, if not selected just add a Text.

When I open the app it’s fine, the Text appears, then I add some items and select it, also works fine, DetailView appears, but once I click outside the table, unselecting it, it crashes. Even tough I have a conditional checking for nil, that’s why it works the first time.

I guess the DetailView is keeping a reference to the selectedItem and crashing once it’s set to nil, but I thought the entire body should be refreshed once a State property is changed, which would remove the previous DetailView from memory and not call a new one, right?

Here's the code:

import SwiftUI

struct DetailView: View {
    @Binding var text: String

    var body: some View {
        TextField("123", text: self.$text)
    }
}

struct ContentView: View {
    @State var text = ""
    @State var items = [String]()
    @State var selectedItem: String? = nil

    var body: some View {
        VStack {
            HStack {
                VStack(alignment: .leading, spacing: 0) {
                    List(selection: $selectedItem) {
                        ForEach(items, id: \.self) { item in
                            Text(item)
                        }
                    }
                    HStack(spacing: 0) {
                        Button(action: {
                            self.items.append(UUID().uuidString)
                        }, label: {
                            Text("Add")
                        })
                        Button(action: {
                            if let item = self.selectedItem {
                                self.items.remove(at: self.items.firstIndex(of: item)!)
                            }
                            self.selectedItem = nil
                        }, label: {
                            Text("Remove")
                        }).disabled(selectedItem == nil)
                    }
                }
                if selectedItem != nil {
                    DetailView(text: Binding($selectedItem)!)
                } else {
                    Text("Add an item")
                }
            }
            .tabItem {
                Text("Test")
            }
        }.frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

Upvotes: 1

Views: 429

Answers (1)

Asperi
Asperi

Reputation: 257889

I guess the DetailView is keeping a reference to the selectedItem and crashing once it’s set to nil, but I thought the entire body should be refreshed once a State property is changed, which would remove the previous DetailView from memory and not call a new one, right?

The order of update is not defined, so I would answer no on above.

Here is a solution. Tested with Xcode 11.4 / iOS 13.4

if selectedItem != nil {
    DetailView(text: Binding(get: {self.selectedItem ?? ""}, 
                             set: {self.selectedItem = $0}))
} else {
    Text("Add an item")
}

Upvotes: 1

Related Questions