swiftswiftuiavaudioplayerpickerswiftui-navigationlink

Implement a preview button within SwiftUI Picker


I would like to create a Picker within my app's settings which allows the user to select their notification tone. Specifically, I want the user to be able to preview this tone before selecting it.

My current implementation actually achieves this. My picker has navigationLink style and contains several HStacks each consisting of Text and Button.

struct SettingsView: View {
 
    ...
    
    var body: some View {
        NavigationStack {
            Form {      
                    Toggle("Push Notifications", isOn: binding)
                    
                    Picker("Tone", selection: $toneSelection) {
                        HStack {
                            Text("Simple")
                            Button(action: {playPreview(previewSound: "Sound1")}) {
                                Image(systemName: "speaker.wave.3.fill")
                            }
                        }.tag(NotificationType.simple)
                        
                        HStack {
                            Text("Reverb")
                            Button(action: {playPreview(previewSound: "Sound2")}) {
                                Image(systemName: "speaker.wave.3.fill")
                            }
                        }.tag(NotificationType.reverb)
                    }
                    .pickerStyle(.navigationLink)
            }
        }
    }
}

But I'd like for the button/icon to disappear upon making the selection.

enter image description here

enter image description here


Solution

  • Instead of using a .navigationLink picker style, you can put an actual NavigationLink in the form. Its destination will be an .inline style picker in a List.

    This way, you can customise the NavigationLink's label, which allows you to put only the name of the tone.

    Here is an example:

    // suppose NotificationType is an enum like this
    enum NotificationType {
        case simple, reverb
        
        var description: LocalizedStringKey {
            switch self {
            case .simple:
                "Simple"
            case .reverb:
                "Reverb"
            }
        }
    }
    
    struct ContentView: View {
        @State var toneSelection = NotificationType.simple
        var body: some View {
            NavigationStack {
                Form {
                    LabeledContent("Tone") {
                        NavigationLink {
                            TonePicker(toneSelection: $toneSelection)
                        } label: {
                            HStack {
                                Spacer() // this spacer pushes the tone's name to the right of the list row
                                Text(toneSelection.description)
                            }
                        }
                    }
                }
            }
        }
    }
    
    struct TonePicker: View {
        @Binding var toneSelection: NotificationType
        @Environment(\.dismiss) var dismiss
        var body: some View {
            List {
                Picker("Tone", selection: $toneSelection) {
                    HStack {
                        Text("Simple")
                        Button(action: { playSound("Sound1") }) {
                            Image(systemName: "speaker.wave.3.fill")
                        }
                    }.tag(NotificationType.simple)
                    
                    HStack {
                        Text("Reverb")
                        Button(action: { playSound("Sound2") }) {
                            Image(systemName: "speaker.wave.3.fill")
                        }
                        
                    }.tag(NotificationType.reverb)
                }
                .pickerStyle(.inline)
            }
            .navigationTitle("Tone")
            // once the user selected something, navigate back
            .onChange(of: toneSelection) {
                dismiss()
            }
        }
    }