I've got a strange situation and I'm not sure why it's happening.
I have a list view that is supposed to present its contents sorted/filtered by a number of criteria. So basically, each choice is really a different subview:
struct LibraryView: View {
@State private var sorting: Sorting = .titleAscending
@State private var filtering: Filtering = .none
@EnvironmentObject var database: LibraryDatabase
var body: some View {
Group {
switch (filtering, sorting) {
// MARK: - Sorted, no filter
case (.none, .titleAscending): sectionedList(books: database.books)
case (.none, .author): bookListSectionedByAuthor(authors: database.authors)
// etc etc
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu(content: {
Picker("Filter by...", selection: $filtering) {
ForEach(Filtering.allCases, id: \.self) { option in
HStack {
Text(option.rawValue)
Image(systemName: option.image)
}
}
}
},
label: {
Image(systemName: "line.horizontal.3.decrease")
})
}
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Picker("Sort by...", selection: $sorting) {
ForEach(Sorting.allCases, id: \.self) { option in
HStack {
Text(option.rawValue)
Image(systemName: option.image)
}
}
}
}
}
}
}
}
The first option in the switch above work as it should:
The second, however, does this:
I really don't understand why that's happening. It was working properly when I had the code as part of the Sorting
enum, as seen here:
func view(_ database: LibraryDatabase) -> some View {
return Group {
switch self {
case .titleAscending: titleListView(database)
case .author: authorListView(database)
}
}
}
private func authorListView(_ database: LibraryDatabase) -> some View {
let authors = database.authors.sorted(by: { $0.description < $1.description })
return List {
ForEach(authors) { author in
Section(header: Text(author.description)) {
ForEach(author.books.sorted(by: {$0.title < $1.title})) { book in
BookRowView(book)
}
}
}
}
.navigationTitle("Authors")
.listStyle(InsetListStyle())
}
The only thing that changed was that I added extensions to get the sorting/filtering code out of the functions that return views, so that those functions were a lot cleaner:
extension Array where Element == Audiobook {
var sorted: [Audiobook] {
self.sorted(by: {$0.title < $1.title})
}
}
extension Array where Element == Author {
var sorted: [Author] {
self.sorted(by: {(SortableOptional(optional: $0.name.familyName), SortableOptional(optional: $0.name.givenName)) <
(SortableOptional(optional: $1.name.familyName), SortableOptional(optional: $1.name.givenName))})
}
}
and then I moved the functions to an extension of the view itself instead of having them in the enum:
extension LibraryView {
/// A view that presented the array of books as a list in ascending order by title
///
/// If the array of books contains less than 20 entries, the list will be unsectioned.
///
/// Otherwise, there will be one section for each first character
func sectionedList(books: [Audiobook]) -> some View {
List {
if books.count <= 20 {
ForEach(books.sorted) { book in
BookRowView(book)
}
} else {
ForEach(books.firstCharacters) { char in
Section(header: Text(String(char))) {
ForEach(books.filteredByFirstCharacter(char).sorted) { book in
BookRowView(book)
}
}
}
}
}
.listStyle(InsetListStyle())
}
/// Presents a list of books sectioned by author
func bookListSectionedByAuthor(authors: [Author]) -> some View {
List(authors) { author in
Section(header: Text(author.description)) {
ForEach(author.books.sorted) { book in
BookRowView(book)
}
}
}
.listStyle(InsetListStyle())
}
}
I don't see why it would be presenting the list horizonally instead of vertically in the second sorting option.