Post not yet marked as solved
It seems like this may have been an issue for a while based on what I've seen, but I have added a toolbar item to a textfield keyboard and it doesn't show. The only way I can get it to show is by opening the keyboard, typing something, closing the keyboard, and then reopening it. Anyone have a workaround for this? It's like Apple purposely wants to make it difficult to close the keyboard.
TextField("Something here...", text: $text, axis: .vertical)
.multilineTextAlignment(.leading)
.toolbar {
ToolbarItemGroup(placement: .keyboard, content: {
Button("Close") { }
})
}
Post not yet marked as solved
Here is the view in which it works
struct MileageHistoryView: View {
let vehicle: Vehicle
init(for vehicle: Vehicle) {
self.vehicle = vehicle
}
@Environment(\.modelContext) private var context
@Environment(\.editMode) private var editMode
var sorted: [Mileage] {
guard let history = vehicle.mileageHistory else { return [] }
return history.sorted(by: { $0.timestamp > $1.timestamp })
}
var body: some View {
List {
ForEach(sorted) { mileage in
MileageListItem(mileage, editing: Binding(get: {editMode?.wrappedValue.isEditing ?? false}, set: {_ in }))
}
.onDelete(perform: deleteMileage)
.deleteDisabled(editMode?.wrappedValue.isEditing ?? false ? false : true)
}
.id(editMode?.wrappedValue.isEditing)
.navigationTitle("Mileage History")
.scrollContentBackground(.hidden)
.toolbar {
ToolbarItem(placement: .topBarTrailing, content: {
EditButton()
})
}
}
}
Here is the other view where it doesn't work. In this view, it seems like when the EditButton is pressed, no change is happening with the editMode so deleteDisabled() is always set to true.
struct VehiclesView: View {
@Environment(\.modelContext) private var context
@Environment(\.editMode) private var editMode
// Local
@Query private var vehicles: [Vehicle]
@State private var addVehicle = false
@AppStorage("vehicle-edit-alert") private var showEditAlert = true
@State private var editAlert = false
@State private var editShown = false
var body: some View {
NavigationStack {
List {
ForEach(vehicles) { vehicle in
NavigationLink(destination: VehicleView(vehicle), label: {
VehicleListItem(vehicle)
})
}
.onDelete(perform: deleteVehicle)
.deleteDisabled(self.editMode?.wrappedValue.isEditing ?? false ? false : true)
}
.id(self.editMode?.wrappedValue.isEditing)
.scrollContentBackground(.hidden)
.navigationTitle("Vehicles")
.toolbar {
ToolbarItem(placement: .topBarLeading, content: {
if showEditAlert && !editShown {
Button("Edit") { editAlert = true }
} else {
EditButton()
}
})
ToolbarItem(placement: .topBarTrailing, content: {
Button(action: { addVehicle.toggle() }, label: { Image(systemName: "plus") })
.accessibilityHint("Opens the view to add a Vehicle")
})
}
.fullScreenCover(isPresented: $addVehicle, content: {
VehicleEditor()
})
}
.scrollIndicators(.hidden)
}
}
When EditButton() is used in the second view the list item is grayed out, but the buttons to delete aren't there.
Does anybody know why this is happening?
The deletion is working, but it does not refresh the view. This is similar to a question I asked previously but I started a new test project to try and work this out.
@Model
class Transaction {
var timestamp: Date
var note: String
@Relationship(deleteRule: .cascade) var items: [Item]?
init(timestamp: Date, note: String, items: [Item]? = nil) {
self.timestamp = timestamp
self.note = note
self.items = items
}
func getModifierCount() -> Int {
guard let items = items else { return 0 }
return items.reduce(0, {result, item in
result + (item.modifiers?.count ?? 0)
})
}
}
@Model
class Item {
var timestamp: Date
var note: String
@Relationship(deleteRule: .nullify) var transaction: Transaction?
@Relationship(deleteRule: .noAction) var modifiers: [Modifier]?
init(timestamp: Date, note: String, transaction: Transaction? = nil, modifiers: [Modifier]? = nil) {
self.timestamp = timestamp
self.note = note
self.transaction = transaction
self.modifiers = modifiers
}
}
@Model
class Modifier {
var timestamp: Date
var value: Double
@Relationship(deleteRule: .nullify) var items: [Item]?
init(timestamp: Date, value: Double, items: [Item]? = nil) {
self.timestamp = timestamp
self.value = value
self.items = items
}
}
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query private var items: [Item]
@Query private var transactions: [Transaction]
@Query private var modifiers: [Modifier]
@State private var addItem = false
@State private var addTransaction = false
var body: some View {
NavigationStack {
List {
Section(content: {
ForEach(items) { item in
LabeledText(label: item.timestamp.formatAsString(), value: .int(item.modifiers?.count ?? -1))
}
.onDelete(perform: { indexSet in
withAnimation {
for index in indexSet {
context.delete(items[index])
}
}
})
}, header: {
LabeledView(label: "Items", view: {
Button("", systemImage: "plus", action: {})
})
})
Section(content: {
ForEach(modifiers) { modifier in
LabeledText(label: modifier.timestamp.formatAsString(), value: .currency(modifier.value))
}
.onDelete(perform: { indexSet in
indexSet.forEach { index in
context.delete(modifiers[index])
}
})
}, header: {
LabeledView(label: "Modifiers", view: {
Button("", systemImage: "plus", action: {})
})
})
Section(content: {
ForEach(transactions) { transaction in
LabeledText(label: transaction.note, value: .int(transaction.getModifierCount()))
}
.onDelete(perform: { indexSet in
withAnimation {
for index in indexSet {
context.delete(transactions[index])
}
}
})
}, header: {
LabeledView(label: "Transactions", view: {
Button("", systemImage: "plus", action: {addTransaction.toggle()})
})
})
}
.navigationTitle("Testing")
.sheet(isPresented: $addTransaction, content: {
TransactionEditor()
})
}
}
}
}
Here's the scenario. Create a transaction with 1 item. That item will contain 1 modifier. ContentView will display Items, Modifiers, and Transactions. For Item, it will display the date and how many modifiers it has. Modifier will display the date and its value. Transactions will display a date and how many modifiers are contained inside of its items.
When I delete a modifier, in this case the only one that exist, I should see the count update to 0 for both the Item and the Transaction. This is not happening unless I close the application and reopen it. If I do that, it's updated to 0. I tried to add an ID variable to the view and change it to force a refresh, but it's not updating.
This issue also seems to be only with this many to many relationship. Previously, I only had the Transaction and Item models. Deleting an Item would correctly update Transaction, but that was a one to many relationship.
I would like for Modifier to have a many to many relationship with Items, so they can be reused.
Why is deleting a modifier not updating the items correctly? Why is this not refreshing the view? How can I resolve this issue?
Post not yet marked as solved
I have a TabView which consists of a few different tabs. One of which does an @Query to retrieve an array of Transaction models. These are then displayed in a list using a ForEach.
struct TransactionsTab: View {
@Query private var transactions: [Transaction]
... other code
Section(content: {
ForEach(transactions) { transaction in
transaction.getListItem()
}
}, header: {
LabeledView(label: "Recent Transactions", view: {
ListButton(mode: .link(destination: {
ListView(list: transactions)
.navigationTitle("All Transactions")
}))
})
})
Transaction contains a different model called TransactionItem and that has a variable called amount. That amount variable is used in the getListItem() function to show how much the total transaction was in the list item.
The issue is that I can delete a Transaction and the ForEach will update to reflect that. However, if I delete an TransactionItem separately, that getListItem() will not show that it's been deleted. The total amount shown will still be as if the TransactionItem was never deleted. It will only update when the app is closed and reopened. Below is the code that's ran when deleting a model, in this case a TransactionItem.
// Deletes a single item
private func delete() {
deleteWarning = false
if let item = itemToDelete {
// If last item is being delete, dismiss the view
if list.count == 1 { dismissView() }
context.delete(item)
context.saveContext()
itemToDelete = nil
}
mode = .view
}
I would think that deleting the model and having it save will cause the transaction query to update. What's going on here to cause it to not update?
By the way, saveContext() just calls the ModelContext save function.
extension ModelContext {
func saveContext() {
do {
try self.save()
} catch {
print("Could not save context: \(error.localizedDescription)")
}
}
}
Post not yet marked as solved
Hello. See the code below.
struct ContentView: View {
var body: some View {
TabView {
VehicleTab()
.tabItem({ Label("Vehicles", systemImage: "car.fill")})
.modelContainer(for: Vehicle.self)
TransactionsTab()
.tabItem { Label("Transactions", systemImage: "dollarsign") }
.modelContainer(for: Transaction.self)
}
}
}
Using the .modelContainer() in this way seems to be causing some issue. I was under the assumption that this would just create a container for each view. I get the error below in this configuration. If I comment out either one of the .modelContainer() modifiers, it works fine.
Query encountered an error: Error Domain=NSCocoaErrorDomain Code=256 "The file “default.store” couldn’t be opened."
Are you not able to do what I'm doing? Is there a way to have two separate containers?
Post not yet marked as solved
Hello. I had an issue that I was able to resolve, but I'd like to see if anyone might know why it happened in the first place. Essentially, I have a SwiftData model called Vehicle. This model has an array of Mileage, another SwiftData model. Vehicle has an init that accepts a Vehicle as a parameter and then matches it with that one.
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
}
Previously, I had the line self.mileageHistory = vehicle.mileageHistory in this init. However, that line caused a duplicate Vehicle model to be created and inserted into the context. I could tell because I had a List that was displaying all of the created Vehicle models from a Query.
It all stems from a view that is being displayed in a sheet. This view accepts a vehicle parameter called copy.
.sheet(isPresented: $edit, content: { VehicleEditView(vehicle: vehicle, copy: Vehicle(from: vehicle)) })
In a way I can understand why it's happening because a new Vehicle model is being created. But I don't understand why it only happens when the mileageHistory variables are set equal to each other. I removed that line and it doesn't do it anymore. I had to workaround it by setting the mileageHistory elsewhere.
Does anyone know why this might be happening?