简体   繁体   中英

Load json from @propertyWrapper in SwiftUI ContentView

I've been using a json decoding utility that works great. I'm wondering if that utility can be abstracted into a propertyWrapper that accepts the json file name as a string.

The call site in the ContentView looks like this:

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

My rough sketch of the property wrapper looks like this:

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

Currently the body of the the ContentView shows this error:

Failed to produce diagnostic for expression; please file a bug report

I like the idea of removing some boilerplate code, but I think I'm missing something fundamental here.

In SwiftUI views are refreshed very often. When a view is refreshed then the @propertyWrapper will be initialised again - this might or might not be desirable. But it's worth noting.

Here is a simple demo showing how to create a property wrapper for loading JSON files. For simplicity I used try? and fatalError but in the real code you'll probably want to add a proper error handling.

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

Then, assuming you have a sample JSON file called items.json :

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

with a corresponding struct:

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

you can load your JSON file in your view:

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

    var body: some View {
        Text(items[0].name)
    }
}

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