1

enter image description hereI have a custom swiftUI picker that takes a duration in the form of the picture above. The issue is, I have to use hard coded frame in my code to make it show and appear. I will explain further below.

var body: some View {
    let hours = [Int](0...maxHours)
    let minutes = [Int](0...maxMinutes)
    HStack(spacing: 0) {
        Picker(selection: self.selection.hours, label: Text("")) {
            ForEach(0..<maxHours, id: \.self) { index in
                Text("\(hours[index]) hr")
                    .foregroundColor(Color(Asset.Color.V2.white.color))
            }
        }
        .labelsHidden()
        .pickerStyle(.wheel)
        Picker(selection: self.selection.minutes, label: Text("")) {
            ForEach(0..<maxMinutes, id: \.self) { index in
                Text("\(minutes[index]) min")
                    .foregroundColor(Color(Asset.Color.V2.white.color))
            }
            .frame(maxWidth: .infinity, alignment: .center)
        }
        .labelsHidden()
        .pickerStyle(.wheel)
    }
}

The issue is I am combining two pickers and it is not a native approach. So it'll end up looking like this:

enter image description here

The frame becomes very small and off when it is part of a larger component.

If I remove the HStack and have one picker, the frame and sizing will fix themselves.

enter image description here

note: Ignore the colors, my only concern is the frame sizes that get messed up when I have two pickers.

problem 1: This picker is part of another large component. Here is how the structure is set up, and apologies in advance as I cannot share all of the code as it will be be more than 5000 lines of code. We have this:

VStack {
    element1
    element2
    ...
    element4
        .onTap{
            showCustomPickerView = true
        }
        .frame(width: 200, height: 200)
    if showCustomPickerView {
        CustomPickerView()
    }
    element5
}

So when we click on element4 which is essentially an HStack, we want our custom picker view to appear underneath it. The issue is I do not want hard coded frame values, but when I remove the frame, the CustomPickerView collapses and becomes like the picture I posted. If my CustomPickerView has only one picker in it, it shows just fine without the frame. But since I have two and I they are in an HStack, it does not show their default size, and I am guessing it shows the HStack size instead.

update 1: I added

extension UIPickerView {
    override open var intrinsicContentSize: CGSize {
        CGSize(width: UIView.noIntrinsicMetric, height: super.intrinsicContentSize.height)
    }
}

at my file, as without it, the right picker would get mixed with the first one, but the framing issue still persists.

Amin
  • 99
  • 5
  • Does this answer your question? https://stackoverflow.com/questions/56567539/multi-component-picker-uipickerview-in-swiftui – jnpdx Jul 29 '22 at 20:55
  • @jnpdx no unfortunately it still looks like the picture I posted, thanks though! – Amin Jul 29 '22 at 21:26
  • Please, when you post code in a question make sure it is a [Minimal Reproducible Example (MRE)](https://stackoverflow.com/help/minimal-reproducible-example). And make sure you post images from that code. The code you posted does look like your first image, though obviously without the color scheme. You have, however, another issue that is unsolved as of the current production version of iOS 15. You can't properly contain the wheel pickers to put them side by side like this. The second picker will overlap the first, so that you cannot move the first except off to its left. – Yrb Jul 30 '22 at 18:52
  • @Yrb Sure, I will keep that in mind. You are right, and that is the other issue that I am facing. I found the solution to it though, adding this fixes it: extension UIPickerView { override open var intrinsicContentSize: CGSize { CGSize(width: UIView.noIntrinsicMetric, height: super.intrinsicContentSize.height) } } – Amin Jul 31 '22 at 05:17

1 Answers1

2

I've slightly modified your solution, and it works fine for me:

@main
struct Test: App {
    @State var hourSelection = 0
    @State var minuteSelection = 0
    
    var body: some Scene {
        WindowGroup {
            CustomDatePicker(hourSelection: $hourSelection, minuteSelection: $minuteSelection)
        }
    }
}

struct CustomDatePicker: View {
    @Binding var hourSelection: Int
    @Binding var minuteSelection: Int
    
    static private let maxHours = 24
    static private let maxMinutes = 60
    private let hours = [Int](0...Self.maxHours)
    private let minutes = [Int](0...Self.maxMinutes)
    
    var body: some View {
        GeometryReader { geometry in
            HStack(spacing: .zero) {
                Picker(selection: $hourSelection, label: Text("")) {
                    ForEach(hours, id: \.self) { value in
                        Text("\(value) hr")
                            .tag(value)
                    }
                }
                .pickerStyle(.wheel)
                .frame(width: geometry.size.width / 2, alignment: .center)
                
                Picker(selection: $minuteSelection, label: Text("")) {
                    ForEach(minutes, id: \.self) { value in
                        Text("\(value) min")
                            .tag(value)
                    }
                    .frame(maxWidth: .infinity, alignment: .center)
                }
                .pickerStyle(.wheel)
                .frame(width: geometry.size.width / 2, alignment: .center)
            }
        }
    }
}

The main idea here is that:

  1. You do not specify the height of the picker, so the GeometryReader adjusts its size to correspond to the default Picker height.
  2. Change the width from geometry.size.width / 3 to geometry.size.width / 2 (as there are only two pickers).
  3. Remove the unnecessary modifiers (.compositeGroup(), .clipped(), .etc).
  4. Move the picker into a separate struct for ease of use.

Alternatively, you can manually specify a fixed size for the custom component using the .frame(height:) modifier.

Let me know if it still collapses

narek.sv
  • 845
  • 5
  • 22
  • Thanks for responding, the view still collapses, I wish I could share all the codes, but the code base is huge. But Just to give some context, the custom picker view I have is put in a structure that has 5 elements aligned in the VStack, and once we click on the 4th one, it should add a new view right underneath it that shows the picker view. If I only have a native SwiftUI picker, the size of the view that pops up will be correct and it will show all of the picker. But in the modified one, it just shows the size of the HStack that has the two pickers. – Amin Jul 31 '22 at 05:28
  • I've tried to simulate something similar to what you described, still works fine on my side. So either you need to share the whole codebase somehow (if there are no privacy issues) or try using a UIKit-ish multi-part picker component, something like this: https://stackoverflow.com/a/58664469/14294561 – narek.sv Jul 31 '22 at 07:16
  • for some weird reason, it was not working yesterday, but now it is. Thank you! – Amin Jul 31 '22 at 20:46