Kyle Carroll
Kyle Carroll

Reputation: 97

Why is my SwiftUI List selection not highlighting when selected?

I know the code is not the prettiest, I'm still learning ways to condense my code and reduce the amount of lines needed for things such as the way I have for sorting my list. However, I do need help trying to figure out why this list is not showing a highlight over whatever row is selected. For reference, I put a print statement to verify taps are being registered on the rows, and it is working fine.

Some further info: the "onDelete" action for the row is deleting the wrong row. Perhaps this has something to do with the selection? Any advice would be great.

Current State

//
//  TableView.swift
//  TableDemo (iOS)
//
//  Created by Kyle Carroll on 7/20/21.
//
//TODO: Delete at "index set" is deleting wrong row
//TODO: Row selection not working

import SwiftUI

struct TableView: View {

@StateObject var bookStore : BookStore = BookStore(books:bookData)
@State var sortBy: String = ""
@State var IDbuttonStatus: IDButtonStatus = .firstPress
@State var titleButtonStatus: TitleButtonStatus = .firstPress
@State var authorButtonStatus: AuthorButtonStatus = .firstPress
@State var pagesButtonStatus: PagesButtonStatus = .firstPress

    
enum IDButtonStatus {
    case secondPress
    case firstPress
    
    var nextStatus: IDButtonStatus {
        switch self {
        case .firstPress: return .secondPress
        case .secondPress: return .firstPress
        }
    }
    var order: String {
        switch self {
        case .firstPress: return "id2"
        case .secondPress: return "id"
        }
    }
}
    
enum TitleButtonStatus {
    case firstPress
    case secondPress
    
    var nextStatus: TitleButtonStatus {
        switch self {
        case .firstPress: return .secondPress
        case .secondPress: return .firstPress
        }
    }
    var order: String {
        switch self {
        case .firstPress: return "title2"
        case .secondPress: return "title"
        }
    }
}
    
enum AuthorButtonStatus {
    case firstPress
    case secondPress
    
    var nextStatus: AuthorButtonStatus {
        switch self {
        case .firstPress: return .secondPress
        case .secondPress: return .firstPress
        }
    }
    var order: String {
        switch self {
        case .firstPress: return "author2"
        case .secondPress: return "author"
        }
    }
}
    
enum PagesButtonStatus {
    case firstPress
    case secondPress
    
    var nextStatus: PagesButtonStatus {
        switch self {
        case .firstPress: return .secondPress
        case .secondPress: return .firstPress
        }
    }
    var order: String {
        switch self {
        case .firstPress: return "pages2"
        case .secondPress: return "pages1"
        }
    }
}
    

var body: some View {
    VStack {
        HStack(alignment: .center, spacing: nil) {
            HStack {
                Text("ID")
                Button {
                    IDbuttonStatus = IDbuttonStatus.nextStatus
                    self.sortBy = IDbuttonStatus.order
                } label: {
                    Image(systemName: "chevron.up.chevron.down")
                        .foregroundColor(Color.black)
                }
                }
            .frame(maxWidth: 50, alignment: .leading)
            Divider()
            HStack {
                Text("Title")
                Button {
                    titleButtonStatus = titleButtonStatus.nextStatus
                    self.sortBy = titleButtonStatus.order
                } label: {
                    Image(systemName: "chevron.up.chevron.down")
                        .foregroundColor(Color.black)
                }
                }
            .frame(maxWidth: 200, alignment: .leading)
            Divider()
            HStack {
                Text("Author")
                Button {
                    authorButtonStatus = authorButtonStatus.nextStatus
                    self.sortBy = authorButtonStatus.order
                } label: {
                    Image(systemName: "chevron.up.chevron.down")
                        .foregroundColor(Color.black)
                }
                }
            .frame(maxWidth: 200, alignment: .leading)
            Divider()
            HStack {
                Text("Pages")
                Button {
                    pagesButtonStatus = pagesButtonStatus.nextStatus
                    self.sortBy = pagesButtonStatus.order
                } label: {
                    Image(systemName: "chevron.up.chevron.down")
                        .foregroundColor(Color.black)
                }
                }
            .frame(maxWidth: 75, alignment: .leading)
        }
        .frame(maxWidth: .infinity, maxHeight: 40, alignment: .leading)
        .padding(.leading, 20)
        List {
                switch self.sortBy {
                case "title":
                    ForEach (bookStore.books.sorted {$0.title < $1.title}) { book in
                    ListCell(book: book)
                    }
                    .onDelete(perform: deleteItems)
                case "title2":
                    ForEach (bookStore.books.sorted {$0.title > $1.title}) { book in
                    ListCell(book: book)
                    }
                case "id":
                    ForEach (bookStore.books.sorted {$0.id < $1.id}) { book in
                    ListCell(book: book)
                    }
                case "id2":
                    ForEach (bookStore.books.sorted {$0.id > $1.id}) { book in
                    ListCell(book: book)
                    }
                case "author":
                    ForEach (bookStore.books.sorted {$0.authorsort < $1.authorsort}) { book in
                    ListCell(book: book)
                    }
                case "author2":
                    ForEach (bookStore.books.sorted {$0.authorsort > $1.authorsort}) { book in
                    ListCell(book: book)
                    }
                case "pages":
                    ForEach (bookStore.books.sorted {$0.pages < $1.pages}) { book in
                    ListCell(book: book)
                    }
                case "pages2":
                    ForEach (bookStore.books.sorted {$0.pages > $1.pages}) { book in
                    ListCell(book: book)
                    }
                default:
                    ForEach (bookStore.books.sorted {$0.id < $1.id}) { book in
                    ListCell(book: book)
                    }
                }
            }
        .listStyle(PlainListStyle())
        .frame(maxWidth: .infinity, alignment: .leading)
        }
    }
    func deleteItems(at offsets: IndexSet) {
        bookStore.books.remove(atOffsets: offsets)
    }
}

struct ListCell: View {
    
    var book : Book

    var body: some View {
        HStack(alignment: .center, spacing: nil) {
                Text("\(book.id)")
                .frame(maxWidth: 50, alignment: .leading)
                Divider()
                Text(book.title)
                .frame(maxWidth: 200, alignment: .leading)
                Divider()
                Text(book.author)
                .frame(maxWidth: 200, alignment: .leading)
                Divider()
                Text("\(book.pages)")
                .frame(maxWidth: 75, alignment: .leading)
        }
        .onTapGesture {
            print(book.title)
        }
    }
}

struct TableView_Previews: PreviewProvider {
    static var previews: some View {
        if #available(iOS 15.0, *) {
            TableView()
                .previewInterfaceOrientation(.landscapeLeft)
        } else {
            // Fallback on earlier versions
        }
    }
}

Upvotes: 2

Views: 1175

Answers (1)

"...why this list is not showing a highlight over whatever row is selected." I don't know for sure, but it's easy to fix. See the test code.

Similarly, "...the "onDelete" action for the row is deleting the wrong row." You must sort the list of books as in the body to get the correct index to delete. You'll need to work on this a bit.

This is the code I used for testing and to get you going:

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct TableView: View {
    
    @StateObject var bookStore : BookStore = BookStore()  // <--- for testing
    
    @State var sortBy: String = ""
    @State var IDbuttonStatus: IDButtonStatus = .firstPress
    @State var titleButtonStatus: TitleButtonStatus = .firstPress
    @State var authorButtonStatus: AuthorButtonStatus = .firstPress
    @State var pagesButtonStatus: PagesButtonStatus = .firstPress
    
    @State var selectedBook: Book? = nil   // <---- here
    
    enum IDButtonStatus {
        case secondPress
        case firstPress
        
        var nextStatus: IDButtonStatus {
            switch self {
            case .firstPress: return .secondPress
            case .secondPress: return .firstPress
            }
        }
        var order: String {
            switch self {
            case .firstPress: return "id2"
            case .secondPress: return "id"
            }
        }
    }
    
    enum TitleButtonStatus {
        case firstPress
        case secondPress
        
        var nextStatus: TitleButtonStatus {
            switch self {
            case .firstPress: return .secondPress
            case .secondPress: return .firstPress
            }
        }
        var order: String {
            switch self {
            case .firstPress: return "title2"
            case .secondPress: return "title"
            }
        }
    }
    
    enum AuthorButtonStatus {
        case firstPress
        case secondPress
        
        var nextStatus: AuthorButtonStatus {
            switch self {
            case .firstPress: return .secondPress
            case .secondPress: return .firstPress
            }
        }
        var order: String {
            switch self {
            case .firstPress: return "author2"
            case .secondPress: return "author"
            }
        }
    }
    
    enum PagesButtonStatus {
        case firstPress
        case secondPress
        
        var nextStatus: PagesButtonStatus {
            switch self {
            case .firstPress: return .secondPress
            case .secondPress: return .firstPress
            }
        }
        var order: String {
            switch self {
            case .firstPress: return "pages2"
            case .secondPress: return "pages1"
            }
        }
    }

    var body: some View {
        VStack {
            HStack(alignment: .center, spacing: nil) {
                HStack {
                    Text("ID")
                    Button {
                        IDbuttonStatus = IDbuttonStatus.nextStatus
                        self.sortBy = IDbuttonStatus.order
                    } label: {
                        Image(systemName: "chevron.up.chevron.down")
                            .foregroundColor(Color.black)
                    }
                }
                .frame(maxWidth: 50, alignment: .leading)
                Divider()
                HStack {
                    Text("Title")
                    Button {
                        titleButtonStatus = titleButtonStatus.nextStatus
                        self.sortBy = titleButtonStatus.order
                    } label: {
                        Image(systemName: "chevron.up.chevron.down")
                            .foregroundColor(Color.black)
                    }
                }
                .frame(maxWidth: 200, alignment: .leading)
                Divider()
                HStack {
                    Text("Author")
                    Button {
                        authorButtonStatus = authorButtonStatus.nextStatus
                        self.sortBy = authorButtonStatus.order
                    } label: {
                        Image(systemName: "chevron.up.chevron.down")
                            .foregroundColor(Color.black)
                    }
                }
                .frame(maxWidth: 200, alignment: .leading)
                Divider()
                HStack {
                    Text("Pages")
                    Button {
                        pagesButtonStatus = pagesButtonStatus.nextStatus
                        self.sortBy = pagesButtonStatus.order
                    } label: {
                        Image(systemName: "chevron.up.chevron.down")
                            .foregroundColor(Color.black)
                    }
                }
                .frame(maxWidth: 75, alignment: .leading)
            }
            .frame(maxWidth: .infinity, maxHeight: 40, alignment: .leading)
            .padding(.leading, 20)
            List {
                switch self.sortBy {
                case "title":
                    ForEach (bookStore.books.sorted {$0.title < $1.title}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                    .onDelete(perform: deleteItems)
                case "title2":
                    ForEach (bookStore.books.sorted {$0.title > $1.title}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                case "id":
                    ForEach (bookStore.books.sorted {$0.id < $1.id}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                case "id2":
                    ForEach (bookStore.books.sorted {$0.id > $1.id}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                case "author":
                    ForEach (bookStore.books.sorted {$0.authorsort < $1.authorsort}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                case "author2":
                    ForEach (bookStore.books.sorted {$0.authorsort > $1.authorsort}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                case "pages":
                    ForEach (bookStore.books.sorted {$0.pages < $1.pages}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                case "pages2":
                    ForEach (bookStore.books.sorted {$0.pages > $1.pages}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                default:
                    ForEach (bookStore.books.sorted {$0.id < $1.id}) { book in
                        ListCell(book: book, selectedBook: $selectedBook) // <---- here
                    }
                }
            }
            .listStyle(PlainListStyle())
            .frame(maxWidth: .infinity, alignment: .leading)
        }
    }

    func deleteItems(at offsets: IndexSet) { // <---- here
//        // must sort the bookStore.books as in the body
//        let sortedList = ...
//        // get the book from the sortedList
//        let theBook = sortedList[offsets.first!]
//        // get the index of theBook from the bookStore, and remove it
//        if let ndx = bookStore.books.firstIndex(of: theBook) {
//            withAnimation {
//                bookStore.books.remove(at: ndx)
//            }
//        }
    }

}

struct ListCell: View {
    var book : Book
    @Binding var selectedBook: Book? // <---- here

    var body: some View {
        HStack(alignment: .center, spacing: nil) {
            Text("\(book.id)")
                .frame(maxWidth: 50, alignment: .leading)
            Divider()
            Text(book.title)
                .frame(maxWidth: 200, alignment: .leading)
            Divider()
            Text(book.author)
                .frame(maxWidth: 200, alignment: .leading)
            Divider()
            Text("\(book.pages)")
                .frame(maxWidth: 75, alignment: .leading)
        }
        .background((selectedBook?.id ?? -1) == book.id ? Color.gray : Color.white) // <---- here
        .onTapGesture {
            print(book.title)
            selectedBook = book // <---- here
        }
    }
}

struct TableView_Previews: PreviewProvider {
    static var previews: some View {
        if #available(iOS 15.0, *) {
            TableView()
                .previewInterfaceOrientation(.landscapeLeft)
        } else {
            // Fallback on earlier versions
        }
    }
}

struct Book: Identifiable, Hashable {
    var id : Int
    var title : String
    var pages : String
    var authorsort : String
    var author : String = "author"
}

class BookStore: ObservableObject {
    @Published var books: [Book] = [
        Book(id: 1, title: "book1", pages: "page1", authorsort: "authorsort1"),
        Book(id: 2, title: "book2", pages: "page2", authorsort: "authorsort2"),
        Book(id: 3, title: "book3", pages: "page3", authorsort: "authorsort3")]
}

struct ContentView: View {
    var body: some View {
        TableView()
    }
}

Upvotes: 1

Related Questions