Issue#972
We want to have a swifty UserDefaults API that works with subscript and in a type safe manner.
extensionDefaults.Keys{staticletstring=Defaults.Key("string",default:"0")}XCTAssertEqual(defaults[.string],"0")defaults[.string]="1"XCTAssertEqual(defaults[.string],"1")
UserDefaults plist compatibility
DefineCompatible
protocol that allows value to be plist compatible
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
publicprotocolCompatible:Equatable{}extensionInt:Compatible{}extensionString:Compatible{}extensionBool:Compatible{}extensionDate:Compatible{}extensionArray:CompatiblewhereElement:Compatible{}extensionDictionary:CompatiblewhereKey:Compatible,Value:Compatible{}
Next, defineDefaults
that acceptsUserDefaults
as initialize dependency, so that we can swap UserDefaults.
Key with generic type
We defineKey
with phantom typeValue
so we know which value this key is pointing to, this makes it easier to reason about the code.
Since Swift has limitationStatic stored properties not supported in generic types
, we can’t extend ourKey
with static stored properties, we have to do via computed property
extensionDefaults.Key{staticvarstring:Defaults.Key<String>{.init("string",default:"0")}}
This works, but does not look nice. To workaround this, we define classAnyKey
and make ourKey
class as well and inherited thisAnyKey
class.
Make a typealiastypealias Keys = AnyKey
so we can refer toDefaults.Keys
when we define our keys.
publicclassDefaults{publicvarsuite:UserDefaultspublicinit(suite:UserDefaults=.standard){self.suite=suite}publicsubscript<Value:Compatible>(key:Key<Value>)->Value{get{ifletvalue=suite.object(forKey:key.name)as?Value{returnvalue}returnkey.defaultValue}set{suite.set(newValue,forKey:key.name)}}publicfuncexists<Value:Compatible>(key:Key<Value>)->Bool{suite.object(forKey:key.name)!=nil}}extensionDefaults{publictypealiasKeys=AnyKeypublicclassKey<Value:Compatible>:AnyKey{vardefaultValue:Valuepublicinit(_name:String,defaultdefaultValue:Value){self.defaultValue=defaultValuesuper.init(name:name)}}publicclassAnyKey{varname:Stringinit(name:String){self.name=name}}}extensionDefaults.AnyKey:Equatable{publicstaticfunc==(lhs:Defaults.AnyKey,rhs:Defaults.AnyKey)->Bool{lhs.name==rhs.name}}extensionDefaults.AnyKey:Hashable{publicfunchash(intohasher:inoutHasher){hasher.combine(name)}}
How about Optional
We can supportOptional
as well, as long as it’s underlying value is compatible. Since the type is defined viaKey
, we can’t accidentally use Optional when the Key has non Optional value
extensionOptional:CompatiblewhereWrapped:Compatible{}extensionDefaults{publicsubscript<Value:Compatible>(key:Key<Optional<Value>>)->Value?{get{ifletvalue=suite.object(forKey:key.name)as?Value{returnvalue}returnnil}set{ifletnewValue{suite.set(newValue,forKey:key.name)}else{suite.removeObject(forKey:key.name)}}}}extensionDefaults.Keys{staticletoptional=Defaults.Key<Int?>("optional.int",default:nil)}functestOptional(){XCTAssertNil(defaults[.optional])defaults[.optional]=1XCTAssertEqual(defaults[.optional],1)defaults[.optional]=nilXCTAssertNil(defaults[.optional])}
Start the conversation
Related
See all IOSHow to use playground in Swift file
Issue#998
Traditionally, if we wanted to quickly test a function or a piece of logic, we would open a separate Playground file (.playground
).
From Xcode 26 we can have access to the new#Playground
macro in Swift 6.2. This allows us to declare a …
How to morph liquid glass view transition
Issue#997
An interesting feature in iOS 26 is the ability to create morph “Liquid Glass” effects, where views with the.glassEffect()
modifier can fluidly morph into one another. This is achieved usingGlassEffectContainer and the …
How to prompt users to configure widgets in iOS 18
Issue#996
When creating widgets for iOS, especially those that need user input to be useful, one common challenge is guiding the user to configure them. Before iOS 18, a user would add a widget, but then would have to know to long-press it and …
How to make zoom transition animation in iOS 18
Issue#995
With iOS 18, SwiftUI introducesmatchedTransitionSource andnavigationtransition as a powerful new way to create zoom animations between views. This allows you to smoothly transition from a small view to a larger, more detailed view, …