How can I implement a generic struct that manages key-value pairs for UserDefaults in Swift?
You can use a custom
Property wrapper:
@propertyWrapper
struct UserDefaultStorage<T: Codable> {
private let key: String
private let defaultValue: T
private let userDefaults: UserDefaults
init(key: String, default: T, store: UserDefaults = .standard) {
self.key = key
self.defaultValue = `default`
self.userDefaults = store
}
var wrappedValue: T {
get {
guard let data = userDefaults.data(forKey: key) else {
return defaultValue
}
let value = try? JSONDecoder().decode(T.self, from: data)
return value ?? defaultValue
}
set {
let data = try? JSONEncoder().encode(newValue)
userDefaults.set(data, forKey: key)
}
}
}
This wrapper can store/restore any kind of codable into/from the user defaults.
Usage
@UserDefaultStorage(key: "myCustomKey", default: 0)
var myValue: Int
iOS 14
SwiftUI has a similar wrapper (only for iOS 14) called @AppStorage
and it can be used as a state. The advantage of using this is that it can be used directly as a State
. But it requires SwiftUI
and it only works from the iOS 14.
Allowing User to Create a Key and Value in Swift userdefaults
Considering you have a UITextField on your UI for entering both Song Name and BMP Value, with a reference to it in your code as songTextField and bmpTextField respectively, when return Key is Tapped or whenever you want to perform the operation, you can use the below code
if let songName = songTextField.text, let bmpValue = bmpTextField.text {
UserDefaults.standard.set(bmpValue, forKey: songName)
}
In Swift, how can I implement a generic system that processes a type by registering processors for different key paths?
I've created a playground showing how this can be solved with function composition and then using what we've learned there to recreate your example.
Function composition allows you to create new functions by chaining together existing ones, as long as the types match up.
precedencegroup CompositionPrecedence {
associativity: left
}
infix operator >>>: CompositionPrecedence
func >>> <T, U, V>(lhs: @escaping (T) -> U, rhs: @escaping (U) -> V) -> (T) -> V {
return { rhs(lhs($0)) }
}
The Processor
can be translated to a function that takes an object O
, transforms it in some way, and returns a new object. Creating the function could be done like this:
func process<O, K>(keyPath: WritableKeyPath<O, K>, _ f: @escaping (K) -> K) -> (O) -> O {
return { object in
var writable = object
writable[keyPath: keyPath] = f(object[keyPath: keyPath])
return writable
}
}
let reverseName = process(keyPath: \Person.name, reverse)
let halfAge = process(keyPath: \Person.age, half)
Now we can compose those two functions. The resulting functions still keeps the signature `(Person) -> Person). We can compose as many functions as we like, creating a processing pipeline.
let benjaminButton = reverseName >>> halfAge
let youngBradPitt = benjaminButton(bradPitt)
Moving on to recreating your example. As this answer mentions, the type is generic over the root object. This is just like in the function composition example, and it allows us to group all the processors in an array for example.
protocol Processor {
associatedtype T
func process(object: T) -> T
}
When erasing an object, it's important to keep a reference to the original, so that we can use it to implement the required functionality. In this case, we're keeping a reference to its process(:)
method.
extension Processor {
func erased()-> AnyProcessor<T> {
AnyProcessor(base: self)
}
}
struct AnyProcessor<T>: Processor {
private var _process: (T) -> T
init<Base: Processor>(base: Base) where Base.T == T {
_process = base.process
}
func process(object: T) -> T {
_process(object)
}
}
Here's two types implementing the Processor
protocol. Notice the first one has two placeholder types. The second placeholder will get erased.
struct AgeMultiplier<T, K: Numeric>: Processor {
let multiplier: K
let keyPath: WritableKeyPath<T, K>
private func f(_ value: K) -> K {
value * multiplier
}
func process(object: T) -> T {
var writable = object
writable[keyPath: keyPath] = f(object[keyPath: keyPath])
return writable
}
}
struct NameUppercaser<T>: Processor {
let keyPath: WritableKeyPath<T, String>
private func f(_ value: String) -> String {
value.uppercased()
}
func process(object: T) -> T {
var writable = object
writable[keyPath: keyPath] = f(object[keyPath: keyPath])
return writable
}
}
Finally, the ObjectProcessor
that uses object composition. Notice the array holds object of the same type. An instance of this struct will only be able to process Person
s for example. What each child processor does is hidden away in the implementation and the fact it may run on different kinds of data does not affect the ObjectProcessor
.
struct ObjectProcessor<T>: Processor {
private var processers = [AnyProcessor<T>]()
mutating func add(processor: AnyProcessor<T>) {
processers.append(processor)
}
func process(object: T) -> T {
var object = object
for processor in processers {
object = processor.process(object: object)
}
return object
}
}
And here it is in action. Notice I add two processors for the same key.
var holyGrail = ObjectProcessor<Person>()
holyGrail.add(processor: NameUppercaser(keyPath: \Person.name).erased())
holyGrail.add(processor: AgeMultiplier(multiplier: 2, keyPath: \Person.age).erased())
holyGrail.add(processor: AgeMultiplier(multiplier: 3, keyPath: \Person.age).erased())
let bradPitt = Person(name: "Brad Pitt", age: 57)
let immortalBradPitt = holyGrail.process(object: bradPitt)
How to retrieve values from settings.bundle in Objective-c/Swift?
Although you define the defaults settings, they are not really stored as a value. They are stored as default. If you try to read it, the value is null. Default setting is another property as value is. But it doesnt mean that will write the default value as a default.
What I do is, first, check if some setting,(that I'm sure that should have a value) has anything stored on it. If it doesn't have anything then I write all the defaults.
Here is an example.
on AppDelegate.m I check if email_notificaciones_preference has a value, if not, I write ALL default settings to each setting.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSUserDefaults * standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString * email_notificaciones_preference = [standardUserDefaults objectForKey:@"email_notificaciones_preference"];
if (!email_notificaciones_preference) {
[self registerDefaultsFromSettingsBundle];
}
}
This function is what I use to write defaults to each element.
#pragma NSUserDefaults
- (void)registerDefaultsFromSettingsBundle {
// this function writes default settings as settings
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
if(!settingsBundle) {
NSLog(@"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];
NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:@"Key"];
if(key) {
[defaultsToRegister setObject:[prefSpecification objectForKey:@"DefaultValue"] forKey:key];
NSLog(@"writing as default %@ to the key %@",[prefSpecification objectForKey:@"DefaultValue"],key);
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}
Hope that helps.
Trying to save custom object in UserDefaults using NSKeyedArchiver
Use JSONEncoder to save data to UserDefaults. Dummy code:
class YourClassName: Codable {
//Your properties here
func saveInPreference() {
do {
let jsonEncoder = JSONEncoder()
UserDefaults.standard.set(try jsonEncoder.encode(Your object here), forKey: "Write UserDefaultKey here")
UserDefaults.standard.synchronize()
}
catch {
print(error.localizedDescription)
}
}
}
Hope that helps!
@Faraz A. Khan comment if you have any questions in the above piece of code.
How to Unit-Test with global structs?
First, LevelView looks like it has too much logic in it. The point of a view is to display model data. It's not to include business logic like GameStatus.xp > 95
. That should be done elsewhere and set into the view.
Next, why is GameStatus static? This is just complicating this. Pass the GameStatus to the view when it changes. That's the job of the view controller. Views just draw stuff. If anything is really unit-testable in your view, it probably shouldn't be in a view.
Finally, the piece that you're struggling with is the user defaults. So extract that piece into a generic GameStorage.
protocol GameStorage {
var xp: Int { get set }
var level: Int { get set }
}
Now make UserDefaults a GameStorage:
extension UserDefaults: GameStorage {
var xp: Int {
get { /* Read from UserDefaults */ return ... }
set { /* Write to UserDefaults */ }
}
var level: Int {
get { /* Read from UserDefaults */ return ... }
set { /* Write to UserDefaults */ }
}
}
And for testing, create a static one:
struct StaticGameStorage: GameStorage {
var xp: Int
var level: Int
}
Now when you create a GameStatus, pass it storage. But you can give that a default value, so you don't have to pass it all the time
class GameStatus {
private var storage: GameStorage
// A default parameter means you don't have to pass it normally, but you can
init(storage: GameStorage = UserDefaults.standard) {
self.storage = storage
}
With that, xp and level can just pass through to storage. No need for a special "load the storage now" step.
private(set) var xp: Int {
get { return storage.xp }
set { storage.xp = newValue }
}
private(set) var level: Int {
get { return storage.level }
set { storage.level = newValue }
}
EDIT: I made a change here from GameStatus being a struct to a class. That's because GameStatus lacks value semantics. If there are two copies of GameStatus, and you modify one of them, the other may change, too (because they both write to UserDefaults). A struct without value semantics is dangerous.
It's possible to regain value semantics, and it's worth considering. For example, instead of passing through xp and level to the storage, you could go back to your original design that has an explicit "restore" step that loads from storage (and I assume a "save" step that writes to storage). Then GameStatus would be an appropriate struct.
I'd also extract LevelState so that you can more easily test it and it captures the business logic outside of the view.
enum LevelState {
case showStart
case showCountdown
case showFinalCountDown
init(xp: Int) {
if xp > 95 {
self = .showFinalCountDown
} else if xp > 90 {
self = .showCountdown
}
self = .showStart
}
}
If this is only ever used by this one view, it's fine to nest it. Just don't make it private. You can test LevelView.LevelState without having to do anything with LevelView itself.
And then you can update the view's GameStatus as you need to:
class LevelView: UIView {
var gameStatus: GameStatus? {
didSet {
// Refresh the view with the new status
}
}
var state: LevelState {
guard let xp = gameStatus?.xp else { return .showStart }
return LevelState(xp: xp)
}
//...configurations depending on the level
}
Now the view itself doesn't need logic testing. You might do image-based testing to make sure it draws correctly given different inputs, but that's completely end-to-end. All the logic is simple and testable. You can test GameStatus and LevelState without UIKit at all by passing a StaticGameStorage to GameStatus.
How can I use UserDefaults in Swift?
ref: NSUserdefault objectTypes
Swift 3 and above
Store
UserDefaults.standard.set(true, forKey: "Key") //Bool
UserDefaults.standard.set(1, forKey: "Key") //Integer
UserDefaults.standard.set("TEST", forKey: "Key") //setObject
Retrieve
UserDefaults.standard.bool(forKey: "Key")
UserDefaults.standard.integer(forKey: "Key")
UserDefaults.standard.string(forKey: "Key")
Remove
UserDefaults.standard.removeObject(forKey: "Key")
Remove all Keys
if let appDomain = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: appDomain)
}
Swift 2 and below
Store
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: "yourkey")
NSUserDefaults.standardUserDefaults().synchronize()
Retrieve
var returnValue: [NSString]? = NSUserDefaults.standardUserDefaults().objectForKey("yourkey") as? [NSString]
Remove
NSUserDefaults.standardUserDefaults().removeObjectForKey("yourkey")
Register
registerDefaults: adds the registrationDictionary to the last item in every search list. This means that after NSUserDefaults has looked for a value in every other valid location, it will look in registered defaults, making them useful as a "fallback" value. Registered defaults are never stored between runs of an application, and are visible only to the application that registers them.
Default values from Defaults Configuration Files will automatically be registered.
for example detect the app from launch , create the struct for save launch
struct DetectLaunch {
static let keyforLaunch = "validateFirstlunch"
static var isFirst: Bool {
get {
return UserDefaults.standard.bool(forKey: keyforLaunch)
}
set {
UserDefaults.standard.set(newValue, forKey: keyforLaunch)
}
}
}
Register default values on app launch:
UserDefaults.standard.register(defaults: [
DetectLaunch.isFirst: true
])
remove the value on app termination:
func applicationWillTerminate(_ application: UIApplication) {
DetectLaunch.isFirst = false
}
and check the condition as
if DetectLaunch.isFirst {
// app launched from first
}
UserDefaults suite name
another one property suite name, mostly its used for App Groups concept, the example scenario I taken from here :
The use case is that I want to separate my UserDefaults (different business logic may require Userdefaults to be grouped separately) by an identifier just like Android's SharedPreferences. For example, when a user in my app clicks on logout button, I would want to clear his account related defaults but not location of the the device.
let user = UserDefaults(suiteName:"User")
use of userDefaults synchronize, the detail info has added in the duplicate answer.
JSON encode/decode Swift CNContact object generically
No, CNContact
hasn't changed according to the official documentation, so it doesn't conform to the Codable
protocol, which is just a typealias for Encodeable & Decodable
. You can see the list of classes currently conforming to Encodable and Decodable and see here as well that CNContact
is not amongst them.
However, you can write an extension for CNContact
to make it conform to above protocols.
Here is an example of how to encode a CNContact
object using JSONSerialization
framework in Swift3
. Please note that this is just an example, so I haven't parsed all possible fields and with this implementation if a certain value doesn't exist in the CNContact
object, the key doesn't exist in the JSON
either. Also, the decoder function is not implemented fully, but you can easily implement it if you check how the encoder works.
The names of the JSON
keys were also chosen arbitrarily along with the structure, so you can change any of those.
Below piece of code is a full, working playground file, so you can test it yourself if you want to.
import Contacts
let contact = CNMutableContact()
contact.birthday = DateComponents(calendar: Calendar.current,year: 1887, month: 1, day: 1)
contact.contactType = CNContactType.person
contact.givenName = "John"
contact.familyName = "Appleseed"
contact.imageData = Data() // The profile picture as a NSData object
let homeEmail = CNLabeledValue(label:CNLabelHome, value: NSString(string: "john@example.com"))
let workEmail = CNLabeledValue(label:CNLabelWork, value: NSString(string: "j.appleseed@icloud.com"))
contact.emailAddresses = [homeEmail, workEmail]
contact.phoneNumbers = [CNLabeledValue(label:CNLabelPhoneNumberiPhone, value:CNPhoneNumber(stringValue:"(408) 555-0126"))]
let homeAddress = CNMutablePostalAddress()
homeAddress.street = "1 Infinite Loop"
homeAddress.city = "Cupertino"
homeAddress.state = "CA"
homeAddress.postalCode = "95014"
contact.postalAddresses = [CNLabeledValue(label:CNLabelHome, value:homeAddress)]
func encodeContactToJson(contact: CNContact)->Data?{
var contactDict = [String:Any]()
if let birthday = contact.birthday?.date {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
contactDict["birthday"] = df.string(from: birthday)
}
contactDict["givenName"] = contact.givenName
contactDict["familyName"] = contact.familyName
if let imageData = contact.imageData {
contactDict["image"] = imageData.base64EncodedString()
}
if contact.emailAddresses.count > 0 {
var emailAddresses = [String:String]()
for (index, emailAddress) in contact.emailAddresses.enumerated() {
emailAddresses[emailAddress.label ?? "email\(index)"] = (emailAddress.value as String)
}
contactDict["emailAddresses"] = emailAddresses
}
if contact.phoneNumbers.count > 0 {
var phoneNumbers = [String:String]()
for (index, phoneNumber) in contact.phoneNumbers.enumerated() {
phoneNumbers[phoneNumber.label ?? "phone\(index)"] = phoneNumber.value.stringValue
}
contactDict["phoneNumbers"] = phoneNumbers
}
if contact.postalAddresses.count > 0 {
var postalAddresses = [String:String]()
for (index, postalAddress) in contact.postalAddresses.enumerated() {
postalAddresses[postalAddress.label ?? "postal\(index)"] = (CNPostalAddressFormatter.string(from: postalAddress.value, style: .mailingAddress))
}
contactDict["postalAddresses"] = postalAddresses
}
return try? JSONSerialization.data(withJSONObject: contactDict)
}
func decodeContactsJson(jsonData: Data)->CNContact?{
if let jsonDict = (try? JSONSerialization.jsonObject(with: jsonData)) as? [String:Any] {
let contact = CNMutableContact()
print(jsonDict)
return contact as CNContact
} else {
return nil
}
}
if let jsonContact = encodeContactToJson(contact: contact) {
print(decodeContactsJson(jsonData: jsonContact) ?? "Decoding failed")
} else {
print("Encoding failed")
}
Related Topics
Can Somebody Post a Good Example of MVC Pattern in Swift
Sidebar Menu for MACos in Swiftui
Swift: +[Catransaction Synchronize] Called Within Transaction While Decoding HTML Entities
Easiest Way to Increment a Data Point in Firebase
Dyld: Library Not Loaded: @Rpath/Libswiftdispatch.Dylib
How to Change Its Own Button Image on Tap in Swift
Calling Stop() on Avaudioplayernode After Finished Playing Causes Crash
Why Does This Swiftui Picker Code Not Work
Compound Key in Realm with Lazy Property
Xcode - Build Setting "Excluded_Source_File_Names" Not Working
How to Set Alignment for Wkinterface Label Using Setattributedtext
Declare Trivial Protocol Conformance for Struct in a Framework
Using Iboutlet from Another Class in Swift
Adding Index List and Section Headers to Translated Tableview
Swift Ui: Center Text into Circle