Background
I want a view that will allow the user to create a new core data item. That item will have a link to another core data item which the user will select from a picker which will be pre-populated with a default item selected when the view first loads (the most relevent one for that user).
## Problem
But when I change the pickers "selection" variable during the .onAppear (in the actual code it's a modified version on .onAppear that only runs the code the first time .onAppear shows but that's good enough for this question) it doesn't change the picker's selected value.
If I then change it again using say a button that runs the same selection code it DOES change the picker.
Also if I add something that references the picker's selection var (which is a @State. See example code) it works perfectly!
How to replicate the issue
To replicate the issue create a new project in Xcode (14.3), select App
and tick Use Core Data
.
Now replace the whole of the ContentView
with the following code:
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: []
) var items: FetchedResults<Item>
@State private var selectedItem: Item?
var body: some View {
Form {
// ---------------------------------------------
// Uncommenting the next line fixes the pickers!
//let _ = selectedItem
// ---------------------------------------------
Picker ("Part of Item Session", selection: $selectedItem) {
Text("New Item").tag(nil as Item?)
ForEach(items) { item in
Text("\(item.timestamp!.description)").tag(item as Item?)
}
}
Button {
ContentView.addItems(withViewContext: viewContext)
} label: {
Text("Add Items")
}
Button {
selectedItem = ContentView.getItem(withViewContext: viewContext)
} label: {
Text("Select Random Item")
}
Button {
if (selectedItem != nil) {
print("Item: \(String(describing: selectedItem!.timestamp))")
} else {
print("Item is nil!")
}
} label: {
Text("View Item (print to console)")
}
}
.onAppear {
selectedItem = ContentView.getItem(withViewContext: viewContext)
if (selectedItem != nil) {
print("Item: \(String(describing: selectedItem!.timestamp))")
} else {
print("Item is nil!")
}
}
}
static func addItems(withViewContext viewContext: NSManagedObjectContext) {
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date().addingTimeInterval(-Double(Int.random(in: 1..<5000)))
}
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
static func getItem(withViewContext viewContext: NSManagedObjectContext) -> Item? {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
fetchRequest.sortDescriptors = []
var items: [Item] = []
do {
// Fetch
items = try viewContext.fetch(fetchRequest)
// Pick a random(ish) item
if (items.count > 0) {
let randomInt = Int.random(in: 0..<items.count)
print("Setting Item: '\(items[randomInt].timestamp!.description)'")
// Return the random(ish) item
return items[randomInt]
}
} catch {
print("Unable to Fetch Item, (\(error))")
}
return nil
}
}
Then:
- Running the project in an
iPhone 14 Pro Max
simulator - Click the
Add Items
button to add some examples to your DB - Re-run the app in the simulator from Xcode to re-start it and you'll see the picker still has
New Item
selected but if you click theView Item ...
button it'll print the selection's actual value which is notnil
(the value forNew Item
)! - If you change the selection use
Select Random Item
(which just picks another random item for the picker) it will correctly change the Picker correctly.
Also if you:
- Restart the app again
- Click the
View Item
button - Select the same item from the picker that was printed in the console it won't change the picker.
- IF however you change the picker to any other item it WILL change the picker!
Strange hack that fixes it
To see the picker working like I expected it to un-comment the line let _ = selectedItem
(line 17) and re-run the app... Now right away the picker is correct!
Question
What's going on here. Is it a bug in Swift or am I doing something wrong?
let _ = selectedItem
. I am curious as to why you are fetchingselectedItem
from the DB when you already have all of the items fetched withitems
? That seems redundant. – Thrips