NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

Creating custom pickers

Forums > SwiftUI

I know that there is a PickerStyle protocol, but to the best of my knowledge, no one has really figured out how to create custom picker styles using it yet?

I need two custom pickers, and I'm struggling to get them "just right".

The first is a picker that allows multiple selections. In theory, I already have this, but in practice, there is something a little wonky going on and I can't figure it out.

Background: I'm creating an audiobook library curating app, and I'm trying to implement functionality for the user to make a book series and add books to it.

What I have, code-wise, is this:

struct SelectableBookList: View {
    @Binding var selectedBooks: Set<Audiobook>
    @EnvironmentObject var database: LibraryDatabase

    var body: some View {
        List(database.books) { book in
            HStack {
                if selectedBooks.contains(book) {
                    Image(systemName: "checkmark")
                }
                Image(nativeImage: book.cover)
                    .resizable()
                    .frame(width: 30, height: 30)
                Text(book.title).font(.title3)
                Spacer()
                Text(book.authorsStringShort).italic()
            }
            .onTapGesture(perform: {
                if selectedBooks.contains(book) {
                    selectedBooks.remove(book)
                } else {
                    selectedBooks.insert(book)
                }
            })
            .padding(.horizontal)
        }
    }
}

struct SeriesCreator: View {
    @State private var title = ""
    @State private var selectedBooks = Set<Audiobook>()
    private var selectedBooksArray: [Audiobook] { selectedBooks.map({$0}).removingDuplicates() }
    @EnvironmentObject var database: LibraryDatabase

    @State private var presentBookSelector = false

    var body: some View {
        VStack {
            Form {
                TextField("Series Title", text: $title)

                Section(header: Text("Books in Series")) {
                    Button(action: { presentBookSelector.toggle() }, label: {
                        HStack {
                            Image(systemName: "plus")
                            Text("Add Books")
                        }
                        .font(.title2)
                    })
                    .sheet(isPresented: $presentBookSelector, content: {
                        SelectableBookList(selectedBooks: $selectedBooks)
                    })
                    List(selectedBooksArray) { book in
                        Text(book.title)
                            .italic()
                    }
                    .font(.subheadline)
                }
            }
            // snip buttons and other stuff
        }
    }
}

In the preview, this works exactly as it should.

But when I actually run the app, a strange thing happens. The first time I tap on a book to select it, the list view changes. It's easier to demonstrate, so here is a video: https://user-images.githubusercontent.com/56804260/111397102-b58eca00-867d-11eb-8769-0e5992994d13.mov

I don't really understand why it's doing that and have no idea how to fix it.

My other custom picker view is both simpler and more complex. It appears if the user is attempting to create a series, but one or more series with the same title already exist in the database. I want to present the user with a list of all the it items with matching titles and ask the user

I only need it to allow one selection, but I want it to appear like an alert or action sheet. Since you can't add a picker to an alert or action sheet, I've sort of cheated by making an action sheet with each button one of the options the user can select, but it's just...clumsy.

    private func multiMatchAlert(options: [Series]) -> ActionSheet {
        var buttons = [ActionSheet.Button]()
        for item in options {
            buttons.append(.default(Text("\(item.title): \(item.authorsStringShort)"), action: {
                selection = nil
                presentationMode.wrappedValue.dismiss()
                targetSeries = item
                presentSeriesView.toggle()
            }))
            buttons.append(.default(Text("Create new series"), action: {
                createOrFetchSeries(disregardDupes: true)
            }))
        }

        return ActionSheet(title: Text("Select a Series"),
                           message: Text("Select which series to use"),
                           buttons: buttons)
    }

Does anyone have any advice, or links to other tutorials, on how I might accomplish the sort of functionality I'm going for here?

   

I figured out what is going on with the view, and how to prevent it, but not why it was happening that way.

I hadn't set a listStyle, and when the first item was selected, it was changing to InsetListStyle(). No idea WHY it was doing that, but making the listStyle inset from the beginning seems to have fixed it?

It's a minor change and not a hardship, but I'd really like to understand why it was happening.

   

Hacking with Swift is sponsored by Instabug

SPONSORED Catch bugs as soon as they happen and know exactly why a crash occurred. Instabug's SDK grabs all the logs they need to fix bugs, crashes and performance issues in minutes instead of days. Get screenshots, device details, network logs, repro steps, and tons of other critical insights needed to resolve issues and prioritize product backlogs straight from your dashboard. It only takes a minute to integrate!

Get started now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.