SwiftUI:如何使用 UserDefaults 保留 @Published 变量?
SwiftUI: How to persist @Published variable using UserDefaults?
我希望保留一个@Published 变量,以便每次重新启动我的应用程序时它都相同。
我想在一个变量上同时使用@UserDefault 和@Published 属性 包装器。例如我需要一个'@PublishedUserDefault var isLogedIn'。
我有以下 属性Wrapper
import Foundation
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
这是我的设置class
import SwiftUI
import Combine
class Settings: ObservableObject {
@Published var isLogedIn : Bool = false
func doLogin(params:[String:String]) {
Webservice().login(params: params) { response in
if let myresponse = response {
self.login = myresponse.login
}
}
}
}
我的观点class
struct HomeView : View {
@EnvironmentObject var settings: Settings
var body: some View {
VStack {
if settings.isLogedIn {
Text("Loged in")
} else{
Text("Not Loged in")
}
}
}
}
有没有办法制作一个涵盖持久化和发布的 属性 包装器?
您目前无法将 @UserDefault
环绕在 @Published
周围,因为目前不允许这样做。
实现@PublishedUserDefault
的方法是将一个objectWillChange
传递给包装器,并在设置变量之前调用它。
private var cancellables = [String:AnyCancellable]()
extension Published {
init(wrappedValue defaultValue: Value, key: String) {
let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
self.init(initialValue: value)
cancellables[key] = projectedValue.sink { val in
UserDefaults.standard.set(val, forKey: key)
}
}
}
class Settings: ObservableObject {
@Published(key: "isLogedIn") var isLogedIn = false
...
}
样本:https://youtu.be/TXdAg_YvBNE
所有 Codable
类型的版本请查看 here
应该可以编写一个新的 属性 包装器:
Composition was left out of the first revision of this proposal,
because one can manually compose property wrapper types. For example,
the composition @A @B could be implemented as an AB wrapper:
@propertyWrapper
struct AB<Value> {
private var storage: A<B<Value>>
var wrappedValue: Value {
get { storage.wrappedValue.wrappedValue }
set { storage.wrappedValue.wrappedValue = newValue }
}
}
The main benefit of this approach is its predictability: the author of
AB decides how to best achieve the composition of A and B, names it
appropriately, and provides the right API and documentation of its
semantics. On the other hand, having to manually write out each of the
compositions is a lot of boilerplate, particularly for a feature whose
main selling point is the elimination of boilerplate. It is also
unfortunate to have to invent names for each composition---when I try
the compose A and B via @A @B, how do I know to go look for the
manually-composed property wrapper type AB? Or maybe that should be
BA?
struct HomeView : View {
@StateObject var auth = Auth()
@AppStorage("username") var username: String = "Anonymous"
var body: some View {
VStack {
if username != "Anonymous" {
Text("Logged in")
} else{
Text("Not Logged in")
}
}
.onAppear(){
auth.login()
}
}
}
import SwiftUI
import Combine
class Auth: ObservableObject {
func login(params:[String:String]) {
Webservice().login(params: params) { response in
if let myresponse = response {
UserDefaults.standard.set(myresponse.login, forKey: "username")`
}
}
}
}
要保留您的数据,您可以使用 @AppStorage
属性 包装器。
但是,如果不使用 @Published
,您的 ObservableObject
将不再发布有关更改数据的消息。要解决此问题,只需从 属性 的 willSet
观察者调用 objectWillChange.send()
。
import SwiftUI
class Settings: ObservableObject {
@AppStorage("Example") var example: Bool = false {
willSet {
// Call objectWillChange manually since @AppStorage is not published
objectWillChange.send()
}
}
}
我希望保留一个@Published 变量,以便每次重新启动我的应用程序时它都相同。
我想在一个变量上同时使用@UserDefault 和@Published 属性 包装器。例如我需要一个'@PublishedUserDefault var isLogedIn'。
我有以下 属性Wrapper
import Foundation
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
这是我的设置class
import SwiftUI
import Combine
class Settings: ObservableObject {
@Published var isLogedIn : Bool = false
func doLogin(params:[String:String]) {
Webservice().login(params: params) { response in
if let myresponse = response {
self.login = myresponse.login
}
}
}
}
我的观点class
struct HomeView : View {
@EnvironmentObject var settings: Settings
var body: some View {
VStack {
if settings.isLogedIn {
Text("Loged in")
} else{
Text("Not Loged in")
}
}
}
}
有没有办法制作一个涵盖持久化和发布的 属性 包装器?
您目前无法将 @UserDefault
环绕在 @Published
周围,因为目前不允许这样做。
实现@PublishedUserDefault
的方法是将一个objectWillChange
传递给包装器,并在设置变量之前调用它。
private var cancellables = [String:AnyCancellable]()
extension Published {
init(wrappedValue defaultValue: Value, key: String) {
let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
self.init(initialValue: value)
cancellables[key] = projectedValue.sink { val in
UserDefaults.standard.set(val, forKey: key)
}
}
}
class Settings: ObservableObject {
@Published(key: "isLogedIn") var isLogedIn = false
...
}
样本:https://youtu.be/TXdAg_YvBNE
所有 Codable
类型的版本请查看 here
应该可以编写一个新的 属性 包装器:
Composition was left out of the first revision of this proposal, because one can manually compose property wrapper types. For example, the composition @A @B could be implemented as an AB wrapper:
@propertyWrapper
struct AB<Value> {
private var storage: A<B<Value>>
var wrappedValue: Value {
get { storage.wrappedValue.wrappedValue }
set { storage.wrappedValue.wrappedValue = newValue }
}
}
The main benefit of this approach is its predictability: the author of AB decides how to best achieve the composition of A and B, names it appropriately, and provides the right API and documentation of its semantics. On the other hand, having to manually write out each of the compositions is a lot of boilerplate, particularly for a feature whose main selling point is the elimination of boilerplate. It is also unfortunate to have to invent names for each composition---when I try the compose A and B via @A @B, how do I know to go look for the manually-composed property wrapper type AB? Or maybe that should be BA?
struct HomeView : View {
@StateObject var auth = Auth()
@AppStorage("username") var username: String = "Anonymous"
var body: some View {
VStack {
if username != "Anonymous" {
Text("Logged in")
} else{
Text("Not Logged in")
}
}
.onAppear(){
auth.login()
}
}
}
import SwiftUI
import Combine
class Auth: ObservableObject {
func login(params:[String:String]) {
Webservice().login(params: params) { response in
if let myresponse = response {
UserDefaults.standard.set(myresponse.login, forKey: "username")`
}
}
}
}
要保留您的数据,您可以使用 @AppStorage
属性 包装器。
但是,如果不使用 @Published
,您的 ObservableObject
将不再发布有关更改数据的消息。要解决此问题,只需从 属性 的 willSet
观察者调用 objectWillChange.send()
。
import SwiftUI
class Settings: ObservableObject {
@AppStorage("Example") var example: Bool = false {
willSet {
// Call objectWillChange manually since @AppStorage is not published
objectWillChange.send()
}
}
}