Issue
I am currently trying to create a page with three adjacent Picker
views inside of an HStack
as seen below:
I made a CustomPicker
view where I limit the frame to 90 x 240, and then use .compositingGroup()
and .clipped()
to make the selectable area of each picker not overlap.
CustomPicker.swift
import SwiftUI
struct CustomPicker: View {
@Binding var selection: Int
let pickerColor: Color
var numbers: some View {
ForEach(0...100, id: \.self) { num in
Text("\(num)")
.bold()
}
}
var stroke: some View {
RoundedRectangle(cornerRadius: 16)
.stroke(lineWidth: 2)
}
var backgroundColor: some View {
pickerColor
.opacity(0.25)
}
var body: some View {
Picker("Numbers", selection: $selection) {
numbers
}
.frame(width: 90, height: 240)
.compositingGroup()
.clipped()
.pickerStyle(.wheel)
.overlay(stroke)
.background(backgroundColor)
.cornerRadius(16)
}
}
ChoicePage.swift
struct ChoicePage: View {
@State var choiceA: Int = 0
@State var choiceB: Int = 0
@State var choiceC: Int = 0
var body: some View {
HStack(spacing: 18) {
CustomPicker(selection: $choiceA, pickerColor: .red)
CustomPicker(selection: $choiceB, pickerColor: .green)
CustomPicker(selection: $choiceC, pickerColor: .blue)
}
}
}
When testing both CustomPicker
and ChoicePage
in the preview canvas and simulator, it had worked perfectly fine, but when I tried to use it on my physical devices (iPhone 8 and iPhone 13, both on iOS 15.1) the clickable areas overlap. I have tried solutions from this post and this post, as well as many others, but nothing seems to be working for me.
Solution
I solved this issue by modifying the solution from Steve M, so all the credit for this goes to him.
He uses a UIViewRepresentable
, but in his implementation, it’s for three different selections inside of one. I slightly adjusted his implementation, to be used for just one value to select from in a given picker.
I start with BasePicker
, which acts as the UIViewRepresentable
:
BasePicker.swift
struct BasePicker: UIViewRepresentable {
var selection: Binding<Int>
let data: [Int]
init(selecting: Binding<Int>, data: [Int]) {
self.selection = selecting
self.data = data
}
func makeCoordinator() -> BasePicker.Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<BasePicker>) -> UIPickerView {
let picker = UIPickerView()
picker.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
picker.dataSource = context.coordinator
picker.delegate = context.coordinator
return picker
}
func updateUIView(_ view: UIPickerView, context: UIViewRepresentableContext<BasePicker>) {
guard let row = data.firstIndex(of: selection.wrappedValue) else { return }
view.selectRow(row, inComponent: 0, animated: false)
}
class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
var parent: BasePicker
init(_ pickerView: BasePicker) {
parent = pickerView
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return 90
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return parent.data.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return parent.data[row].formatted()
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
parent.selection.wrappedValue = parent.data[row]
}
}
}
I then use the BasePicker
Representable inside of CustomPicker
, which is a SwiftUI View
. I did this to make it a bit easier to keep my previous styling/structure in the original code.
CustomPicker.swift
struct CustomPicker: View {
@Binding var selection: Int
let pickerColor: Color
let numbers: [Int] = Array(stride(from: 0, through: 100, by: 1))
var stroke: some View {
RoundedRectangle(cornerRadius: 16)
.stroke(lineWidth: 2)
}
var backgroundColor: some View {
pickerColor
.opacity(0.25)
}
var body: some View {
BasePicker(selecting: $selection, data: numbers)
.frame(width: 90, height: 240)
.overlay(stroke)
.background(backgroundColor)
.cornerRadius(16)
}
}
I then just need to slightly change ChoicePage
and it’s fixed. Also, take note that I moved the numbers
array into my CustomPicker
view, but you adust it so that you can pass it in from ChoicePage
if you wanted.
ChoicePage.swift
struct ChoicePage: View {
@State var choiceA: Int = 0
@State var choiceB: Int = 0
@State var choiceC: Int = 0
var body: some View {
HStack(spacing: 18) {
CustomPicker(selection: $choiceA, pickerColor: .red)
CustomPicker(selection: $choiceB, pickerColor: .green)
CustomPicker(selection: $choiceC, pickerColor: .blue)
}
}
}
Answered By – Teddy Bersentes
Answer Checked By – Mary Flores (BugsFixing Volunteer)