iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
tags: 釣魚術 swift swiftui

踩雷筆記

  • 前面兩天嘗試獨黨和存檔;讀檔本身沒什麼大礙,存檔這塊一直撞牆。勢必是要找文件來看看、找別人的踩雷心得來看看。
  • 關於存檔這件事,首先是一個叫做 Bundle 的東東,我以為檔案存到自己建好的目錄下(Documents/saved/NewSecretLocations.json)就可以高枕無憂了,但事與願違。
  • 在這個模擬的 iOS 測試環境,他是一個沙盒 ... 每次刷進新程式之後,APP ID 都會改變。所以如果有儲存檔案後要重開程式測試能不能讀就資料,要不就是 Debug 中斷了,要不就是 APP ID 改變。我覺得我目前測試的方式很蠢,勢必是有必要摸清楚這邊的限制。

目前開發中的 SecretLocationsDataLoader.swift

//
//  SecretLocationsDataLoader.swift
//  BaoAnGongFisher
//
//  Created by nipapa on 2022/9/24.
//

import Foundation
import CoreLocation

public class LocationsLoader {

    @Published var originLoadData = [FishPinAnnotation]()
    @Published var locationData = [PinLocation]()

    init() {
        loadDataFromFile()
        transferCoordinate()
    }

    func loadDataFromFile() {
        let manager = FileManager()
        guard let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return
        }
        let savedFolderUrl = url.appendingPathComponent("saved")
        let fileUrl = savedFolderUrl.appendingPathComponent("NewSecretLocations.json")
        if manager.fileExists(atPath: fileUrl.absoluteString) { //  先前存檔過,就從存檔紀錄讀資料
            do {
                let data = try Data(contentsOf: fileUrl)
                let jsonDecoder = JSONDecoder()
                let dataFromJson = try jsonDecoder.decode([FishPinAnnotation].self, from: data)
                self.originLoadData = dataFromJson
            } catch {
                print(error)
            }
        } else {
            if let myLocationFile = Bundle.main.url(forResource: "MySecretLocations", withExtension: "json") {
                do {
                    let data = try Data(contentsOf: myLocationFile)
                    let jsonDecoder = JSONDecoder()
                    let dataFromJson = try jsonDecoder.decode([FishPinAnnotation].self, from: data)
                    self.originLoadData = dataFromJson
                } catch {
                    print(error)
                }
            }
        }
    }

    func transferCoordinate() {    // 將目前程式使用中的圖釘清單內新增轉換後的資料
        for pin in self.originLoadData {
            self.locationData.append(
                PinLocation(
                    name: pin.name,
                    image: pin.image,
                    coordinate: CLLocationCoordinate2D(latitude: pin.latitude, longitude: pin.longitude),
                    rank: pin.rank)
            )
        }
    }
    
    func encodeCoordinate() -> [FishPinAnnotation] {
        var retPins: [FishPinAnnotation] = []
        for pin in self.locationData {
            retPins.append(
                FishPinAnnotation(
                    name: pin.name,
                    image: pin.image,
                    latitude: pin.coordinate.latitude,
                    longitude: pin.coordinate.longitude,
                    rank: pin.rank)
            )
        }
        return retPins
    }

    func saveDataToFile() {
        // Create Dir
        let manager = FileManager.default
        guard let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return
        }
        print(url.path)
        let newFolderUrl = url.appendingPathComponent("saved")
        do {
            try manager.createDirectory(at: newFolderUrl, withIntermediateDirectories: true)
        } catch {
            print(error)
        }
        // Create file
        let fileUrl = newFolderUrl.appendingPathComponent("NewSecretLocations.json")
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        // 需要做 error handling
        do {
            let jsonData = try jsonEncoder.encode(self.encodeCoordinate())
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                print(jsonString)
                //try jsonData.write(to: myLocationFile)
                manager.createFile(atPath: fileUrl.path,
                                   contents: jsonData,
                                   attributes: [FileAttributeKey.creationDate: Date()])
            }
        } catch {
            print(error)
        }
    }
    
    func copyFileFromBundleToDocumentsFolder(sourceFile: String, destinationFile: String = "") {
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first

        if let documentsURL = documentsURL {
            let sourceURL = Bundle.main.bundleURL.appendingPathComponent(sourceFile)

            // Use the same filename if destination filename is not specified
            let destURL = documentsURL.appendingPathComponent(!destinationFile.isEmpty ? destinationFile : sourceFile)

            do {
                try FileManager.default.removeItem(at: destURL)
                print("Removed existing file at destination")
            } catch (let error) {
                print(error)
            }

            do {
                try FileManager.default.copyItem(at: sourceURL, to: destURL)
                print("\(sourceFile) was copied successfully.")
            } catch (let error) {
                print(error)
            }
        }
    }

    func getLocations () -> [PinLocation] {
        return self.locationData
    }
}

struct FishPinAnnotation: Hashable, Codable{
//    private let id = UUID()
    var name: String
    var image: String
    var latitude: Double
    var longitude: Double
    var rank: Int
}

閱讀文件

圖片來源:上述 0xn3va gitbook
這邊在講的是,基於安全理由,一個 App 能存取的範圍都是沙盒內的幾個容器而已。容器擺放的內容,有其對應的目的。

參考

  • fileExists - Returns a Boolean value that indicates whether a file or directory exists at a specified path.
  • bundle - A representation of the code and resources stored in a bundle directory on disk.

上一篇
【Day 20】FileManager
下一篇
【Day 22】iOS debug 方法 - 下載沙盒看內容
系列文
無法成為釣魚大師也要努力摸魚!!辣個吃魚神器 APP38
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言