I noticed a problem with the NavigationCookbook sample code. The builtInRecipes array containing Recipe models without IDs which means state restoration fails because the recipes have new unique IDs every time the app is launched (there is a let id = UUID() in Recipe). The problem is in NavigationCookbook/Models/DataModel
private let builtInRecipes: [Recipe] = {
var recipes = [
"Apple Pie": Recipe(
name: "Apple Pie", category: .dessert,
ingredients: applePie.ingredients),
Should be
let builtInRecipes: [Recipe] = {
var recipes = [
"Apple Pie": Recipe(
id: UUID(uuidString: "E35A5C9C-F1EA-4B3D-9980-E2240B363AC8")!,
name: "Apple Pie", category: .dessert,
ingredients: Ingredient.fromLines(applePie)),
And the same thing for all the other built-in recipes in the array. The builtInRecipes containing ids can be found in the Code tab in the Developer app for this samples WWDC session video:
https://developer.apple.com/wwdc22/10054
I also submitted this as feedback FB11744612
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
If they had used .task they could have removed the class LocationsHandler: ObservableObject and simply done:
struct ContentView: View {
@State var lastLocation: CLLocation?
var body: some View {
VStack {
...
}
.task {
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
if let loc = update.location {
self.lastLocation = loc
}
}
}
And saved themselves about 20 or so lines of code. .task was added in the year before so it isn't the case that it wasn't available to the CoreLocation team yet. To wrap async/await in a Combine's ObservableObject is very strange. They could have also used @AppStorage instead of UserDefaults and saved another few lines. To be honest this is some of the strangest SwiftUI code I've seen.
I was wondering if anyone knows why the sample project uses Task.detached everywhere because it seems highly non-standard, e.g. in ContentView:
.task {
Task.detached { @MainActor in
await flightData.load()
}
}
Instead, I would expect to see something like:
.task {
flightData = await controller.loadFlightData()
}
Or:
.task {
await controller.load(flightData: flightData)
}
Is the use of detached perhaps an attempt to work around some issue with ObservableObject published updates?
Could an Apple employee that works on SwiftUI please explain the update() func in the DynamicProperty protocol? The docs have ambiguous information, e.g.
"Updates the underlying value of the stored value."
and
"SwiftUI calls this function before rendering a view’s body to ensure the view has the most recent value."
From: https://developer.apple.com/documentation/swiftui/dynamicproperty/update()
How can it both set the underlying value and get the most recent value? What does underlying value mean? What does stored value mean?
E.g. Is the code below correct?
struct MyProperty: DynamicProperty {
var x = 0
mutating func update() {
// get x from external storage
x = storage.loadX()
}
}
Or should it be:
struct MyProperty: DynamicProperty {
let x: Int
init(x: Int) {
self.x = x
}
func update() {
// set x on external storage
storage.save(x: x)
}
}
This has always been a mystery to me because of the ambigious docs so thought it was time to post a question.
Topic:
UI Frameworks
SubTopic:
SwiftUI
I noticed if I show a sheet from a List row, then remove the row the sheet isn't removed from the screen like it is if using VStack or LazyVStack.
I'd be interested to know the reason why the sheet isn't removed from the screen in the code below. It only occurs with List/Form. VStack/LazyVStack gives the expected result. I was wondering if it is an implementation issue, e.g. since List is backed by UICollectionView maybe the cells can't be the presenter of the sheet for some reason.
Launch on iPhone 16 Pro Simulator iOS 18.2
Tap "Show Button"
Tap "Show Sheet"
What is expected:
The sheet should disappear after 5 seconds. And I don't mean it should dismiss, I just mean removed from the screen. Similarly if the View that showed the sheet was re-added and its show @State was still true, then the sheet would be added back to the screen instantly without presentation animation.
What actually happens:
Sheet remains on screen despite the row that presented the sheet being removed.
Xcode 16.2
iOS Simulator 18.2.
struct ContentView: View {
@State var showButton = false
var body: some View {
Button("\(showButton ? "Hide" : "Show" ) Button") {
showButton = true
Task {
try? await Task.sleep(for: .seconds(5))
self.showButton = false
}
}
//LazyVStack { // does not have this problem
List {
if showButton {
SheetButton()
}
}
}
}
struct SheetButton: View {
@State var sheet = false
@State var counter = 0
var body: some View {
Text(counter, format: .number)
Button("\(sheet ? "Hide" : "Show") Sheet") {
counter += 1
sheet.toggle()
}
.sheet(isPresented: $sheet) {
Text("Wait... This should auto-hide in 5 secs. Does not with List but does with LazyVStack.")
Button("Hide") {
sheet = false
}
.presentationDetents([.fraction(0.3)])
}
// .onDisappear { sheet = false } // workaround
}
}
I can work around the problem with .onDisappear { sheet = false } but I would prefer the behaviour to be consistent across the container controls.