skip to Main Content

I parsed the Api and stored in CoreData but when i display the results in UITableView its displaying the image, name and email twice. How to eliminate the duplicate and display only unique data. I also added a constraint and "context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy which stopped the merging of data after each run but now its displaying all the data twice."

Any help Will be highly appreciable

ViewController

import UIKit
import Kingfisher

class ViewController: UIViewController {

    @IBOutlet weak var textName: UITextField!
    @IBOutlet weak var textAge: UITextField!
    @IBOutlet weak var tableView: UITableView!
    
    let database = DatabaseHandle.shared
    var users: [User]?{
        didSet{
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //tableView.register(UserTableViewCell.self, forCellReuseIdentifier: "UserTableViewCell")
        tableView.tableFooterView = UIView(frame: .zero)
    }
    override func viewWillAppear(_ animated: Bool) {
        users = database.fetch(User.self)
        
        //print(users)
    }
    override func viewDidAppear(_ animated: Bool) {
        ApiHandler.shared.syncUser {
            self.users = self.database.fetch(User.self)
        }
    }
    
}
extension ViewController: UITableViewDataSource,UITableViewDelegate{
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return users?.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UserTableViewCell
        
        cell.user = users?[indexPath.row]
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        guard let user = users?[indexPath.row] else {return}
        tableView.beginUpdates()
        self.database.delete(object: user)
        users?.remove(at: indexPath.row)
        tableView.deleteRows(at: [indexPath], with: .automatic)
        tableView.endUpdates()
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
}

**ApiHandler **

import UIKit

class ApiHandler{
    
    static let shared = ApiHandler()
    
    func syncUser(completion: @escaping (() -> Void)){
        var req = URLRequest(url: URL(string: "https://reqres.in/api/users?page=2")!)
        req.httpMethod = "GET"
        let session = URLSession.shared
        
        let task = session.dataTask(with: req, completionHandler: { data,response,error -> Void in
            print(response!)
            
            do{
                let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary <String,AnyObject>
                let model = try JSONDecoder().decode(ApiResponse<[UserServerModel]>.self, from: data!)
                model.data.forEach({$0.store()})
                print(json)
                completion()
            }catch{
                print(error.localizedDescription)
                completion()
            }
            
            
        })
        task.resume()
        
    }
}

public struct ApiResponse<T: Codable>: Codable {
    public let total_pages: Int
    public let per_page: Int
    public let data: T
    public let page: Int
    public let total: Int
    
}

DatabaseHandle

import UIKit
import CoreData

class DatabaseHandle{
    
    private var viewContext:NSManagedObjectContext
    static let shared = DatabaseHandle()
    init() {
        viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    }
    func add<T: NSManagedObject>(_ type: T.Type)-> T?{
        guard let entityName = T.entity().name else {return nil}
        guard let entity = NSEntityDescription.entity(forEntityName: entityName, in: viewContext) else{return nil}
        let object = T(entity: entity, insertInto: viewContext)
        return object
        }
    
    func fetch<T:NSManagedObject>(_ type: T.Type) -> [T] {
        let request = T.fetchRequest()
        do{
            let result = try viewContext.fetch(request)
            
            return result as! [T]
        }catch{
            print(error.localizedDescription)
            return []
        }
        
    }
    
    func save(){
        do{
            try viewContext.save()
        }catch{
            print(error.localizedDescription)
        }
    }
    
    func delete<T:NSManagedObject>(object: T){
        viewContext.delete(object)
        save()
    }
}

2

Answers


  1. when you’re calling

    model.data.forEach({$0.store()})
    

    I’m not sure if store is a custom function, but perhaps you’re not first checking if it already exists in coredata, so you end up just saving it twice.

    Login or Signup to reply.
  2. As the others have mentioned you need to check the persistence layer for the existence of records before adding a new one. I’d suggest implementing a protocol so that your other API data structures can be persisted as well.

    protocol Persistable {
        associatedtype Persisted: NSManagedObject
        var predicate: NSPredicate? { get }
        func persist(into object: Persisted)
    }
    
    extension NSPredicate {
        static func userId(equalTo id: Int16) -> NSPredicate {
            return .init(format: "id == %d", id)
        }
    }
    
    extension UserServerModel: Persistable {
    
        var predicate: NSPredicate? { .userId(equalTo: Int16(self.id)) }
    
        func persist(into object: User) {
            object.avatar = avatar
            object.email = email
            object.first_name = first_name
            object.last_name = last_name
            object.id = Int16(id)
        }
    }
    

    I’m not a fan of mixing repository logic with my models so I’d create some sort of a bridge between your API and persistence models.

    enum PersistenceBridge {
    
        @discardableResult
        static func updateOrCreate<T: Persistable>(model: T) -> T.Persisted? {
            return update(model: model) ?? create(model: model)
        }
    
        @discardableResult
        static func update<T: Persistable>(model: T) -> T.Persisted? {
            let object = DatabaseHandle.shared.fetch(T.Persisted.self, predicate: model.predicate).first
            return persist(model: model, into: object)
        }
    
        @discardableResult
        static func create<T: Persistable>(model: T) -> T.Persisted? {
            let object = DatabaseHandle.shared.add(T.Persisted.self)
            return persist(model: model, into: object)
        }
    
        static func persist<T: Persistable>(model: T, into object: T.Persisted?) -> T.Persisted? {
            if let object = object { model.persist(into: object) }
            return object
        }
    }
    

    You’ll need to add a predicate parameter to your fetch method so you can constrain your results:

    extension DatabaseHandle {
    
        func fetch<T:NSManagedObject>(_ type: T.Type, predicate: NSPredicate? = nil) -> [T] {
            let request = T.fetchRequest()
            request.predicate = predicate
            do{
                let result = try viewContext.fetch(request)
    
                return result as! [T]
            }catch{
                print(error.localizedDescription)
                return []
            }
        }
    }
    

    Finally, add some persistence logic to your ApiHandler and you’re good to go:

    extension ApiHandler {
        func persist<T: Persistable>(response: ApiResponse<[T]>) {
            response.data.forEach {
                PersistenceBridge.updateOrCreate(model: $0)
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search