iOS: List selection is reset to nil when app is sent to background

For some reason, a List will reset its selection to nil when the app is in the background.

Steps to reproduce the issue:

  • Run attached sample project
  • Once the app has launched, select a name in the sidebar
  • Move the app to the background
  • Wait a few seconds
  • Bring back the app to the foreground

Expected result:

The list selection should still be valid

Actual result:

The list selection is set to nil

Notes:

I’m using a StateObject, which should be the way to ensure that data isn’t regenerated when views are rendered. Is this a bug or something else needs to be taken care of?

class AppModel: ObservableObject {
    @Published var selectedPerson: Person?
}


@main

struct NilListSelectionApp: App {
    @StateObject var appModel = AppModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(appModel)
        }
    }
}
struct Person: Identifiable, Hashable {
    let id: UUID
    let firstname: String

    init(firstname: String) {
        id = UUID()
        self.firstname = firstname
    }
}


struct ContentView: View {
    @EnvironmentObject private var appModel: AppModel

    var body: some View {
        NavigationSplitView {
            SidebarView()
        } detail: {
            PersonView(person: appModel.selectedPerson)
        }
    }
}


struct SidebarView: View {
    @EnvironmentObject private var appModel: AppModel

    private let persons = [Person(firstname: "Joe"), Person(firstname: "Jane")]

    var body: some View {
        List(persons, id:\.self, selection: $appModel.selectedPerson) { person in
            Text(person.firstname).tag(person)
        }
        .listStyle(.sidebar)
    }
}


struct PersonView: View {
    let person: Person?

    var body: some View {
        if let person {
            Text(person.firstname)
        }
        else {
            Text("No Selection")
        }
    }
}
Post not yet marked as solved Up vote post of Lucky7 Down vote post of Lucky7
1.9k views

Replies

Apple folks: FB11876396

The first thing to check is whether your app is getting terminated while it's in the background (which is quite possible, since it's not doing anything then). If so, then it's going to be relaunched when it comes back to the foreground, and you'll need to use some kind of state restoration to put the UI back into the state it was:

https://developer.apple.com/documentation/uikit/view_controllers/preserving_your_app_s_ui_across_launches https://developer.apple.com/documentation/uikit/uiscenedelegate/restoring_your_app_s_state

  • I'm running the app in the debugger so the app is not being terminated. Once it is in the background, selectedPerson is set to nil but the call stack doesn't reveal anything unfortunately.

    I manage to "fix" this behaviour by saving the value when didEnterBackgroundNotification is triggered and setting it back when willEnterForegroundNotification is. This is more of a hack than an actual solution but it was just to demonstrate the issue.

  • Moreover, I always set selectedPerson to a default value at launch.

Add a Comment

I got the same problem. My situation is the code works fine on Mac and iPhone, no setting to nil on the background, but the problem occurs occasionally on the iPad (not every time, 1/3 I guess).

I "fix" this by the save the selection to a temp variable onChange of the @Environment(.scenePhase)

.onChange(of: scenePhase) { phase in
            if phase == .background{
                tempSelectedId = selectedId
            } else {
                if phase == .inactive && selectedId == nil{
                    selectedId = tempSelectedId
                    print("Restore - scenePhase")
                }
            }
        }
  • I used the same "fix" but this is of course just a temporary workaround.

Add a Comment

Having the same issue as described above.

Just wanted to add, I also have the same issue described above.

Same issue for me.

Strangely, this problem does not happen when "Requires full screen" is enabled.

But we want to avoid this approach...

Add a Comment

This code works for me

.onChange(of: sideBarSelection) { [sideBarSelection] newValue in
            if newValue == nil {
                self.sideBarSelection = sideBarSelection
            }
        }
  • This approach can fail when in compact mode (e.g. side by side with another app) because it does not allow you to navigate back to the side bar.

Add a Comment

The newest iOS17 beta fixes this bug.