Created
April 22, 2020 17:00
-
-
Save kylpo/915b3f5d73b7d656c0968f18724f95e1 to your computer and use it in GitHub Desktop.
@userdefault Property Wrapper
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 Foundation | |
import Combine | |
enum TimeFrameOption: String, CaseIterable { | |
case today | |
case week | |
case month | |
case year | |
case total | |
} | |
enum ThemeOption: String, CaseIterable { | |
case light | |
case dark | |
case black | |
} | |
enum StartingDayOfWeekOption: String, CaseIterable { | |
case saturday | |
case sunday | |
case monday | |
} | |
private let themeKey = "theme" | |
private let timeFrameKey = "timeFrame" | |
private let startingDayOfWeekKey = "startingDayOfWeek" | |
final class Settings: ObservableObject { | |
static let defaultTheme = ThemeOption.light | |
static let defaultTimeFrame = TimeFrameOption.total | |
static let defaultStartingDayOfWeek = StartingDayOfWeekOption.sunday | |
// this is needed to conform to Subject and allow subscribe() | |
let objectWillChange = PassthroughSubject<Void, Never>() | |
private var didChangeCancellable: AnyCancellable? | |
private var storage: UserDefaults = .standard | |
// Commenting @UserDefault usage, because passing in `storage` does not work. | |
// This means it isn't testable :( | |
// | |
// @UserDefault(key: timeFrameKey, defaultValue: defaultTimeFrame) | |
// var timeFrame: TimeFrameOption | |
// | |
// Also note that the current approach of just using functions is totally fine, AND testable. | |
var theme: ThemeOption { | |
get { return getEnumValue(forKey: themeKey, defaultValue: Settings.defaultTheme) } | |
set { setEnumValue(newValue, forKey: themeKey) } | |
} | |
var timeFrame: TimeFrameOption { | |
get { return getEnumValue(forKey: timeFrameKey, defaultValue: Settings.defaultTimeFrame) } | |
set { setEnumValue(newValue, forKey: timeFrameKey) } | |
} | |
var startingDayOfWeek: StartingDayOfWeekOption { | |
get { return getEnumValue(forKey: startingDayOfWeekKey, defaultValue: Settings.defaultStartingDayOfWeek) } | |
set { setEnumValue(newValue, forKey: startingDayOfWeekKey)} | |
} | |
init(storage: UserDefaults = .standard) { | |
self.storage = storage | |
// from https://swiftwithmajid.com/2019/09/04/modeling-app-state-using-store-objects-in-swiftui/ | |
didChangeCancellable = NotificationCenter.default | |
.publisher(for: UserDefaults.didChangeNotification) | |
.map { _ in () } // clear data since it isn't needed/used | |
.receive(on: DispatchQueue.main) | |
.subscribe(objectWillChange) // ping subscribers to get new values | |
} | |
private func setEnumValue<T: RawRepresentable>(_ newValue: T, forKey key: String) where T.RawValue == String { | |
self.objectWillChange.send() | |
storage.set(newValue.rawValue, forKey: themeKey) | |
} | |
private func getEnumValue<T: RawRepresentable>(forKey key: String, defaultValue: T) -> T where T.RawValue == String { | |
if let string = storage.string(forKey: themeKey) { | |
return T(rawValue: string) ?? defaultValue | |
} | |
return defaultValue | |
} | |
private func getValue<T>(forKey key: String, defaultValue: T) -> T { | |
return storage.object(forKey: key) as? T ?? defaultValue | |
} | |
private func setValue<T>(_ newValue: T, forKey key: String) { | |
self.objectWillChange.send() | |
storage.set(newValue, forKey: key) | |
} | |
} |
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 XCTest | |
import Combine | |
@testable import // insert_yout_module_name | |
final class TestUserDefaultPropertyWrapper: XCTestCase { | |
func test_UserDefault_property_wrapper_default() { | |
let mockSettings = MockSettings() | |
XCTAssertEqual(MockSettings.value, mockSettings.testSetting) | |
} | |
func test_UserDefault_property_wrapper_setter() { | |
// given | |
let mockSettings = MockSettings() | |
XCTAssertEqual(MockSettings.value, mockSettings.testSetting) | |
// when | |
let newSettingsValue = "beep beep, we're the sheep" | |
mockSettings.testSetting = newSettingsValue | |
// then | |
XCTAssertEqual(newSettingsValue, mockSettings.testSetting) | |
} | |
} | |
private final class MockSettings { | |
static let value = "value" | |
@UserDefault(key: "key", defaultValue: MockSettings.value, storage: UserDefaults(suiteName: #file)!) | |
var testSetting: String | |
init() { | |
UserDefaults(suiteName: #file)?.removePersistentDomain(forName: #file) | |
} | |
} |
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 Foundation | |
/** | |
``` | |
@UserDefault(key: "key", defaultValue: "value") | |
var theme: String | |
``` | |
*/ | |
@propertyWrapper | |
struct UserDefault<T> { | |
var key: String | |
var defaultValue: T | |
var storage: UserDefaults = .standard | |
var wrappedValue: T { | |
set { storage.set(newValue, forKey: key) } | |
get { storage.object(forKey: key) as? T ?? defaultValue } | |
} | |
} | |
// | |
//Commenting out because I haven't tested/used this. | |
//From https://github.com/Dimillian/ACHNBrowserUI/blob/411aacbde39efd4d734bd514291b5ea6f9810048/ACHNBrowserUI/ACHNBrowserUI/wrappers/UserDefaultWrapper.swift#L32 | |
// | |
// | |
//@propertyWrapper | |
//public struct UserDefaultEnum<T: RawRepresentable> where T.RawValue == String { | |
// let key: String | |
// let defaultValue: T | |
// | |
// init(_ key: String, defaultValue: T) { | |
// self.key = key | |
// self.defaultValue = defaultValue | |
// } | |
// | |
// public var wrappedValue: T { | |
// get { | |
// if let string = UserDefaults.standard.string(forKey: key) { | |
// return T(rawValue: string) ?? defaultValue | |
// } | |
// return defaultValue | |
// } | |
// set { | |
// UserDefaults.standard.set(newValue.rawValue, forKey: key) | |
// } | |
// } | |
//} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment