简体   繁体   中英

Userdefaults can not retrieve custom Struct in Share Extension

I have an App and also a Share Extension . Between them I share data via UserDefaults . But it stopped working all of a sudden. Only bools or Strings can now be retrieved inside the Share Extension but when trying to retrieve a Custom Struct it is always returning nil .

Custom Struct getter/setter in UserDefaults :

//MARK: dataSourceArray
func setDataSourceArray(data: [Wishlist]?){
    set(try? PropertyListEncoder().encode(data), forKey: Keys.dataSourceKey)
    synchronize()
}


func getDataSourceArray() -> [Wishlist]? {
    if let data = self.value(forKey: Keys.dataSourceKey) as? Data {
        do {
            _ = try PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as [Wishlist]
        } catch let error {
            print(error)
        }
        if let dataSourceArray =
            try? PropertyListDecoder().decode(Array < Wishlist > .self, from: data) as[Wishlist] {
                return dataSourceArray
            } 
    }
    return nil
}

I am calling it like this inside my Extension as well as in my Main App:

   if let defaults = UserDefaults(suiteName: UserDefaults.Keys.groupKey) {
        if let data = defaults.getDataSourceArray() {
            print("working")
        } else {
            print("error getting datasourceArray")
        }
    }

This is printing "working" in the Main App but "error getting datasourceArray" in my Extension . I don't understand the issue, especially because simple Bool-Getter are working also from my Share Extension, the issue is only with the Custom Struct .

What am I missing here?

Wishlist Struct:

import UIKit

enum PublicState: String, Codable {
    case PUBLIC
    case PUBLIC_FOR_FRIENDS
    case NOT_PUBLIC
}

struct Wishlist: Codable {
    var id: String
    var name: String
    var image: UIImage
    var wishes: [Wish]
    var color: UIColor
    var textColor: UIColor
    var index: Int
    var publicSate: PublicState

    enum CodingKeys: String, CodingKey {
        case id, name, image, wishData, color, textColor, index, isPublic, isPublicForFriends, publicSate
    }

    init(id: String, name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int, publicSate: PublicState) {
        self.id = id
        self.name = name
        self.image = image
        self.wishes = wishes
        self.color = color
        self.textColor = textColor
        self.index = index
        self.publicSate = publicSate
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        id = try values.decode(String.self, forKey: .id)
        name = try values.decode(String.self, forKey: .name)
        wishes = try values.decode([Wish].self, forKey: .wishData)
        color = try values.decode(Color.self, forKey: .color).uiColor
        textColor = try values.decode(Color.self, forKey: .textColor).uiColor
        index = try values.decode(Int.self, forKey: .index)
        publicSate = try values.decode(PublicState.self, forKey: .publicSate)

        let data = try values.decode(Data.self, forKey: .image)
        guard let image = UIImage(data: data) else {
            throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
        }
        self.image = image
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        try container.encode(wishes, forKey: .wishData)
        try container.encode(Color(uiColor: color), forKey: .color)
        try container.encode(Color(uiColor: textColor), forKey: .textColor)
        try container.encode(index, forKey: .index)
        try container.encode(image.pngData(), forKey: .image)
        try container.encode(publicSate, forKey: .publicSate)
    }
}

Update

This is the part where it fails:

if let data = self.value(forKey: Keys.dataSourceKey) as? Data

Is there any way to catch an error ?

I also found out that this feature is actually working for other users. The app is live: https://apps.apple.com/de/app/wishlists-einfach-w%C3%BCnschen/id1503912334

But it is not working for me? I deinstalled the app, downloaded it from the App Store but it is still not working.

I had the same problem but with another type of extension. Hope it works for you too.

  1. Create a file you share between the two targets and put the following code there:
//MARK: - Model
struct WishlistStruct: Codable {
//your wishlist struct, I'll assume you'll have a name and some items
  var name : String
    var items : [String]
}
typealias Wishlist = WishlistStruct

//MARK: - Defaults
let sharedUserdefaults = UserDefaults(suiteName: SharedDefault.suitName)
struct SharedDefault {
    static let suitName = "yourAppGroupHere"
    
    struct Keys{
        static let WishlistKey = "WishlistKey"
       
    }
}

var myWishlist: [Wishlist] {
   get {
    if let data = sharedUserdefaults?.data(forKey: SharedDefault.Keys.WishlistKey) {
            let array = try! PropertyListDecoder().decode([Wishlist].self, from: data)
        return array
    } else{
        //Here you should return an error but I didn't find any way to do that so I put this code which hopefully will never be executed
    return sharedUserdefaults?.array(forKey: SharedDefault.Keys.WishlistKey) as? [Wishlist] ?? [Wishlist]()
    }
   } set {
       
   }
}
  1. Now, whenever you need to retrieve the struct, both in app and extension, use the following code:
 var wishlist  : [Wishlist] = []
 var currentWishlist = myWishlist

//In your viewDidLoad call
wishlist.append(contentsOf: myWishlist)

  1. To edit the data inside of your wishlist use the following code
 wishlist.append(Wishlist(name: "wishlist", items: ["aaa","bbb","ccc"]))
        currentWishlist.append(Wishlist(name: "wishlist", items: items: ["aaa","bbb","ccc"]))
        if let data = try? PropertyListEncoder().encode(currentWishlist) {
            sharedUserdefaults?.set(data, forKey: SharedDefault.Keys.WishlistKey)
        }

Let me know if you need more clarifications

Updated code to your struct. You should change some type of properties to yours(i remove some field for test).

import UIKit

enum PublicState: String, Codable {
    case PUBLIC
    case PUBLIC_FOR_FRIENDS
    case NOT_PUBLIC
}

struct Wishlist: Codable {
    var id: String = ""
    var name: String = ""
    var image: Data = Data()//TODO: use Data type
    var color: String = ""//TODO: change it to your class
//    var wish: //TODO: add this filed, i don't have it
    var textColor: String = "" //TODO: change it to your class
    var index: Int = 0
    var publicSate: PublicState = .PUBLIC

    enum CodingKeys: String, CodingKey {
        case id, name, image, color, textColor, index, publicSate
    }

    init() {}
    
    init(id: String, name: String, image: Data, color: String, textColor: String, index: Int, publicSate: PublicState) {
        self.id = id
        self.name = name
        self.image = image
        self.color = color
        self.textColor = textColor
        self.index = index
        self.publicSate = publicSate
    }
}

struct WishlistContainer: Codable {
    var list: [Wishlist] = []

    enum CodingKeys: String, CodingKey {
        case list
    }
}


class UserDefaultsManager {

//be sure your correctly setup your app groups
private var currentDefaults: UserDefaults = UserDefaults(suiteName: "put here your app group ID")!

private func getFromLocalStorage<T: Codable>(model: T.Type, key: String) -> T? {
    
    if let decoded = currentDefaults.object(forKey: key) as? String {
        
        guard let data = decoded.data(using: .utf8) else { return nil }
        
        if let product = try? JSONDecoder().decode(model.self, from: data) {
            return product
        }
    }
    
    return nil
}

private func saveToLocalStorage(key: String, encodedData: String) {
    currentDefaults.set(encodedData, forKey: key)
}

private func removeObject(key: String) {
    currentDefaults.removeObject(forKey: key)
}

var wishList: WishlistContainer? {
    set {
        guard let value = newValue else {
            removeObject(key: "wishList")
            return
        }
        
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted
        
        guard let jsonData = try? encoder.encode(value) else { return }
        
        guard let jsonString = String(data: jsonData, encoding: .utf8) else { return }
        
        saveToLocalStorage(key: "wishList", encodedData: jsonString)
    }
    get {
        guard let value = getFromLocalStorage(model: WishlistContainer.self, key: "wishList") else {
            return nil
        }
        
        return value
    }
  }
}

//MARK: - Usage
let list: [Wishlist] = [Wishlist()]
let container: WishlistContainer = WishlistContainer(list: list)
UserDefaultsManager().wishList = container //set
UserDefaultsManager().wishList // get

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM