I am working on an app running on a Mac using XCode 16.2..
One view is displaying a table with some person data.
The person might have several addresses. Because
SwiftUI can only display 10 columns as a maximum, (several) address data is displayed in one table column and row.
The table has a context menu, which will allow editing of the person data. On this editing page, the address list should
be rendered as a table.
In preview mode everything renders as expected, but when running the app, the edit page does not display the
table with the address data.
ContentView is displaying the Person Table. If one Person is selected, the Edit-Context-Menu is enabled. This should display the PersonTableEditor. Unfortunately this view is not complete. What am I doing wrong?
struct ContentView: View {
@State private var showingEditPage = false
@State private var selected: Person.ID?
@State var persons: [Person]
@State var sortOrder = [KeyPathComparator(\Person.id)]
var body: some View {
VStack {
Table(persons, selection: $selected, sortOrder: $sortOrder) {
TableColumn("Name", value: \.name).width(100.0)
TableColumn("Fistname", value: \.firstName ).width(100.0)
TableColumn("Birthday", value: \.birthDate) { pers in
Text(formatter.string(from: pers.birthDate))
}.width(100.0)
TableColumn("Addresses", value: \.addresses, comparator: KeyPathComparator(\[Address].description)) { pers in
ForEach(pers.addresses) { adr in
HStack {
Text(adr.street)
Text(adr.zip)
Text(adr.city)
}}
}
}
.contextMenu(forSelectionType: Person.ID.self) { _ in
Button("Edit ...") { showingEditPage = true}.disabled(selected == nil)
}
.sheet(isPresented: $showingEditPage) {
PersonTableEditor(person: selectedPerson()!).id(UUID())
}
}
.padding()
}
private func selectedPerson() -> Person? {
guard let person = persons.first(where: {return $0.id == selected}) else { return nil
}
return person
}
private let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
}
struct PersonTableEditor: View {
@State var showingEditPage = false
@State private var selected: Address.ID?
@State var person: Person
@State var sortOrder = [KeyPathComparator(\Address.id)]
@State var perferred = false
var body: some View {
VStack {
TextField("Name", text: $person.name)
TextField("First", text: $person.firstName)
DatePicker("Birthday", selection: $person.birthDate, displayedComponents: [.date])
Table(person.addresses, selection: $selected, sortOrder: $sortOrder) {
TableColumn("Preferred", value: \Address.preferred, comparator: BoolComparator()) { address in
Text(address.preferred ? "Yes" : "No")
}
TableColumn("Street", value: \.street)
TableColumn("Zip", value: \.zip)
TableColumn("City", value: \.city)
}.id(UUID())
.contextMenu(forSelectionType: Person.ID.self) { _ in
Button("Edit ...") { showingEditPage = true}.disabled(selected == nil)
}
.sheet(isPresented: $showingEditPage) {
AddressTableEditor(address: selectedAddress()!)
}
}
.padding()
}
private func selectedAddress() -> Address? {
guard let adrs = person.addresses.first(where: {return $0.id == selected}) else { return nil
}
return adrs
}
}
let persons = [
Person(name: "Meier", firstName: "Hans", birthDate: Date(), addresses: [
Address(street: "Mauernstr. 1", zip: "10000", city: "Berlin", preferred: false),
Address(street: "Hafenstr. 2", zip: "20000", city: "Hamburg", preferred: true)
])
]
I have tried several setups for the PersonTableEditor without any success. Is the an other way to provide an editing architecture?
Finally I have found a work around. Just append the .frame(height: 200.0)
modifier to the VStack with a value large enough to display the table. If the value is too small, the table won't be rendered.