TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

How save data.in offline mode? 🙄

Forums > SwiftUI

Good afternoon. I am doing a test. "Offline mode: i.e. if there is no network access, show the last downloaded data" I can't figure out how to save data. The model contains both text and links to the image. Please tell me how this is implemented.

I think through the UserDefaults 🙄

2      

I would advice against user defaults. that's meant for small data. I suggest to use JSON files stored in the documents directory. there's tons on info about how to do that on HWS

2      

Save files to storage on your phone? JSON save not to userDefaults?

2      

Save files to storage on your phone? JSON save not to userDefaults?

Consider that an application's UserDefaults is stored with your application. By design, it probably should store small chunks of data and preferences. Preferred color scheme. Last date of log in. User profile. This sort of data.

The iPhone has a document directory that's designed for storing much larger files. Pages documents. Cat memes jpegs. Web links that you never seem to revisit. Selfies. Hundreds of large files.

I think @Gakkie is pointing out if you have a LARGE file of json data as an offline version of your application's data set, consider storing that json file in your document's directory, rather than in UserDefaults.

2      

Some thing like below , using a json and storing it in DocumentsDirectory and retrieving it

struct Person: Codable {
    var name: String
    var age: Int
}

func saveToDocuments(person: Person) {
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    do {
        let data = try encoder.encode(person)
        let filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let path = filePath.appendingPathComponent("person.json")
        try data.write(to: path)
        print("json file saved to path \(path.path)")

    } catch {
        print("error detected")
    }

}

func retrieveDataFromFile() {
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let fileURL = documentsDirectory.appendingPathComponent("person.json")
    do {
        let decoder = try JSONDecoder()
        let data = try Data(contentsOf: fileURL)
        let decodedData = try decoder.decode(Person.self, from: data)
        print(decodedData)

    } catch {
        print("error")
    }
}

let person = Person(name: "James", age: 19)
saveToDocuments(person: person)
retrieveDataFromFile()

2      

I have this extension that I use

extension FileManager {
    private func getDocumentsDirectory() -> URL {
        let paths = self.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

    func encode<T: Encodable>(_ input: T, to file: String) throws {
        let url = getDocumentsDirectory().appendingPathComponent(file)
        let encoder = JSONEncoder()

        let data = try encoder.encode(input)
        try data.write(to: url, options: [.atomic, .completeFileProtection])
    }

    func decode<T: Decodable>(_ type: T.Type,
                              from file: String,
                              dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
                              keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys
    )  throws -> T {
        let url = getDocumentsDirectory().appendingPathComponent(file)
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = dateDecodingStrategy
        decoder.keyDecodingStrategy = keyDecodingStrategy

        let data = try Data(contentsOf: url)
        let loaded = try decoder.decode(T.self, from: data)
        return loaded

    }
}

Then you want to check if you have internet access

class Questions: ObservableObject {
    @Published var yourData = [YourData]()
    @Published var noInternet = false // So you can show an alert that you have no internet and no data!
    let monitor = NWPathMonitor()

    init() {
        Task {
            await loadAndCheckConnection()
        }
    }

    private func loadAndCheckConnection() async {

        // 1. If on WiFi then remove all data and fetch data
        let queue = DispatchQueue.main
        monitor.start(queue: queue)
        monitor.pathUpdateHandler = { path in
            if path.status == .satisfied {
                self.bank.removeAll(keepingCapacity: true)
                Task {
                    await self.fetch()
                }    
            } else if path.status == .unsatisfied {
                if self.yourData.isEmpty {
                    self.noInternet = true
                }
            }

            self.monitor.cancel()
        }
    }

    private func fetch() async {
         do  {
             async let items = try await URLSession.shared.decode([YourData].self, from: "URL.com")
             yourData = try await items
         // 2. Save data to persistent store
             try FileManager.default.encode(bank, to: "yourData.json")
         } catch {
             print("Failed to fetch data!")
         }
     }
}

You might want URL extension too

/// A URLSession extension that fetches data from a URL and decodes to some Decodable type.
/// Usage: let user = try await URLSession.shared.decode(UserData.self, from: someURL)
/// Note: this requires Swift 5.5.
extension URLSession {
    func decode<T: Decodable>(
        _ type: T.Type = T.self,
        from urlString: String,
        keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
        dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .deferredToData,
        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate
    ) async throws  -> T {
        guard let url = URL(string: urlString) else {
            fatalError("Unable to get URL")
        }

        let (data, _) = try await data(from: url)

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = keyDecodingStrategy
        decoder.dataDecodingStrategy = dataDecodingStrategy
        decoder.dateDecodingStrategy = dateDecodingStrategy

        let decoded = try decoder.decode(T.self, from: data)
        return decoded
    }
}

2      

Thank you. How much new information. I sit down to study.

2      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.