Загрузите json из @propertyWrapper в SwiftUI ContentView

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

Сайт вызова в ContentView выглядит так:

struct ContentView: View {
     @DataLoader("tracks.json") var tracks: [Tracks]
...

Мой примерный набросок оболочки свойств выглядит так:

@propertyWrapper 
struct DataLoader<T: Decodable>: DynamicProperty {
    private let fileName: String
    
    var wrappedValue: T {
        get {
            return Bundle.main.decode(T.self, from: fileName)
        }
        
        set {
             //not sure i need to set anything since i just want to get the array
        }
    }
    
    init(_ fileName: String) {
        self.fileName = fileName
        wrappedValue = Bundle.main.decode(T.self, from: fileName)
    }
}

В настоящее время в теле ContentView отображается эта ошибка:

Не удалось произвести диагностику для выражения; пожалуйста, отправьте отчет об ошибке

Мне нравится идея удалить шаблонный код, но я думаю, что мне здесь не хватает чего-то фундаментального.


person josh k    schedule 17.09.2020    source источник


Ответы (1)


В SwiftUI представления обновляются очень часто. Когда представление обновляется, @propertyWrapper будет снова инициализирован - это может быть, а может и не быть желательным. Но стоит отметить.

Вот простая демонстрация, показывающая, как создать оболочку свойств для загрузки файлов JSON. Для простоты я использовал try? и fatalError, но в реальном коде вы, вероятно, захотите добавить правильную обработку ошибок.

@propertyWrapper
struct DataLoader<T> where T: Decodable {
    private let fileName: String

    var wrappedValue: T {
        guard let result = loadJson(fileName: fileName) else {
            fatalError("Cannot load json data \(fileName)")
        }
        return result
    }

    init(_ fileName: String) {
        self.fileName = fileName
    }

    func loadJson(fileName: String) -> T? {
        guard let url = Bundle.main.url(forResource: fileName, withExtension: "json"),
            let data = try? Data(contentsOf: url),
            let result = try? JSONDecoder().decode(T.self, from: data)
        else {
            return nil
        }
        return result
    }
}

Затем, если у вас есть образец файла JSON с именем items.json:

[
    {
        "name": "Test1",
        "count": 32
    },
    {
        "name": "Test2",
        "count": 15
    }
]

с соответствующей структурой:

struct Item: Codable {
    let name: String
    let count: Int
}

вы можете загрузить свой файл JSON в своем представлении:

struct ContentView: View {
    @DataLoader("items") private var items: [Item]

    var body: some View {
        Text(items[0].name)
    }
}
person pawello2222    schedule 17.09.2020
comment
Очень хорошо. В моем случае это выглядит солидно для заполнения массива треков. Это вызывает новую ошибку. Я хочу передать треки как привязку к другому виду. Итак, внутри тела я прохожу по $ track и получаю следующую ошибку: Не могу найти "$ track" в области видимости. Это работало раньше, когда я настраивал треки с помощью @State var track. - person josh k; 18.09.2020
comment
Вы не указали это в вопросе. Это полностью меняет контекст. И зачем вам это должно быть обязательным? Вы хотите изменить файл JSON из другого представления? - person pawello2222; 18.09.2020
comment
Таким образом, вам не нужна привязка - просто передайте ее другому представлению как стандартную переменную. - person pawello2222; 18.09.2020