SwiftUI ScrollView messing up picker selection behaviour

212 Views Asked by At

I'm building a WatchOS-app (SwiftUI) with multiple pickers, but as soon as I add them to a ScrollView I can no longer simply tap a picker to select it.

When I tap a picker the first picker on the screen gets selected and I have to tap once more to have the right picker selected. Once I've double tapped the picker I can select other pickers just fine, but as soon as I tap outside to deselect all pickers I have to double tap again.

Sorry if the explanation is a bit fuzzy. This video shows the issue: Video

I'm new to both programming and Swift, so be gentle ;)

import SwiftUI

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct ContentView: View {
    
    let paceArray = Array(0...59)
    let speedArray = Array(0...99)
    
    @State private var globalSecondsPerKM: Double = 0
    @State private var paceMKMHours: Int = 0
    @State private var paceMKMMinutes: Int = 0
    @State private var paceMKMSeconds: Int = 0
    @State private var paceMMHours: Int = 0
    @State private var paceMMMinutes: Int = 0
    @State private var paceMMSeconds: Int = 0
    @State private var speedKMHWhole: Int = 0
    @State private var speedKMHDecimal: Int = 0
    @FocusState private var paceMKMFocused: Bool
    @FocusState private var paceMMFocused: Bool
    @FocusState private var speedKMHFocused: Bool
    
    var body: some View {
        
        ScrollView {
            VStack {
               
                    VStack {
                        Text("Pace per km")
                            .font(.headline)
                        HStack {
                            Picker(selection: $paceMKMHours, label: Text(""), content: {
                                ForEach(0..<speedArray.count, id: \.self) { index in
                                    Text(String(format: "%02dh", speedArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                            Picker(selection: $paceMKMMinutes, label: Text(""), content: {
                                ForEach(0..<paceArray.count, id: \.self) { index in
                                    Text(String(format: "%02dm", paceArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                            Picker(selection: $paceMKMSeconds, label: Text(""), content: {
                                ForEach(0..<paceArray.count, id: \.self) { index in
                                    Text(String(format: "%02ds", paceArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                        }
                        .focused($paceMKMFocused)
                        .padding(.bottom, 5)
                        .frame(height: 35)
                    }
                    Divider()
                
   
                    VStack {
                        Text("Pace per mile")
                            .font(.headline)
                        HStack {
                            Picker(selection: $paceMMHours, label: Text(""), content: {
                                ForEach(0..<speedArray.count, id: \.self) { index in
                                    Text(String(format: "%02dh", speedArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                            
                            Picker(selection: $paceMMMinutes, label: Text(""), content: {
                                ForEach(0..<paceArray.count, id: \.self) { index in
                                    Text(String(format: "%02dm", paceArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                            
                            Picker(selection: $paceMMSeconds, label: Text(""), content: {
                                ForEach(0..<paceArray.count, id: \.self) { index in
                                    Text(String(format: "%02ds", paceArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                        }
                        .focused($paceMMFocused)
                        .padding(.bottom, 5)
                        .frame(height: 35)
                    }
                    Divider()
    

                    VStack {
                        Text("Speed in km/h")
                            .font(.headline)
                        HStack {
                            Picker(selection: $speedKMHWhole, label: Text(""), content: {
                                ForEach(0..<speedArray.count, id: \.self) { index in
                                    Text(String(format: "%02d", speedArray[index])).tag(index)
                                }
                            })
                            .frame(width: 45)
                            
                            Picker(selection: $speedKMHDecimal, label: Text("")) {
                                ForEach(0..<speedArray.count, id: \.self) { index in
                                    Text(String(format: ".%02d", speedArray[index])).tag(index)
                                }
                            }
                            .frame(width: 45)
                        }
                        .focused($speedKMHFocused)
                        .padding(.bottom, 5)
                        .frame(height: 35)
                    }
                    Divider()
                
            }
        }
        .labelsHidden()
        .font(.system(size: 13))
    }
}

1

There are 1 best solutions below

1
On BEST ANSWER

This looks like a SwiftUI bug. A possible workaround is setting up a tap gesture on the picker, which triggers a focus change. The initial animation is not perfect, but it looks fine after that.

import SwiftUI

@available(watchOSApplicationExtension 8.0, *)
struct ContentView: View {
    let paceArray = Array(0...59)

    @State private var paceMKMHours: Int?
    @State private var paceMKMMinutes: Int?
    @State private var paceMKMSeconds: Int?

    @FocusState private var shouldFocusHours: Bool
    @FocusState private var shouldFocusMinutes: Bool
    @FocusState private var shouldFocusSeconds: Bool

    var body: some View {
        ScrollView {
            VStack {
                HStack {
                    Picker("hours", selection: $paceMKMHours, content: {
                        ForEach(0..<paceArray.count, id: \.self) { index in
                            Text(String(format: "%02dh", paceArray[index])).tag(index)
                        }
                    })
                    .onTapGesture {
                        shouldFocusHours = true
                    }
                    .focused($shouldFocusHours)

                    Picker("minutes", selection: $paceMKMMinutes, content: {
                        ForEach(0..<paceArray.count, id: \.self) { index in
                            Text(String(format: "%02dm", paceArray[index])).tag(index)
                        }
                    })
                    .onTapGesture {
                        shouldFocusMinutes = true
                    }
                    .focused($shouldFocusMinutes)

                    Picker("seconds", selection: $paceMKMSeconds, content: {
                        ForEach(0..<paceArray.count, id: \.self) { index in
                            Text(String(format: "%02ds", paceArray[index])).tag(index)
                        }
                    })
                    .onTapGesture {
                        shouldFocusSeconds = true
                    }
                    .focused($shouldFocusSeconds)
                }
                .padding(.bottom, 5)
                .frame(height: 35)
            }
        }
        .labelsHidden()
        .font(.system(size: 13))
    }
}