Lab 029 – Waiting on a Color Picker
What if a using a color picker was like waiting at baggage claim?
Overview
Just a bit of a fun and distraction while I’m waiting at the airport during a layover.
- Hardcoded array of colors
- Create a sphere for each color and arrange them in a circle
- Position the group of colors in front of the user
- Use a cylinder to clip the visible colors
- Rotate the color group slowly
- Tap gesture to select a color
Video Demo
Full Lab Code
struct Lab029: View {
@State private var colors: [Color] = [
.red,
.pink,
.orange,
.yellow,
.green,
.mint,
.teal,
.cyan,
.blue,
.indigo,
.purple,
.brown,
.white,
Color(uiColor: .systemGray),
Color(uiColor: .systemGray2),
Color(uiColor: .systemGray3),
Color(uiColor: .systemGray4),
Color(uiColor: .systemGray5),
Color(uiColor: .systemGray6),
.black
]
@State var isSpinning: Bool = true
@State var colorGroup = Entity()
@State var selectedColorIndex: Int = 0
var body: some View {
RealityView { content, attachments in
if let scene = try? await Entity(named: "BaggageClaim", in: realityKitContentBundle) {
content.add(scene)
colorGroup.name = "ColorGroup"
colorGroup.position = SIMD3(x: 0, y: 1, z: -5)
scene.addChild(colorGroup)
let radius: Float = 2.5 // 5 meter diameter
let totalColors = Float(colors.count)
for (index, color) in colors.enumerated() {
let colorEntity = ModelEntity(mesh: .generateSphere(radius: 0.25))
colorEntity.name = "Color_\(index)"
colorEntity.components.set(HoverEffectComponent())
colorEntity.components.set(InputTargetComponent())
let collision = CollisionComponent(shapes: [.generateSphere(radius: 0.25)])
colorEntity.components.set(collision)
var material = PhysicallyBasedMaterial()
material.baseColor.tint = .init(color)
material.roughness = 0.5
material.metallic = 0.0
colorEntity.model?.materials = [material]
// Calculate position on circle
let angle = (Float(index) / totalColors) * 2 * .pi
let x = radius * sin(angle)
let z = radius * cos(angle)
colorEntity.position = SIMD3(x: -x, y: 0, z: z)
colorGroup.addChild(colorEntity)
}
if let panel = attachments.entity(for: "Panel") {
panel.position = SIMD3(x: 0, y: 1.5, z: -1.5)
content.add(panel)
}
}
} update: { content, attachments in
// Empty update closure is fine
} attachments: {
Attachment(id: "Panel") {
VStack {
Text("Selected Color")
RoundedRectangle(cornerRadius: 5)
.fill(Color(colors[selectedColorIndex]))
.padding()
}
.frame(width: 300, height: 200)
.glassBackgroundEffect()
}
}
.gesture(tapGesture)
.task {
while true {
try? await Task.sleep(for: .milliseconds(16)) // ~60fps
if isSpinning {
colorGroup.orientation *= simd_quatf(angle: .pi/6300, axis: SIMD3<Float>(0, 1, 0)) // 0.5 degrees per frame
}
}
}
}
var tapGesture: some Gesture {
TapGesture(count: 2)
.targetedToAnyEntity()
.onEnded { value in
let name = value.entity.name
let index = Int(name.split(separator: "_")[1])!
selectedColorIndex = index
}
}
}Support our work so we can continue to bring you new examples and articles.

Follow Step Into Vision