我在使用 USerDefault 保存数据时做错了什么

What am I doing wrong on using USerDefault to save data

我正在对用户选择的图像进行编码,然后保存到 USerDefault。因此,我通过解码并转换为 UIImages 数组来检索这些图像,并在其中填充我的 UITableView。我的目标是能够终止应用程序,当我再次打开时,UITableView 仍然存在,我想保留用户数据。 一切都很完美,我得到了我想要的,但在尝试了几次之后,我得到了以下错误:

Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray'

什么是我做我想做的事的最佳方式?

这里是应该检索数据的地方:

class ViewController: UIViewController, CLLocationManagerDelegate,  UITableViewDataSource, dataReceivedDelegate {

    func dataReceived(nameSaved: NSArray) {
        nameSaved1 = nameSaved
//        imagemCell = fotoSaved
       self.itensTableView.reloadData()
    }

    @IBOutlet weak var itensTableView: UITableView!

    //Notification
    let content = UNMutableNotificationContent()
    //
    var locationManager:CLLocationManager = CLLocationManager()
    var currentLocation: CLLocation?
    let region = CLBeaconRegion(proximityUUID: UUID(uuidString: "DD07D751-FBE1-430C-973A-281F9DA59A39")!, identifier: "Estimotes")

    var arrayNomes = NSMutableArray()
    var nomeReceived = ""
    var qtd:Int = 0
    var imagemCell = [NSData]()
    var nameSaved1 = NSArray()
    var dataSaved = [NSData]()
    var imageSaved = [UIImage]()

    override func viewDidLoad() {
        super.viewDidLoad()
        if isKeyPresentInUserDefaults(key: "namesSavedArray") == true{
            nameSaved1 = UserDefaults.standard.array(forKey: "namesSavedArray") as! [String] as NSArray
            itensTableView.reloadData()
        }
        if isKeyPresentInUserDefaults(key: "fotoSaved") == true{
            // Load the image
            imagemCell = UserDefaults.standard.object(forKey: "fotoSaved") as! [NSData]
            for uiimage in imagemCell {
                let imageConverted = UIImage(data: uiimage as Data)
                imageSaved.append(imageConverted!)

                itensTableView.reloadData()
            }
        }

        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
        //LocationManager
        locationManager.delegate = self
        if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedWhenInUse) {
            locationManager.requestWhenInUseAuthorization()
        }
        locationManager.startRangingBeacons(in: region)

    }


    func isKeyPresentInUserDefaults(key: String) -> Bool {
        return UserDefaults.standard.object(forKey: key) != nil
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "addVc" {
            let vc:adicionarNovoItemVc = segue.destination as! adicionarNovoItemVc
            vc.delegate = self

        }
    }


    func rangeBeacons(){
        let uuid = UUID(uuidString: "DD07D751-FBE1-430C-973A-281F9DA59A39")
        let major:Int = 1
        let minor:Int = 1
        let identifier = "Bruz IBeacon"

        let region = CLBeaconRegion(proximityUUID: uuid!, major: CLBeaconMajorValue(major), minor: CLBeaconMinorValue(minor), identifier: identifier)

        locationManager.startRangingBeacons(in: region)
    }


    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

        switch status {
        case .restricted:
            print("Location access was restricted.")
        case .denied:
            print("User denied access to location.")
        case .notDetermined:
            print("Location status not determined.")
        case .authorizedAlways: fallthrough
        case .authorizedWhenInUse:
            print("Location status is OK.")

        }

        if status == .authorizedAlways{

            rangeBeacons()
        }
    }
    // Handle incoming location events.
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let location: CLLocation = locations.last!
        print("Location: \(location)")
    }

    func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
        guard let discoveredBeacon = beacons.first?.proximity else {
            print("Beacon nao encontrado"); return}

            switch discoveredBeacon {

            case .immediate:
                beaconMuitoProximo()
                self.view.backgroundColor = .green
            case .near: break
//                beaconProximo()
//                self.view.backgroundColor = .orange
            case .far:
                beaconLonge()
                self.view.backgroundColor = .red
            case .unknown:
                self.view.backgroundColor = .black
        }


    }
    // Handle location manager errors.
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        locationManager.stopUpdatingLocation()
        print("Error: \(error)")
    }

    func beaconMuitoProximo(){

        //Notificacoes de perda de objetos
//        self.content.title = "Seguro"
//        self.content.body = "Suas coisas estao perto de voce =)"
//        self.content.sound = UNNotificationSound.default
//        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
//        let request = UNNotificationRequest(identifier: "testIdentifierPerto", content: self.content, trigger: trigger)
//        UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
//



    }
    func beaconProximo(){


    }
    func beaconLonge(){


        // create a sound ID, in this case its the tweet sound.
        let systemSoundID: SystemSoundID = 1016

        // to play sound
        AudioServicesPlaySystemSound (systemSoundID)

    }


    @IBAction func botaoAdicionar(_ sender: UIButton) {}


    //TableView
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        //        let item = objetos[indexPath.row]
        let cell = itensTableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! tableviewCell
        cell.nameCell.text =  nameSaved1[indexPath.row] as? String//Nil value
        cell.imageViewCell.image = imageSaved[indexPath.row] //Nil value
        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return imageSaved.count
    }
}

这是我保存数据的地方:

protocol dataReceivedDelegate {
    func dataReceived(nameSaved:NSArray)
}

class adicionarNovoItemVc: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate {

    @IBOutlet weak var textFieldNome: UITextField!
    @IBOutlet weak var namePreview: UILabel!
    @IBOutlet weak var imagePreview: UIImageView!

    let imagePicker = UIImagePickerController()
    let picker = UIImagePickerController()
    var arrayName = [String]()
    var arrayFotoData = [NSData]()
    var delegate:dataReceivedDelegate? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        self.textFieldNome.delegate = self

        if isKeyPresentInUserDefaults(key: "namesSavedArray") == true{
            arrayName = UserDefaults.standard.array(forKey: "namesSavedArray") as! [String]
        }
        if isKeyPresentInUserDefaults(key: "fotoSaved") == true{
            arrayFotoData = UserDefaults.standard.array(forKey: "fotoSaved") as! [NSData]
        }
    }

    func isKeyPresentInUserDefaults(key: String) -> Bool {
        return UserDefaults.standard.object(forKey: key) != nil
    }

    @IBAction func botaoAdcFoto(_ sender: UIButton) {

        picker.allowsEditing = true
        picker.delegate = self
        picker.sourceType = .photoLibrary
        if let mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) {
            picker.mediaTypes = mediaTypes
        }
        present(picker, animated: true)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[.originalImage] as? UIImage {
            self.imagePreview.image = image
            self.namePreview.text = self.textFieldNome.text

            //Encode Image
            let dataSaved:NSData = image.pngData()! as NSData
            arrayFotoData.append(dataSaved)

            UserDefaults.standard.set(arrayFotoData, forKey: "fotoSaved")
        }
        self.picker.dismiss(animated: true, completion: nil)
    }


    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        self.textFieldNome.resignFirstResponder()
        return true
    }

    @IBAction func botaoAdcItem(_ sender: UIButton) {
        if (self.namePreview!.text != nil) && (self.imagePreview!.image != nil) {
            if delegate != nil {

                arrayName.append(self.namePreview.text!)

                delegate?.dataReceived(nameSaved: arrayName as NSArray)

                UserDefaults.standard.set(arrayName, forKey: "namesSavedArray")

                self.navigationController?.popViewController(animated: true)
            }
        }
        else {return}
}


}

应用应该是这样工作的: 此屏幕是 UITableView 所在的位置,因此数据应该保留在这里:

这个屏幕是应该设置和保存数据的地方: 按下按钮后,应关闭屏幕并重新加载包含数据的 UITableView。

我认为您遇到的问题与as有关! [字符串]

仅仅因为密钥存在并不意味着你不会得到 nil。

用作? [细绳] ?? []

因此,如果返回 nil,您将得到一个空数组。

在为 UserDefault 设置新值后调用 UserDefaults.standard.synchronize(),它应该触发同步以实际存储新值。

我有您可能需要的 Objective-c 版本的代码,请尝试创建 swift 版本。

所以,要save/write图像数据到应用程序的文件夹,你可以这样做

 //to write the image in folder
NSData *pngData = UIImagePNGRepresentation(image);

[pngData writeToFile:[[AlertHelper sharedAlertManager] documentsPathForFileName:@"The name for your image"] atomically:YES]; //Write the file

我在上面的代码中使用了一个辅助方法来简化这个过程,并在各种 class 文件中使用它。

并向读取图像数据,

  NSData *pngData1 = [NSData dataWithContentsOfFile:[[AlertHelper sharedAlertManager] documentsPathForFileName:@"The same name you used to write for your image"]];
  UIImage *image = [UIImage imageWithData:pngData1];

And the Helper methood is

// to write and read files in paths
- (NSString *)documentsPathForFileName:(NSString *)name
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSLog(@"%@",[documentsPath stringByAppendingPathComponent:name]); // to check if the name used for image.
    return [documentsPath stringByAppendingPathComponent:name];
}

更新:-

对于我遇到的 Swift 版本 something 这可能有用。