Home > OS >  How to use Userdefaults to store UItableview data in swift?
How to use Userdefaults to store UItableview data in swift?

Time:09-17

Now what I am adding an item to a cart it is appearing correctly. But when I am reopening the page, the data is lost. I am trying to store the cartTableView data in userdefaults. I have tried several methods but couldn't turn up. Here is my code for cartViewController :

import UIKit
class CartViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var arrdata = [jsonstruct]()
    var categorydata = [Categories]()
    var imgdata = [Images]()
    
    var cartArray = [CartStruct]()
    
    @IBOutlet var cartTableView: UITableView!
    
    @IBOutlet var totalCount: UILabel!
    @IBOutlet var subtotalPrice: UILabel!
    @IBOutlet var shippingPrice: UILabel!
    @IBOutlet var totalPrice: UILabel!
    @IBOutlet var proceedBtn: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        cartArray = (UserDefaults.standard.array(forKey: "cartt") as? [CartStruct])!
        cartTableView.reloadData()

    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cartArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "CartCellTableViewCell", for: indexPath) as! CartCellTableViewCell
 
        cell.cartImageView.downloadImage(from: cartArray[indexPath.row].cartItems.images.first?.src ?? "place_holder_image")

        cell.productNameCart.text = cartArray[indexPath.row].cartItems.name
        cell.prodductDescCart.text = cartArray[indexPath.row].cartItems.categories.first?.type
        cell.productPriceCart.text = cartArray[indexPath.row].cartItems.price
        
        cell.addBtn.addTarget(self, action: #selector(add(sender:)), for: .touchUpInside)
        cell.addBtn.tag = indexPath.row
        
        let cartQuantity = cartArray[indexPath.row].cartQuantity
        cell.prodCount.text = "\(cartQuantity)"
        
        if cartQuantity >= 0 {
            cell.subBtn.isUserInteractionEnabled = true;
            cell.subBtn.addTarget(self, action: #selector(sub(sender:)), for: .touchUpInside)
            cell.subBtn.tag = indexPath.row
        } else {
            cell.subBtn.isUserInteractionEnabled = false;
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            cartArray.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
            tableView.reloadData()
        }
    }
    
    @objc func add(sender: UIButton){
        if cartArray[sender.tag].cartQuantity >= 0 {
            cartArray[sender.tag].cartQuantity  = 1
            cartTableView.reloadData()
        }
    }
    
    @objc func sub(sender: UIButton){
        if cartArray[sender.tag].cartQuantity > 0 {
            cartArray[sender.tag].cartQuantity -= 1
            cartTableView.reloadData()
        }
    }
}
   

But here is showing some runtime error : " Thread 1: Swift runtime failure: force unwrapped a nil value "

Please suggest me how can I fix the whole thing.. Thanks in advance!

CodePudding user response:

You can use UserDefaults, But You should use core data or realm for better performance. how to use this code? // Save: CartCache.save([Cart])

// Get data: let cart = CartCache.get()

struct CartCache {
    
        static let key = "CartCache"
    
        static func save(_ value: [CartStruct]!) {
          UserDefaults.standard.set(try? PropertyListEncoder().encode(value), forKey: key)
        }
    
        static func get() -> [CartStruct]! {
          var cartData: User!
          if let data = UserDefaults.standard.value(forKey: key) as? Data {
            cartData = try? PropertyListDecoder().decode([User].self, from: data)
            return cartData!
          } else {
            return []
          }
        }
    
         static func remove() {
            UserDefaults.standard.removeObject(forKey: key)
          }
        }

CodePudding user response:

You should use different data saving methods other than user defaults. User defaults mostly used to save small informations like name, username, gender etc. I recommend you to use Core Data instead of user defaults and here is a very useful explanation and example projec of Core Data.

https://www.raywenderlich.com/7569-getting-started-with-core-data-tutorial

Have a great day :)

CodePudding user response:

As many people have mentioned UserDefaults is not really meant for storing this type of data. From the docs:

The UserDefaults class provides a programmatic interface for interacting with the defaults system. The defaults system allows an app to customize its behavior to match a user’s preferences. For example, you can allow users to specify their preferred units of measurement or media playback speed. Apps store these preferences by assigning values to a set of parameters in a user’s defaults database. The parameters are referred to as defaults because they’re commonly used to determine an app’s default state at startup or the way it acts by default.

However I feel like this is still a valid question and something that every IOS/MacOS developer should know how to use.

NSUserDefaults is simple and easy to use, but there are some things you have to understand.

Namely you can only store property list items.

A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

This means that CartStruct cannot simply be persisted in UserDefaults, because it is not a NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary or a combination of these.

Now using UserDefaults is simple they have a singleton for the app called standard.

UserDefaults.standard.setValue("Hello Defaults", forKey: "Hello_Key")
let greeting = UserDefaults.standard.value(forKey: "Hello_Key")

print(greeting!)// prints Hello Dafaults

The next question becomes how to turn your struct into data. I'm sure if you ask that question on here and post the code for your struct you'll get a quick and easy solution. Once you have data you can save that in UserDefaults or better yet in the file system. In fact I think that really is the question your trying to ask is how to persist and retrieve your struct data for use in a tableview.

CodePudding user response:

Yes, we can store in UserDefaults, But it is not flexible as compare to Core Data or other storage for add, updates or delete operations.

Below code supposed to work.

You need to make "CartStruct" as Codable

struct CartStruct : Codable {
    var cartItems: jsonstruct
    var cartQuantity: Int
}

Next, Add this to insert and store it in tap "addToCartbtnTapped" .(DetailViewController)

 func saveCart(data: CartStruct) {
    let defaults = UserDefaults.standard
    if let cdata = defaults.data(forKey: "cartt") {
        var cartArray = try! PropertyListDecoder().decode([CartStruct].self, from: cdata)
        cartArray.append(data)
        cartCount.text = "\(cartArray.count)"
        if let updatedCart = try? PropertyListEncoder().encode(cartArray) {
            UserDefaults.standard.set(updatedCart, forKey: "cartt")
        }
    }
}

@IBAction func addToCartbtnTapped(_ sender: Any) {
    if let info = detailInfo {
        let cartData = CartStruct(cartItems: info, cartQuantity: 1)
        self.saveCart(data: cartData)
        showAlert()
        (sender as AnyObject).setTitle("Go to Cart", for: .normal)
        addToCartbtn.isUserInteractionEnabled = false
    }
}


@IBAction func cartTappedToNavigate(_ sender: Any) {
        let cart = self.storyboard?.instantiateViewController(withIdentifier: "CartViewController") as? CartViewController
        self.navigationController?.pushViewController(cart!, animated: true)
    }

Now, Fetch your cart data from where you stored.(CartViewController)

override func viewDidLoad() {
        super.viewDidLoad()
        self.getCartData()
    }

 func getCartData() {
        let defaults = UserDefaults.standard
        if let data = defaults.data(forKey: "cartt") {
            cartArray = try! PropertyListDecoder().decode([CartStruct].self, from: data)
            cartTableView.reloadData()
        }
    }
  • Related