Search code examples
swiftuipicker

Wheel Pickers in form disappear after some show/hide cycles


I'm experiencing a strange behavior with wheel pickers embedded in a conditional subview. When the subview is shown, sometimes the values are not shown. Switching around between two conditional views, the values sometimes reappear. I've attached an animation showing the behavior and the full code. I can't find the reason for this.

Update: I've tried a lot of things to find the reason for this. Even after simplifying the revealing subform to just one single picker, replacing the foreach loop with hardcoded Text() entries, removing the framing and clipping on the picker, it still doesn't work.

enter image description here

import SwiftUI

fileprivate enum OpenSetting {
    case none, start, end
}

struct ContentView: View {
    @State private var openSetting = OpenSetting.none
    @State private var startMinutes = 0
    @State private var startSeconds = 10
    @State private var endMinutes = 3
    @State private var endSeconds = 0

    var body: some View {
        NavigationView {
            Form {
                // Start
                TimeSetting(
                    title: "Start",
                    color: Color.yellow,
                    minutes: startMinutes,
                    seconds: startSeconds,
                    setting: .start,
                    openSetting: $openSetting
                )
                if openSetting == .start {
                    TimePicker(minutes: $startMinutes, seconds: $startSeconds)
                }

                // End
                TimeSetting(
                    title: "End",
                    color: Color.green,
                    minutes: endMinutes,
                    seconds: endSeconds,
                    setting: .end,
                    openSetting: $openSetting
                )
                if openSetting == .end {
                    TimePicker(minutes: $endMinutes, seconds: $endSeconds)
                }
            }
                .navigationBarTitle("Test")
                .navigationBarItems(
                    trailing: Text("Start")
            )

        }

    }
}

struct TimeSetting: View {
    var title: String
    var color: Color
    var minutes: Int
    var seconds: Int
    fileprivate var setting: OpenSetting
    fileprivate var openSetting: Binding<OpenSetting>

    var body: some View {
        HStack {
            Text(title)
            Spacer()
            ZStack {
                RoundedRectangle(cornerRadius: 4)
                    .fill(color)
                Text(toTime(minutes: minutes, seconds: seconds))
            }
            .frame(width: 64, height: 32)
        }
        .contentShape(Rectangle())
        .onTapGesture {
            withAnimation() {
                self.openSetting.wrappedValue = (self.openSetting.wrappedValue == self.setting) ? OpenSetting.none : self.setting
            }
        }
    }

    func toTime(minutes: Int, seconds: Int) -> String {
        let timeString = String(format: "%02d", minutes) + ":" + String(format: "%02d", seconds)
        return timeString
    }
}

struct TimePicker: View {
    var minutes: Binding<Int>
    var seconds: Binding<Int>

    var body: some View {
        HStack() {
            Spacer()
            Picker(selection: minutes, label: EmptyView()) {
                ForEach((0...9), id: \.self) { ix in
                    Text("\(ix)").tag(ix)
                }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
            Text("Min.")
            Picker(selection: seconds, label: EmptyView()) {
                ForEach((0...59), id: \.self) { ix in
                    Text("\(ix)").tag(ix)
                }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
            Text("Sec.")
            Spacer()
        }
    }
}

Solution

  • Using .id for Pickers seems helped. Tested with Xcode 11.2 / iOS 13.2.

    var body: some View {
        HStack() {
            Spacer()
            Picker(selection: minutes, label: EmptyView()) {
                ForEach((0...9), id: \.self) { ix in
                    Text("\(ix)").tag(ix)
                }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
            .id(UUID().uuidString)
            Text("Min.")
            Picker(selection: seconds, label: EmptyView()) {
                ForEach((0...59), id: \.self) { ix in
                    Text("\(ix)").tag(ix)
                }
                }.pickerStyle(WheelPickerStyle()).frame(width: 50).clipped()
            .id(UUID().uuidString)
            Text("Sec.")
            Spacer()
        }
    }