Created
January 2, 2024 06:19
-
-
Save hassanvfx/a0616ae77001d6ea6d3c11d21d4a9c44 to your computer and use it in GitHub Desktop.
swiftui rouletter wheel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
struct RouletteWheel: View { | |
let segments: [String] | |
var onResultSelected: (String) -> Void | |
@State private var rotationAngle: Double = 0 | |
@State private var isSpinning: Bool = false | |
var body: some View { | |
ZStack { | |
ForEach(0..<segments.count, id: \.self) { index in | |
RouletteSegment( | |
label: segments[index], | |
segmentCount: segments.count, | |
index: index | |
) | |
} | |
.frame(width: 320, height: 320, alignment: .center) | |
.rotationEffect(.degrees(rotationAngle)) | |
TriangleIndicator() | |
.fill(Color.red) | |
.frame(width: 30, height: 30) | |
.offset(y: -160) | |
.shadow(radius: 4) | |
} | |
.onTapGesture { | |
if !isSpinning { | |
let fullRotation = 360.0 | |
let rotations = Double.random(in: 5...10) | |
let totalDegrees = fullRotation * rotations | |
let animation = Animation.easeOut(duration: 3) | |
withAnimation(animation) { | |
rotationAngle += totalDegrees | |
} | |
// Determine the winning segment after the animation | |
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { | |
isSpinning = false | |
// Normalize the angle between 0 to 359 | |
let normalizedRotation = rotationAngle.truncatingRemainder(dividingBy: fullRotation) | |
// Compute the winning segment | |
let segmentAngle = fullRotation / Double(segments.count) | |
let winningIndex = Int((fullRotation - normalizedRotation) / segmentAngle) % segments.count | |
let winningSegment = segments[winningIndex] | |
onResultSelected(winningSegment) | |
} | |
isSpinning = true | |
} | |
} | |
} | |
} | |
struct RouletteSegment: View { | |
var label: String | |
var segmentCount: Int | |
var index: Int | |
var body: some View { | |
GeometryReader { geometry in | |
Path { path in | |
let center = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2) | |
path.move(to: center) | |
path.addArc(center: center, radius: geometry.size.width / 2, | |
startAngle: .degrees((360 / Double(segmentCount)) * Double(index)), | |
endAngle: .degrees((360 / Double(segmentCount)) * Double(index + 1)), | |
clockwise: false) | |
} | |
.fill(segmentColor(index: index)) | |
Text(label) | |
.position(segmentLabelPosition(geometry: geometry, index: index, segmentCount: segmentCount)) | |
} | |
} | |
private func segmentColor(index: Int) -> Color { | |
return [Color.red, Color.green, Color.blue, Color.orange, Color.purple, Color.yellow][index % 6] | |
} | |
private func segmentLabelPosition(geometry: GeometryProxy, index: Int, segmentCount: Int) -> CGPoint { | |
let angle = (2 * .pi / Double(segmentCount)) * Double(index) + (.pi / Double(segmentCount)) | |
let radius = geometry.size.width / 3 | |
let labelX = geometry.size.width / 2 + cos(angle) * radius | |
let labelY = geometry.size.height / 2 + sin(angle) * radius | |
return CGPoint(x: labelX, y: labelY) | |
} | |
} | |
struct TriangleIndicator: Shape { | |
func path(in rect: CGRect) -> Path { | |
var path = Path() | |
let center = CGPoint(x: rect.midX, y: rect.minY) | |
let left = CGPoint(x: rect.minX, y: rect.maxY) | |
let right = CGPoint(x: rect.maxX, y: rect.maxY) | |
path.move(to: center) | |
path.addLines([left, right, center]) | |
return path | |
} | |
} | |
struct RouletteApp_Preview: PreviewProvider { | |
static var previews: some View { | |
RouletteWheel(segments: ["Option 1", "Option 2", "Option 3", "Option 4", "Option 5"]){_ in} | |
} | |
} |
Author
hassanvfx
commented
Jan 2, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment