Ошибки пользователя не могут получить пользовательскую структуру в расширении общего ресурса

#ios #swift #nsuserdefaults #ios8-share-extension

#iOS #swift #nsuserdefaults #ios8-share-extension

Вопрос:

У меня есть приложение, а также Share Extension . Между ними я обмениваюсь данными через UserDefaults . Но он внезапно перестал работать. bools Strings Теперь внутри можно получить только or Share Extension , но при попытке получить a Custom Struct он всегда возвращается nil .

Пользовательская структура getter/setter в 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
}
 

Я называю это Extension так как в моем, так и в моем основном приложении:

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

Это печать «работает» в основном приложении, но «ошибка получения datasourceArray» в моем Extension . Я не понимаю проблемы, особенно потому, что simple Bool-Getter работают и с моим расширением общего доступа, проблема только с Custom Struct .

Чего мне здесь не хватает?

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)
    }
}
 

Обновить

Это та часть, где происходит сбой:

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

Есть ли какой-либо способ catch error ?

Я также обнаружил, что эта функция действительно работает для других пользователей. Приложение работает: https://apps.apple.com/de/app/wishlists-einfach-wünschen/id1503912334

Но у меня это не работает? Я деинсталлировал приложение, загрузил его из App Store, но оно все еще не работает.

Комментарии:

1. Не связано, но почему вы дважды расшифровываете список свойств? Создайте метод throw , тогда этого будет достаточно: func getDataSourceArray() throws -> [Wishlist] { guard let data = self.data(forKey: Keys.dataSourceKey) else {return [] } return try PropertyListDecoder().decode([Wishlist].self, from: data) }

2. @vadian хороший момент, спасибо, но, как вы сказали, не имеет отношения к основной проблеме здесь: (

Ответ №1:

У меня была та же проблема, но с другим типом расширения. Надеюсь, это сработает и для вас.

  1. Создайте файл, которым вы делитесь между двумя целями, и поместите туда следующий код:
 //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. Теперь всякий раз, когда вам нужно получить структуру, как в приложении, так и в расширении, используйте следующий код:
  var wishlist  : [Wishlist] = []
 var currentWishlist = myWishlist

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

 
  1. Чтобы отредактировать данные внутри вашего списка пожеланий, используйте следующий код
  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)
        }
 

Дайте мне знать, если вам нужны дополнительные разъяснения

Комментарии:

1. Я попробую! Но есть ли у вас какие-либо идеи, почему возникает проблема???

2. Ошибка в способе извлечения данных. Вы используете значение для ключа, а затем преобразуете результат в данные. Попробуйте вместо этого использовать данные для ключа. Код внутри myWishlist переменной, которую я ввел в свой ответ, должен вам помочь

3. Я не думаю, что это все. Я попытался изменить свой getDataSourceArray на это: if let data = data(forKey: Keys.dataSourceKey) { let array = try! PropertyListDecoder().decode([Wishlist].self, from: data) return array } else { print(":(") } , но тот же результат

4. пока нет, но я это сделаю. В данный момент я занят проектом, и я не вижу основной разницы между вашим и моим?

5. Разница в том, что с моим кодом вы сможете получить структуру.

Ответ №2:

Обновленный код для вашей структуры. Вы должны изменить некоторые свойства на свои (я удаляю некоторые поля для тестирования).

 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
 

Комментарии:

1. вы уверены? тогда почему это работает внутри основного приложения? Я думаю, вы можете сохранить их, если они есть Codable , и Wishlists подтверждает codable .

2. Если ваш общий ресурс работает правильно для примитивных типов, но не работает для пользовательских структур. Этот код должен помочь.

3. Я попробую, но я не совсем понимаю, потому что, как я уже сказал в вопросе, это действительно работает для других

4. Я думаю, что мне нужно перейти standard на мой suitName , не так ли?

5. и не могли бы вы привести пример того, как я буду использовать код в моем примере?