When developing UIKit apps it's quite easy to mock the UserDefaults by defining a protocol and injecting the needed implementation in the UIViewController.
protocol Defaults {
var numberOfHandsPlayed: Int { get set }
}
struct AppDefaults: Defaults {
static let shared = AppDefaults()
private let userDefaults = UserDefaults.standard
struct Keys {
private init() {}
static let numberOfHandsPlayed = "numberOfHandsPlayed"
}
var numberOfHandsPlayed: Int {
get {
userDefaults.integer(forKey: Keys.numberOfHandsPlayed)
}
set(numberOfHandsPlayed) {
userDefaults.setValue(numberOfHandsPlayed, forKey: Keys.numberOfHandsPlayed)
}
}
}
struct MockDefaults: Defaults {
var numberOfHandsPlayed: Int {
get {
// mocking behaviour
}
set(numberOfHandsPlayed) {
// mocking behaviour
}
}
class PracticeViewController: UIViewController {
private var defaults: Defaults?
// defaults can be set to MockDefaults or AppDefaults
}
But now with SwiftUI I can do the following using the #AppStorage property wrapper:
#AppStorage("numberOfHandsPlayed") var numberOfHandsPlayed: Int = 3
Is there a clean solution in SwiftUI to mock this property wrapper and have the same flexibility as in my UIKit example?
The AppStorage allows to specify storage to be used (by default standard UserDefaults), so this feature can be utilised for your purpose.
One of possible approaches is to subclass standard user defaults, and mock it later if/where needed.
class MyUserDefaults: UserDefaults {
// override/add anything needed here
}
struct DemoView: View {
#AppStorage("numberOfHandsPlayed", store: MyUserDefaults()) var numberOfHandsPlayed: Int = 3
// ... other code
Related
Im trying so save persisting data to my Userdefault storage so I can use it inside my extension.
Question
How do I implement this so I can update my view(update value of toggle) when another target is run, in my case an extension. I created the same app group. For my userdefaults
App is structured like this first my UserDefaults implementation
extension UserDefaults {
static let group = UserDefaults(suiteName: "group.com.carlpalsson.superapp")
func save<T: Codable>(_ object: T, forKey key: String) {
let encoder = JSONEncoder()
if let encodedObject = try? encoder.encode(object) {
UserDefaults.group?.set(encodedObject, forKey: key)
UserDefaults.standard.synchronize()
}
}
func getObject<T: Codable>(forKey key: String) -> T? {
if let object = UserDefaults.group?.object(forKey: key) as? Data {
let decoder = JSONDecoder()
if let decodedObject = try? decoder.decode(T.self, from: object) {
return decodedObject
}
}
return nil
}
}
class UserSettings : ObservableObject {
let test = FamilyActivitySelection()
#Published var discouragedAppsCategoryTokens : Set<ActivityCategoryToken> {
didSet {
UserDefaults.group?.save(discouragedAppsCategoryTokens, forKey:"DiscourageAppsCategoryTokens")
}
}
init() {
self.discouragedAppsCategoryTokens =
(UserDefaults.group?.getObject(forKey: "DiscourageAppsCategoryTokens")) ?? appcategorytokens
}
static var shared: UserSettings {
return _userSettings
}
}
In my extension
class MyDeviceActivityMonitor: DeviceActivityMonitor {
let store = ManagedSettingsStore()
let userSettings = UserSettings.shared
let korven = UserSettings()
override func intervalDidStart(for activity: DeviceActivityName) {
do{
//Im trying to my values here but it´s always null
var family = userSettings.DiscouragedAppsFamilyActivitySelection
var familys: FamilyActivitySelection? = UserDefaults.group?.getObject(forKey: "DiscouragedAppsFamilyActivitySelection")
var iii = korven.DiscouragedAppsFamilyActivitySelection
}
}
Inside my #main
#StateObject var userSettings = UserSettings.shared
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(model)
.environmentObject(store)
.environmentObject(userSettings)
}
}
And in my view
struct ContentView: View {
#EnvironmentObject var userSettings : UserSettings
VStack {
Button("Select Apps to Discourage") {
isDiscouragedPresented = true
}
.familyActivityPicker(isPresented: $isDiscouragedPresented, selection: $userSettings.DiscouragedAppsFamilyActivitySelection)
.onChange(of: userSettings.DiscouragedAppsFamilyActivitySelection) { newSelection in
UserSettings.shared.DiscouragedAppsFamilyActivitySelection = newSelection
MyModel.shared.startDiscourageApps()
// MySchedule.setSchedule()
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(MyModel())
.environmentObject(UserSettings())
}
}
If I understand you correctly you want to update your UI in your extension when the user defaults value changes in your app. This certainly is possible, but requires some more code on your side.
In your UserSettings class you currently read the user defaults in the initializer and then leave it as is. To get your UI to update you also need to update your property in there when the actual user defaults change. To do this you need to observe the user defaults. The easy way would be using the Notification Center with the NSUserDefaultsDidChangeNotification. In your case this won’t work as this notification only is sent for changes made in the same process.
Changes from a different process can be observed using Key-Value-Observing (KVO) though. Unfortunately this seems to be impossible using the nice Swift KeyPath API. Instead you have to do that using the Objective-C version. To do this you would make your UserSettings class inherit NSObject and implement observeValue(forKeyPath:of:change:context:). In there you can read the new data from the user defaults. Then you can add your object as an observer on the user defaults using addObserver(_:forKeyPath:options:context:). Options can stay empty and context can be nil.
class ShareData {
class var sharedInstance: ShareData {
struct Static {
static var instance: ShareData?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = ShareData()
}
return Static.instance!
}
var someString : String! //Some String
var selectedTheme : AnyObject! //Some Object
var someBoolValue : Bool!
}
This is my singleton design.However , I want to know how I can clear all its data as and when required?
Also can i have more than one singleton Class??
Since you've only got 3 properties on your singleton it would be far easier just to set up a method that nils each property in turn.
Once you start getting in to how to destroy and recreate your singleton, you get in to the realm of do you actually even want a singleton or should you just be using a regular object.
You are creating a Singleton with the syntax available in... 2014
Today there's a better syntax to define a Singleton class
final class SharedData {
static let sharedInstance = SharedData()
private init() { }
var someString: String?
var selectedTheme: AnyObject?
var someBoolValue: Bool?
func clear() {
someString = nil
selectedTheme = nil
someBoolValue = nil
}
}
As you can see I also added the clearData() method you were looking for.
I am trying to write a simple property observer that can be used to see updates to a type - ie a public form of didSet. The basic implementation was easy enough, but I then wanted to use it for both strong (value and reference) types and weak (reference) types. The motivation for the latter was to serve as a lazy caching strategy wherein a shared resource would remain in use until the last observer freed it, whereupon it would query for the value again - generally from NSURLCache, but otherwise a remote server. In short, I wanted a transparent multi-tiered cache.
I have done things like this in C++, where I had stored either a type, or a type wrapped in smart pointer, through the use of a trait with typedef's for Type, StoredType, etc, so I could pass a trait for value types, or a trait for ref-counted pointer types etc.
From my understanding of Swift though, the unowned and weak modifiers are applied to the property and not to the type per se, so you can't for example do something like:
typealias StorageType = weak T
To work around this limitation, I abstracted my generic type T to always use a storage container, where the container could use either the direct type, or a weak reference to what would have to be a class-based AnyClass type. (This effort itself was cludged by the fact that you can't override assignment operators and that conversion functions were abandoned in the early betas)
Frustratingly, I ran into compiler bugs where it just segfaulted.
Given that I can't be the first person to have tried to solve this type of problem, has anybody found a clean way through it? Obviously the segfault is just another compiler bug, but perhaps there is a simpler solution?
For reference, my code (with the segfault inducing code in comments) is:
public class ObserverSubscription : Hashable {
// public (hashable)
public var hashValue: Int { return token }
// private
private init(token:Int, observable:ObservableProtocol) {
self.token = token
self.observable = observable
}
deinit {
self.observable.unsubscribe(self)
}
private var token:Int
private var observable:ObservableProtocol
}
public func ==(lhs: ObserverSubscription, rhs: ObserverSubscription) -> Bool {
return lhs.hashValue == rhs.hashValue
}
public protocol Storage {
typealias T
typealias StorageType
init(t:StorageType)
var element:StorageType { get set }
}
public class Weak<Type:AnyObject> : Storage {
typealias T = Type
typealias StorageType = T?
public required init(t:StorageType) { element = t }
public weak var element:StorageType
}
public class Strong<Type> : Storage{
typealias T = Type
typealias StorageType = T
public required init(t:StorageType) { element = t }
public var element:StorageType
}
public protocol ObservableProtocol {
func unsubscribe(subscription:ObserverSubscription)
}
public class Observable<T, Container:Storage where T == Container.T> : ObservableProtocol {
public typealias StorageType = Container.StorageType
public typealias Subscription = ObserverSubscription
public typealias ChangeNotifier = (Container.StorageType) -> ()
public init(_ t:Container.StorageType) {
self.value = Container(t:t)
}
public init(_ source:Observable<T, Container>) {
self.value = Container(t:source.value.element)
}
public func subscribe(notifier:ChangeNotifier) {
let subscription = Subscription(token: token++, observable: self)
subscriptions[subscription] = notifier
}
public func unsubscribe(subscription:Subscription) {
subscriptions.removeValueForKey(subscription)
}
public func subscription(notifier:(Container.StorageType) -> ()) -> Subscription {
let subscription = Subscription(token: token++, observable: self)
subscriptions[subscription] = notifier
return subscription
}
public func update(t:Container.StorageType) {
self.value.element = t
}
public private(set) var value:Container { didSet {
for n in subscriptions.keys {
self.subscriptions[n]!(self.value.element)
}}}
private var token:Int = 0
private var subscriptions: [Subscription: ChangeNotifier] = [:]
}
public class ValueObserver<T> : Observable<T, Strong<T>> {
override init(_ t:StorageType) {
super.init(t)
}
}
// dare ye segfault?
//public class WeakObserver<T:AnyObject> : Observable<T, Weak<T>> {
// override init(_ t:StorageType) {
// super.init(t)
// }
//}
My actual code was a little more involved, I derived a lazily loading class from the observer, but this too, induced a segfault:
//public class LazyLoader<T:AnyObject> : Observable<T, Weak<T>> {
//
// override init(_ t:StorageType) {
// super.init(t)
// }
//
// // do lazy loading here
//}
I have tested the observer in isolation, outside of the missing-trait work around, but has been many characters since I tested it. My goal at this point is to find a solution that compiles (and which could hopefully be simpler)
Thanks!
I need to subclass NSFileManager to add some need functions to it.
I created a subclass, added the functions and private variables, now I want to access the functions from another class like this. MyFileManager.defaultManager().awesomeFunction("Test")
Here is my code:
import UIKit
class AwesomeFileManager: NSFileManager {
private let awesomeLet = ["let1", "let2", "let3"]
func awesomeFunction(parameter: String) -> Bool! {
return true
}
}
Somehow I can not access the function from another class. What am I doing wrong?
I am using Swift 2.0
Override defaultManager and make sure it returns an instance of your class.
To avoid the subclassing of methods you need to access the new method, that task is better handled with an extension:
extension NSFileManager {
private struct AssociatedKey {
static var awesomeLet: [String]?
}
var awesomeLet: [String]? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.awesomeLet) as? [String]
}
set {
if let value = newValue {
objc_setAssociatedObject(self, &AssociatedKey.awesomeLet, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
}
func awesomeFunction(parameter: String) -> Bool! {
return true
}
}
Because you can't add variables to an extensions, I gave you the code to add a associated key to store/retrieve any objects you want to add the extension.
I'm an android app developer and a beginner in swift. I'm trying to implement a singleton class whose data members are shared throughout the app (like Settings).
Getting this done in android is pretty simple but I'm breaking my head to do it in swift.
Below is the code I've tried ..
public class DataSet
{
public var notificationOnOff: Bool!
public var interval: Int!
public var alert: String!
init()
{
self.notificationOnOff = true
self.interval = 1;
self.alert = nil;
}
init (onOff: Bool) {
self.notificationOnOff = onOff
}
init (time: Int) {
self.interval = time
}
init (stop: String) {
self.alert = stop
}
}
This class implementation couldn't persist the data.
Is this the right way of doing it?
EDIT
For example, when I click switch in Settings view controller, I'm setting notificationOnOff like ..
dataset.notificationOnOff = DataSet(onOff: true) // value is set and able to print it
and using this value in another class like...
if dataset.notificationOnOff
{
// do some stuff
}
So basically, the value is persisting only within the Setting class but not when I switch to other class.
Solved!
I have used the below code to successfully implement this..
var instance: DataSet?
class Dataset {
...
class var sharedInstance: DataSet {
struct Static {
static var instance: DataSet?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = DataSet()
}
return Static.instance!
}
}
and in the other classes ..
dataset = Dataset.sharedInstance;