Here is part of the Vehicle structure. I've left out some unrelated things like various functions.
@Model
final class Vehicle: Equatable, ContainsListItem {
var id: UUID
// Other variables here...
var mileageHistory: [Mileage] = []
Below is what the init look like when it has an issue.
init(from vehicle: Vehicle) {
self.id = vehicle.id
self.timestamp = vehicle.timestamp
self.year = vehicle.year
self.make = vehicle.make
self.model = vehicle.model
self.trim = vehicle.trim
self.mileage = vehicle.mileage
self.nickname = vehicle.nickname
self.mileageHistory = vehicle.mileageHistory
}
As a reminder, if you remove that last line in the init everything will work fine. If that line is there, a copy of Vehicle is being created and inserted into the context without me explicitly doing that.
Post
Replies
Boosts
Views
Activity
I've found through further testing that the relationship between Modifier and Item is not being made until the app is closed and reopened. For example, lets say I create the Transaction, Item, and Modifier, then add the Modifier to the Item. When I get back to ContentView, that Modifier model does not contain anything inside of its items relationship. If I close and reopen the application, Modifier will then have a relationship with the Item is was added to. I can then delete the Modifier, and the counts are set to 0 as expected. I found this through using this function I created.
extension ModelContext {
func deleteModifier(_ modifier: Modifier) {
var itemsDescriptor = FetchDescriptor<Item>()
var savedItems = try? self.fetch(itemsDescriptor)
if var savedItems {
if let modifierItems = modifier.items {
for item in modifierItems {
if let index = item.modifiers!.firstIndex(where: {$0.persistentModelID == modifier.persistentModelID}) {
savedItems.remove(at: index)
}
}
}
}
self.delete(modifier)
}
}
I put stop points after if var savedItems and I could see that modifierItems count was 0 after the initial creation of the Transaction. After closing and reopening the app, modifierItems.count was 1 as expected.
Is there a way to force the relationship?
Well, the issue seems to be that I'm not inserting the Modifier or Item into the context prior to creating a Transaction. I would only append the Modifier to the array in Item, and then I would append the Item to the array in Transaction. Once the Transaction was created, it would insert the Transaction into the context. I assumed that would insert all relevant models but I guess not.
I recall having an issue with this, because I don't want to insert the Modifier until the Transaction has been created. I believe I tried to use context.rollback() in a different application, however, it doesn't work because the Modifier is being inserted and saved. At the moment, this seems to be working for me.
First, I had to manually set a different modelContainer for the view that allows you to create a Transaction, and then disable auto-save.
.sheet(isPresented: $addTransaction, content: {
TransactionEditor()
.modelContainer(for: Transaction.self, isAutosaveEnabled: false)
})
I tested this, and it creates the models without any issues, but of course they don't get saved. So when you view ContentView(), nothing is there.
Second, in the my other editors (ItemEditor and ModifierEditor), I insert those models into the context before appending them to their specific arrays. Here is code from ModifierEditor(). This context of course being the one attached to TransactionEditor() since ModifierEditor() is in that same stack.
Button("Add") {
let modifier = Modifier(timestamp: timestamp, value: value)
context.insert(modifier)
modifiers.append(modifier)
dismiss()
}
Then, in my TransactionEditor in the button that is creating the Transaction, I did this.
Section {
Button("Add") {
let transaction = Transaction(timestamp: timestamp, note: note, items: items)
context.insert(transaction)
do {
try context.save()
} catch {
print("Could not save transaction: \(error.localizedDescription)")
}
dismiss()
}
}
That will save the transaction, and it successfully makes all relationships without having to close to reopen the application. I don't fully understand why I'm not able to only insert the Transaction. When I did it that way, it would make the relationship to the Item successfully, just not the Modifier to Item relationship. Anyways, I'm glad it seems to be working now.
I figured it out. You have to use mapCameraKeyframeAnimator().
@State private var centerCoordinate = CLLocationCoordinate2D(latitude: 38.9072, longitude: -77.0369)
@State private var distance: CLLocationDistance = 1000000
@State private var triggerCamera = false
Map(initialPosition: .camera(MapCamera(centerCoordinate: centerCoordinate, distance: distance))) {
}
.frame(height: geo.size.height * 0.60)
.shadow(color: .black.opacity(0.5), radius: 1, y: 1)
.onReceive(locationManager.$location, perform: { location in
if let location {
centerCoordinate = location
triggerCamera = true
}
})
.mapCameraKeyframeAnimator(trigger: triggerCamera, keyframes: { camera in
KeyframeTrack(\MapCamera.centerCoordinate, content: {
LinearKeyframe(centerCoordinate, duration: 1)
})
KeyframeTrack(\MapCamera.distance, content: {
LinearKeyframe(300, duration: 1)
})
})
Updating the trigger to true will start the animation, which moves to the provided location.
Well I figured out the issue and it wasn't related to TransactionCategory at all. When using CloudKit you have to use optional relationships with default values. Apparently doing this: var tags: [Tag]? = nil is not acceptable. You have to use an empty string as the default value.
Did you ever figure out exactly what was causing it, or could you explain how your new approach was different? I'm having the same issue.
I had the same issue as the OP. In my case, my chart data was being recomputed when you selected a sector: .chartAngleSelection(value: $selectedCount). It was because my chart data was a computed property based on other data. I changed it so that the chart data was set only once in the view initializer since I didn't need that data to update dynamically.
I seem to be having a similar issue. My app would lag or glitch a bit if I scrolled down immediately after the app launched. I thought it was the way I was calculating some data and presenting it, but I realized that it stopped once CloudKit stopped outputting stuff to the console. If I wait for that to stop, it doesn't lag. If I disconnect my phone from a network, so it can't sync, it doesn't lag.
The answer from @Claude31 solved it, but here is the additional code I added.
First I created a new variable to track what's being selected.
@State private var selectedCycle: Cycle? = nil
Then I watched for a change on the selectedID.
.onChange(of: selectedID, {
if let id = selectedID.first {
selectedCycle = cycles.first(where: {$0.id == id})
}
})
Then I added the onDisappear in my navigationDestination to clear what's selected.
.navigationDestination(item: $selectedCycle, destination: { cycle in
CycleDetailViewMac(cycle: cycle)
.onDisappear(perform: {
selectedID.removeAll()
})
})
I figured out a way to do what I want. To summarize it:
Hide the navigation bar back button
Create a toolbar item to work as the new back button
Have it's action close your view
You can't use dismiss because it will get rid of whatever view is in the detail section of your NavigationSplitView. Instead, you must pass the variable being used to present the new view, and then set it back to false.
Lastly, your view that's equivalent to Content() needs a primaryAction toolbar item. Otherwise, the placement of toolbar items won't appear right, because it will be based on the entire toolbar and not the portion above Detail().
struct Sidebar: View {
var body: some View {
NavigationLink("Sidebar button", destination: {
Content()
})
}
}
struct Content: View {
var body: some View {
NavigationLink("Content button", destination: {
Detail()
})
.toolbar {
ToolbarItem(placement: .primaryAction, content: {
Button("Add") {
}
})
}
}
}
struct Detail: View {
@State private var showOtherView = false
var body: some View {
NavigationStack {
Button("Other view") {
showOtherView.toggle()
}
.navigationDestination(isPresented: $showOtherView, destination: {
OtherView(presenting: $showOtherView)
})
}
}
}
struct OtherView: View {
@Binding var presenting: Bool
var body: some View {
Text("More details here")
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .destructiveAction, content: {
Button(action: {
presenting = false
}, label: {
Image(systemName: "chevron.left")
.foregroundStyle(.secondary)
.font(.title2)
.frame(width: 20)
})
.buttonStyle(.automatic)
})
ToolbarItem(placement: .principal, content: {
Text("View Title")
.font(.title3)
})
}
}
}
#if os(macOS)
struct TestView: View {
var body: some View {
NavigationSplitView(sidebar: {
Sidebar()
}, content: {
}, detail: {
})
}
}
#endif
Here's a photo of what it looks like.
Using the standard behavior of the NavigationSplitView works fine, but I prefer the look and feel of this better.