I am trying to write a property wrapper to access userDefaults but have two problems. The code snippet is as below
User Default wrapper
@propertyWrapper
struct UserDefault<T> {
private let key: String
private let defaultValue: T
private let userDefaults: UserDefaults
init(key: String, defaultValue: T, userDefaults: UserDefaults = .standard) {
self.key = key
self.defaultValue = defaultValue
self.userDefaults = userDefaults
}
var wrappedValue: T {
get {
return userDefaults.object(forKey: key) as? T ?? defaultValue
}
set {
userDefaults.set(newValue, forKey: key)
}
}
}
Observable Object
final class AppSettings: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
@UserDefault(key: "focusSliderValue", defaultValue: 25.0)
var focusSliderValue: TimeInterval {
willSet {
objectWillChange.send()
}
}
@UserDefault(key: "shortBreakLength", defaultValue: 5.0)
var shortBreakLength: TimeInterval {
willSet {
objectWillChange.send()
}
}
@UserDefault(key: "longBreakLength", defaultValue: 25.0)
var longBreakLength: TimeInterval {
willSet {
objectWillChange.send()
}
}
@UserDefault(key: "sessionsPerRound", defaultValue: 4)
var sessionsPerRound: Int {
willSet {
objectWillChange.send()
}
}
}
The above code works but have the following issues
Problem 1:
I am trying to replace most of the boiler plate code with @Published keyword but when i do that I get "Value of type 'Double' has no member 'wrappedValue'" error which I am unable to understand/fix.
Problem 2:
Keeping the code as is, I am using a stepper to update "focusSliderValue" on my settings screen which gets updated on my Root View. If I press the stepper once and immediately go back to the root view get updated properly but if I press the stepper say 10 times then go back to the root view I see the screen with "..." (updating) symbol then the field gets updated after few secs. Why is this transition / observable object not smooth ?
Content View For problem 2
struct ContentView: View {
@State var to : CGFloat = 1
@State private var isAnimating = false
@ObservedObject var appSettings = AppSettings()
let gradientColors = Gradient(colors: [Color.blue, Color.purple])
var body: some View {
NavigationView {
ZStack{
VStack(spacing: 50) {
ZStack {
Circle().trim(from: 0, to: 1)
.stroke(Color.black.opacity(0.09), style: StrokeStyle(lineWidth: 10, lineCap: .round))
.frame(width: 200, height: 200)
Circle()
.trim(from: 0, to: self.isAnimating ? self.to : 0)
.stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .round))
.frame(width: 200, height: 200)
.rotationEffect(.init(degrees: -90))
.overlay(Text("\(Int(appSettings.focusSliderValue))").font(.title), alignment: .center)
.animation(Animation.linear(duration: 5).repeatCount(0, autoreverses: false))
}
Button(action: {self.isAnimating.toggle()}, label: {
Text("Start")
.font(.body)
.foregroundColor(.black)
.padding(40)
}).padding()
.clipShape(Circle())
.overlay(Circle().stroke(Color.black, style: StrokeStyle(lineWidth: 2, lineCap: .round)))
}.padding()
}.navigationBarTitle(Text("Pomodoro Timer"), displayMode: .automatic)
.navigationBarItems(trailing: NavigationLink(destination: SettingsView(appSettings: appSettings), label: {
Image(systemName: "gear")
.font(.title)
.foregroundColor(.black)
}))
}
}
}
struct SettingsView: View {
@ObservedObject var appSettings: AppSettings
var body: some View {
Form {
Section(header: Text("TIMER LENGTH")) {
HStack {
Stepper(value: $appSettings.focusSliderValue , in: 0...60, step: 1.0) {
Text("Focus Length : \($appSettings.focusSliderValue.wrappedValue)" + " mins")
.fontWeight(.bold)
.foregroundColor(.black)
}
}
HStack {
Stepper(value: $appSettings.shortBreakLength) {
Text("Short break Length : \(($appSettings.shortBreakLength.wrappedValue))" + " mins")
.fontWeight(.bold)
.foregroundColor(.black)
}
}
HStack {
Stepper(value: $appSettings.longBreakLength) {
Text("Long break Length : \(($appSettings.longBreakLength.wrappedValue))" + " mins")
.fontWeight(.bold)
.foregroundColor(.black)
}
}
}.font(.caption)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(appSettings: AppSettings())
}
}
Edit: Updated gift for problem 2