Skip to content

Commit

Permalink
Add FortuneWheel Model (#7)
Browse files Browse the repository at this point in the history
* Add FW model

* Update FW model access modifier

* Update README.md
  • Loading branch information
sameersyd authored Oct 26, 2022
1 parent 900461e commit 48d9ae9
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 75 deletions.
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,43 @@ pod 'FortuneWheel'

```swift
dependencies: [
.package(url: "https://github.com/sameersyd/FortuneWheel.git", .upToNextMajor(from: "0.1.4"))
.package(url: "https://github.com/sameersyd/FortuneWheel.git", .upToNextMajor(from: "0.1.5"))
]
```

<br />

## Sample Code 🌟

Import Fortune Wheel in your file
Import Fortune Wheel in your file.
```ruby
import FortuneWheel
```

Create a `FortuneWheelModel` and pass it to the view.
```swift
struct ContentView: View {
var players = ["Sameer", "Spikey", "Amelia", "Joan", "Karen", "Natalie"]
var body: some View {
ZStack {
FortuneWheel(titles: players, size: 320, onSpinEnd: { index in
// your action here - based on index
}, getWheelItemIndex: getWheelItemIndex)
}
}

private func getWheelItemIndex() -> Int {
return index
}

private var players = ["Sameer", "Spikey", "Amelia", "Danny", "Karen", "Kathy"]

var body: some View {
let model = FortuneWheelModel(
titles: players, size: 320,
onSpinEnd: onSpinEnd,
getWheelItemIndex: getWheelItemIndex
)
ZStack {
FortuneWheel(model: model)
}
}

private func onSpinEnd(index: Int) {
// your action here - based on index
}

private func getWheelItemIndex() -> Int {
return getIndexFromAPI()
}
}
```

Expand Down
45 changes: 13 additions & 32 deletions Sources/FortuneWheel/FortuneWheel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,27 @@ import SwiftUI

@available(macOS 11.0, *)
@available(iOS 14.0, *)

public struct FortuneWheel: View {

private var titles: [String], size: CGFloat, onSpinEnd: ((Int) -> ())?, strokeWidth: CGFloat, strokeColor: Color = Color(hex: "252D4F")
private var colors: [Color] = Color.spin_wheel_color, pointerColor: Color = Color(hex: "DA4533")
@StateObject var viewModel: FortuneWheelViewModel
private let model: FortuneWheelModel
@StateObject private var viewModel: FortuneWheelViewModel

public init(
titles: [String], size: CGFloat, onSpinEnd: ((Int) -> ())?,
colors: [Color]? = nil, pointerColor: Color? = nil,
strokeWidth: CGFloat = 15, strokeColor: Color? = nil,
animDuration: Double = Double(6), animation: Animation? = nil,
getWheelItemIndex: (() -> (Int))? = nil
) {
self.titles = titles
self.size = size
self.strokeWidth = strokeWidth

if let colors = colors { self.colors = colors }
if let pointerColor = pointerColor { self.pointerColor = pointerColor }
if let strokeColor = strokeColor { self.strokeColor = strokeColor }

let timeCurveAnimation = Animation.timingCurve(0.51, 0.97, 0.56, 0.99, duration: animDuration)
_viewModel = StateObject(wrappedValue: FortuneWheelViewModel(
titles: titles,
animDuration: animDuration,
animation: animation ?? timeCurveAnimation,
onSpinEnd: onSpinEnd,
getWheelItemIndex: getWheelItemIndex
))
public init(model: FortuneWheelModel) {
self.model = model
_viewModel = StateObject(wrappedValue: FortuneWheelViewModel(model: model))
}

public var body: some View {
ZStack(alignment: .top) {
ZStack(alignment: .center) {
SpinWheelView(data: (0..<titles.count).map { _ in Double(100/titles.count) },
labels: titles, colors: colors)
.frame(width: size, height: size)
SpinWheelView(data: (0..<model.titles.count).map { _ in Double(100 / model.titles.count) },
labels: model.titles, colors: model.colors)
.frame(width: model.size, height: model.size)
.overlay(
RoundedRectangle(cornerRadius: size/2).stroke(lineWidth: strokeWidth)
.foregroundColor(strokeColor)
RoundedRectangle(cornerRadius: model.size / 2)
.stroke(lineWidth: model.strokeWidth)
.foregroundColor(model.strokeColor)
)
.rotationEffect(.degrees(viewModel.degree))
.gesture(
Expand All @@ -62,7 +43,7 @@ public struct FortuneWheel: View {
)
SpinWheelBolt()
}
SpinWheelPointer(pointerColor: pointerColor).offset(x: 0, y: -25)
SpinWheelPointer(pointerColor: model.pointerColor).offset(x: 0, y: -25)
}
}
}
Expand Down
46 changes: 46 additions & 0 deletions Sources/FortuneWheel/FortuneWheelModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// File.swift
//
//
// Created by Sameer Nawaz on 24/10/22.
//

import SwiftUI

@available(macOS 10.15, *)
@available(iOS 13.0, *)
public struct FortuneWheelModel {

let titles: [String]
let size: CGFloat
let onSpinEnd: ((Int) -> ())?
let colors: [Color]
let pointerColor: Color
let strokeWidth: CGFloat
let strokeColor: Color
let animDuration: Double
let animation: Animation
let getWheelItemIndex: (() -> (Int))?

public init(
titles: [String], size: CGFloat, onSpinEnd: ((Int) -> ())?,
colors: [Color]? = nil,
pointerColor: Color? = nil,
strokeWidth: CGFloat = 15,
strokeColor: Color? = nil,
animDuration: Double = Double(6),
animation: Animation? = nil,
getWheelItemIndex: (() -> (Int))? = nil
) {
self.titles = titles
self.size = size
self.onSpinEnd = onSpinEnd
self.colors = colors ?? Color.spin_wheel_color
self.pointerColor = pointerColor ?? Color(hex: "DA4533")
self.strokeWidth = strokeWidth
self.strokeColor = strokeColor ?? Color(hex: "252D4F")
self.animDuration = animDuration
self.animation = animation ?? Animation.timingCurve(0.51, 0.97, 0.56, 0.99, duration: animDuration)
self.getWheelItemIndex = getWheelItemIndex
}
}
47 changes: 18 additions & 29 deletions Sources/FortuneWheel/FortuneWheelViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,29 @@ import SwiftUI
@available(macOS 10.15, *)
@available(iOS 13.0, *)
class FortuneWheelViewModel: ObservableObject {

private var titles: [String]

@Published var degree = 0.0
private let animDuration: Double
private var animation: Animation

private var pendingRequestWorkItem: DispatchWorkItem?

private var onSpinEnd: ((Int) -> ())?, getWheelItemIndex: (() -> (Int))?

init(
titles: [String], animDuration: Double, animation: Animation,
onSpinEnd: ((Int) -> ())?, getWheelItemIndex: (() -> (Int))?
) {
self.titles = titles
self.animDuration = animDuration
self.animation = animation
self.onSpinEnd = onSpinEnd
self.getWheelItemIndex = getWheelItemIndex
@Published var degree = 0.0

private let model: FortuneWheelModel

init(model: FortuneWheelModel) {
self.model = model
}

private func getWheelStopDegree() -> Double {
var index = -1;
if let method = getWheelItemIndex { index = method() }
if index < 0 || index >= titles.count { index = Int.random(in: 0..<titles.count) }
index = titles.count - index - 1;
if let method = model.getWheelItemIndex { index = method() }
if index < 0 || index >= model.titles.count { index = Int.random(in: 0..<model.titles.count) }
index = model.titles.count - index - 1;
/*
itemRange - Each items degree range (For 4, each will have 360 / 4 = 90 degrees)
indexDegree - No. of 90 degrees to reach i item
freeRange - Flexible degree in the item, so the pointer doesn't always point start of the item
freeSpins - No. of spins before it goes to selected item index
finalDegree - Final exact degree to spin and stop in the index
*/
let itemRange = 360 / titles.count;
let itemRange = 360 / model.titles.count;
let indexDegree = itemRange * index;
let freeRange = Int.random(in: 0...itemRange);
let freeSpins = (2...20).map({ return $0 * 360 }).randomElement()!
Expand All @@ -52,21 +41,21 @@ class FortuneWheelViewModel: ObservableObject {
}

func spinWheel() {
withAnimation(animation) {
withAnimation(model.animation) {
self.degree = Double(360 * Int(self.degree / 360)) + getWheelStopDegree();
}
// Cancel the currently pending item
pendingRequestWorkItem?.cancel()
// Wrap our request in a work item
let requestWorkItem = DispatchWorkItem { [weak self] in
if let count = self?.titles.count,
let distance = self?.degree.truncatingRemainder(dividingBy: 360) {
let pointer = floor(distance/(360/Double(count)))
if let onSpinEnd = self?.onSpinEnd { onSpinEnd(count - Int(pointer) - 1) }
}
guard let self = self else { return }
let count = self.model.titles.count
let distance = self.degree.truncatingRemainder(dividingBy: 360)
let pointer = floor(distance / (360 / Double(count)))
if let onSpinEnd = self.model.onSpinEnd { onSpinEnd(count - Int(pointer) - 1) }
}
// Save the new work item and execute it after duration
pendingRequestWorkItem = requestWorkItem
DispatchQueue.main.asyncAfter(deadline: .now() + animDuration + 1, execute: requestWorkItem)
DispatchQueue.main.asyncAfter(deadline: .now() + model.animDuration + 1, execute: requestWorkItem)
}
}

0 comments on commit 48d9ae9

Please sign in to comment.