1313import Foundation
1414import SwiftUI
1515import Security
16+ import Combine
1617
1718/// A property wrapper that reads and writes to the keychain.
1819///
@@ -29,9 +30,13 @@ public struct SecureStorage: DynamicProperty {
2930 var service : String
3031
3132 /// Should delete when asked?
32- var shouldDeleteWhenAsked : Bool = false
33+ var isInitialized : Bool = false
3334
34- @State var value : String ?
35+ /// The cancellables to store.
36+ var cancellables = Set < AnyCancellable > ( )
37+
38+ /// The observed storage
39+ @ObservedObject private var store : Storage
3540
3641 /// Creates an `SecureStorage` property.
3742 ///
@@ -44,8 +49,6 @@ public struct SecureStorage: DynamicProperty {
4449 ) {
4550 self . key = key
4651 self . service = service
47- self . wrappedValue = wrappedValue
48- self . shouldDeleteWhenAsked = true
4952
5053 let query = [
5154 kSecAttrService: service,
@@ -59,19 +62,20 @@ public struct SecureStorage: DynamicProperty {
5962
6063 if let data = result as? Data ,
6164 let string = String ( data: data, encoding: . utf8) {
62- self . value = string
65+ store = . init ( string)
6366 } else {
64- self . value = nil
67+ store = . init ( nil )
6568 }
69+
70+ self . isInitialized = true
6671 }
6772
68- /// The value of the key in the keychain .
73+ /// The value of the key in iCloud .
6974 public var wrappedValue : String ? {
7075 get {
71- return value
76+ return store . value
7277 }
7378
74- // This needs to be nonmutating because we're setting a property on a struct.
7579 nonmutating set {
7680 if let newValue {
7781 let data = Data ( newValue. utf8)
@@ -102,7 +106,7 @@ public struct SecureStorage: DynamicProperty {
102106 }
103107 } else {
104108 // Wait until we are initialized before deleting items
105- if shouldDeleteWhenAsked {
109+ if isInitialized {
106110 let query = [
107111 kSecAttrService: service,
108112 kSecAttrAccount: key,
@@ -113,16 +117,28 @@ public struct SecureStorage: DynamicProperty {
113117 }
114118 }
115119
116- value = newValue
120+ store . value = newValue
117121 }
118122 }
119123
120124 /// A binding to the value of the key in iCloud.
121125 public var projectedValue : Binding < String ? > {
122- Binding {
123- return self . wrappedValue
124- } set: { newValue in
125- self . wrappedValue = newValue
126+ $store. value
127+ }
128+
129+ // MARK: - Storage
130+ private final class Storage : ObservableObject {
131+ var parentWillChange : ObservableObjectPublisher ?
132+
133+ var value : String ? {
134+ willSet {
135+ objectWillChange. send ( )
136+ parentWillChange? . send ( )
137+ }
138+ }
139+
140+ init ( _ value: String ? ) {
141+ self . value = value
126142 }
127143 }
128144
@@ -146,5 +162,23 @@ public struct SecureStorage: DynamicProperty {
146162
147163 SecItemDelete ( query)
148164 }
165+
166+ /// Get the parent, to send a willChange event to there.
167+ public static subscript< OuterSelf: ObservableObject > (
168+ _enclosingInstance instance: OuterSelf ,
169+ wrapped wrappedKeyPath: ReferenceWritableKeyPath < OuterSelf , String ? > ,
170+ storage storageKeyPath: ReferenceWritableKeyPath < OuterSelf , Self >
171+ ) -> String ? {
172+ get {
173+ instance [ keyPath: storageKeyPath] . store. parentWillChange = (
174+ instance. objectWillChange as? ObservableObjectPublisher
175+ )
176+
177+ return instance [ keyPath: storageKeyPath] . wrappedValue
178+ }
179+ set {
180+ instance [ keyPath: storageKeyPath] . wrappedValue = newValue
181+ }
182+ }
149183}
150184#endif
0 commit comments