Prevent for duplicates Core data swift - swift

I want to avoid duplication in my core data swift. I have a sense that it should be done in-app delegate, but I am not sure what has to change.
Here is my code when I add a new Item:
#IBAction func buttonClicked(_ sender: UIBarButtonItem) {
let fetchRequest: NSFetchRequest<Bookmarks> = Bookmarks.fetchRequest()
let bookmark = Bookmarks(context: context)
if bookmarkButton.image == UIImage(systemName: "bookmark") {
bookmarkButton.image = UIImage(systemName: "bookmark.fill")
bookmark.urlToImage = urlImage
bookmark.source = sourceName
bookmark.titleName = titleName
bookmark.urlLink = url
bookmarks.append(bookmark)
} else {
bookmarkButton.image = UIImage(systemName: "bookmark")
if fetchRequest.predicate == NSPredicate.init(format: "titleName == %#", titleName!){
print(bookmark)
do {
let objects = try context.fetch(fetchRequest)
for object in objects {
context.delete(object)
}
try context.save()
} catch _ {
// error handling
}
}
}
do {
try context.save()
} catch {
}
}
Here is the photo of my application with duplication:
If you know anything about how to solve this problem help me out
Thanks in advance

Before inserting a new item check if an item with the same titleName already exists. If not insert the new item.
Something like this
#IBAction func buttonClicked(_ sender: UIBarButtonItem) {
let fetchRequest: NSFetchRequest<Bookmarks> = Bookmarks.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "titleName == %#", titleName!)
if (try? context.fetch(fetchRequest))?.first != nil {
bookmarkButton.image = UIImage(systemName: "bookmark.fill")
} else {
let bookmark = Bookmarks(context: context)
bookmarkButton.image = UIImage(systemName: "bookmark")
bookmark.urlToImage = urlImage
bookmark.source = sourceName
bookmark.titleName = titleName
bookmark.urlLink = url
bookmarks.append(bookmark)
do {
try context.save()
} catch {
print(error)
}
}
}
If the item cannot be identified uniquely by the title add more conditions to the predicate.

Related

save string over saved string in core data

In my swift code below the code saves an item in core data. The goal is to overwrite that item. I am getting a runtime error at
CoreDataHandler.changeName(user: fetchUser!\[indexNumber\], jessica: "jo")
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I don't know how to wrap in the index number. The goal is it to print judo then jo
import UIKit;import CoreData
class ViewController: UIViewController {
var fetchUser: [UserInfo]? = nil
var indexNumber : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
CoreDataHandler.saveObject2( name: "judo")
getText(textNo: indexNumber)
saveTheItem()
}
#objc func saveTheItem(){
CoreDataHandler.changeName(user: fetchUser![indexNumber], jessica: "jo")
}
func getText(textNo:Int) {
// first check the array bounds
let info = helpText.shareInstance.fetchText()
if info.count > textNo {
if let imageData = info[textNo].name
{
print(imageData)
} else {
// no data
print("data is empty Textss")
}
} else {
// image number is greater than array bounds
print("you are asking out of bounds")
}
}
}
class CoreDataHandler : NSManagedObject {
class func saveObject2( name: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "UserInfo", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
do{
try context.save()
return true
}
catch {
return false
}
}
private class func getContext() -> NSManagedObjectContext{
let appD = UIApplication.shared.delegate as! AppDelegate
return appD.persistentContainer.viewContext
}
class func changeName(user: UserInfo,jessica : String) -> Bool
{
let context = getContext()
user.name = jessica
print(jessica)
do{
try context.save()
return true
}
catch{
return false
}
}
}
class helpText: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpText()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveName(data: String) {
let imageInstance = UserInfo(context: context)
imageInstance.name = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func fetchText() -> [UserInfo] {
var fetchingImage = [UserInfo]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserInfo")
do {
fetchingImage = try context.fetch(fetchRequest) as! [UserInfo]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
No offense but your code is a mess.
And there is a big misunderstanding. Core Data records are unordered, there is no index. To update a record you have to fetch it by a known attribute, in your example by name, update it and save it back.
This is a simple method to do that. It searches for a record with the given name. If there is one, update the attribute with newName and save the record.
The code assumes that there is a NSManagedObject subclass UserInfo with implemented class method fetchRequest.
func changeName(_ name: String, to newName: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request : NSFetchRequest<UserInfo> = UserInfo.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
let records = try context.fetch(request)
guard let foundRecord = records.first else { return }
foundRecord.name = newName
try context.save()
} catch {
print(error)
}
}
Regarding your confusing code:
Create CoreDataHandler as singleton (and it must not be a subclass of NSManagedObject). Move the Core Data related code from AppDelegate and the methods to read and write in this class.

NSTableView animation bug while using NSTableViewDiffableDataSource

I am using NSTableViewDiffableDataSource. When I do a multiple delete, I see the wrong insert-animation after (flicker and moving from top or bottom). How fix it?
// I am using UUID instead of NSManagedObjectID because when I create a new NSManagedObject it first has a temporary objectID.
func configureDataSource() {
let dataSource : NSTableViewDiffableDataSource<String, UUID> = .init(tableView: tableView) { table, column, index, objectID in
let request = NSFetchRequest<Task>()
request.entity = Task.entity()
request.predicate = NSPredicate(format: "id = %#", argumentArray: [objectID])
guard let task = try? self.viewContext.fetch(request).first as? Task else {
return NSView()
}
let cell = self.create(viewFor: column, task: task)
return cell
}
dataSource.defaultRowAnimation = .effectGap
dataSource.sectionHeaderViewProvider = nil
self.dataSource = dataSource
}
func storeDidReloadContent() {
var snapshot = NSDiffableDataSourceSnapshot<String, UUID>()
snapshot.appendSections([""])
snapshot.appendItems(store.objects.compactMap{ $0.id }, toSection: "")
dataSource.apply(snapshot, animatingDifferences: false)
}
func storeDidChangeContent(with snapshot: NSDiffableDataSourceSnapshotReference) {
var newSnapshot = NSDiffableDataSourceSnapshot<String, UUID>()
newSnapshot.appendSections([""])
newSnapshot.appendItems(store.objects.compactMap{ $0.id }, toSection: "")
dataSource.apply(newSnapshot, animatingDifferences: true)
}
// class ObjectFactory
// Batch operation
public func delete(objects: [T]) {
let objectIDs = objects.compactMap{ $0.objectID }
CoreDataStorage.shared.performBackground { privateContext in
objectIDs.forEach{
let object = privateContext.object(with: $0)
privateContext.delete(object)
}
try? privateContext.save()
}
}
PS: Store class (var store) incapsulate all works with NSFetchedResultController.
ObjectFactory class incapsulate all works with NSManagedObjects.
NSFetchedResultController works with only main NSManagedObjectContext.
Batch operation in NSTableView is pain :(

Use core data index to fetch a specific item from core data

My swift code below when loaded places 3 items in the core data entity named "UserName". When the user enters a number into textfield enterT I want the label labelName to display it. So when the user enters 1 the label should display jessica biel because Jesical Biel is the first name entered. Someone stated the suggestion below to solve this problem. I dont know exactly how to do this.I have added a gif below.
Convert the entered number to Int. If this succeeds pass the integer to joke and fetch the record matching the idx attribute.
https://github.com/redrock34/index-fetch
import UIKit
import CoreData
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet var labelName : UILabel!
#IBOutlet var enterT : UITextField!
lazy var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
openDatabse()
fetchData()
enterT.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
guard let index = Int(textField.text!) else {
// display an alert about invalid text
return
}
joke(at: index - 1)
}
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
labelName.text = user.username
}
} catch {
print("Could not fetch \(error) ")
}
}
func openDatabse()
{
let names = ["kim kardashian", "jessica biel", "Hailey Rienhart"]
for i in 0..<names.count {
let newUser = Users(context: context)
newUser.username = names[i]
newUser.idx = Int32(i + 1)
}
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed", error)
}
}
func fetchData()
{
print("Fetching Data..")
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
request.returnsObjectsAsFaults = false
do {
let result = try context.fetch(request)
for data in result as! [NSManagedObject] {
let userName = data.value(forKey: "username") as! String
print("User Name is : "+userName)
}
} catch {
print("Fetching data Failed")
}
}}
Of course you have to assign values to the idx attribute and you have to assign the result of the fetch to the label.
First replace
let appDelegate = UIApplication.shared.delegate as! AppDelegate //Singlton instanc
var context:NSManagedObjectContext!
with
lazy var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
Then replace both openDatabse and saveData with
func openDatabse()
{
let names = ["kim kardashian", "jessica biel", "Hailey Rienhart"]
for i in 0..<names.count {
let newUser = Users(context: context)
newUser.name = names[i]
newUser.idx = Int32(i + 1)
}
print("Storing Data..")
do {
try context.save()
} catch {
print("Storing data Failed", error)
}
}
Finally add a line in joke to display the value
func joke(at index : Int) {
let fetchRequest = NSFetchRequest<Users>(entityName: "Users")
fetchRequest.predicate = NSPredicate(format: "idx == %d", Int32(index))
do {
if let user = try context.fetch(fetchRequest).first {
labelName.text = user.username
}
} catch {
print("Could not fetch \(error) ")
}
}
It creates the records and assigns the proper indexes. Then entering a number in the text field should work.
But – once again – on each launch of the app the 3 records are inserted again with the same names and indexes. Be aware of that!

Coredata - updating information

I have two viewcontrollers.
VC1 is home viewcontroller.
In VC2 I have text fields so that the user inputs results and a save button where data is saved.
The strange thing is when I save I have a fetchrequest to show new data but... whats happening is that old data being showed to me.
But if I go to VC1, new data is there.
This is my code (VC2):
#IBOutlet var designLabel: UITextField!
#IBOutlet var localLabel: UITextField!
#IBOutlet var typeLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest:NSFetchRequest<Arm> = Land.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id = %#", "\(choosenArmID!)")
do{
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
for result in searchResults as [Arm]{
//to get results in coredata and put them in the text fields: for edition
self.title = result.designation
designLabel.text = result.designation
localLabel.text = result.local
typeLabel.text = result.type
}
}
catch{
print("Error: \(error)")
}
}
for the save button I have this:
#IBAction func saveButton(_ sender: UIBarButtonItem) {
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Arm")
request.predicate = NSPredicate(format: "id = %#", "\(choosenArmID!)")
do {
let results = try context.fetch(request)
if results.count > 0 {
for result in results as! [NSManagedObject] {
result.setValue(designLabel.text, forKey: "designation")
result.setValue(typeLabel.text, forKey: "type")
result.setValue(localLabel.text, forKey: "local")
do {
try context.save()
print("context saved") //I get into this
} catch {
print("Error updating")
}
}
}
} catch {
print ("Error")
}
//I do another fetchrequest to show new results
let fetchRequest:NSFetchRequest<Arm> = Arm.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "id = %#", "\(choosenArmID!)")
do{
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
for result in searchResults as [Arm]{
print("New saved Results: ")
print("title: \(result.designation!)")
print("local: \(result.local!)")
print("type: \(result.type!)")
}
}
catch{
print("Error: \(error)")
}
}
Now in the new results I'd expected the new saved results, but instead I get the old ones, even though the new results were saved!!!
Can you help me please?

Show indicator for two request

I have two requests. Each of them getting different data. I need to show an indicator when the all of two request is requesting. How i can do this?
this is my first request:
func productList(tableView:UITableView,spinner:UIActivityIndicatorView,index1:Int,index2:Int,index3:Int){
if product.count<=0{
alamoFireManager?.request(.GET, "http://mobile.unimax.kz/api/Default1",parameters: ["type1id":index1,"type2id":index2,"type3id":index3,"name":"","userid":1089])
.responseJSON { response in
guard response.result.error == nil else {
if let httpError = response.result.error {
switch(httpError.code){
case -1009:
let alert = UIAlertView(title: "Ошибка",message: "Нету интернета!!",delegate: nil,cancelButtonTitle: "OK")
alert.show()
break
default:
let alert = UIAlertView(title: "Ошибка",message: "Повторите попытку!!",delegate: nil,cancelButtonTitle: "OK")
alert.show()
break
}
} else { //no errors
let statusCode = (response.response?.statusCode)!
print(statusCode)
}
spinner.stopAnimating()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
return
}
if let value = response.result.value {
// handle the results as JSON, without a bunch of nested if loops
let product = JSON(value)
for (_,subJson):(String, JSON) in product {
let img:NSData
if let src=subJson["sphoto"].string{
if src.containsString("jpg"){
let range = src.startIndex.advancedBy(2)..<src.endIndex
let substring = src[range]
var urlString = "http://admin.unimax.kz/\(substring)"
urlString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
if let dataFromURL=NSData(contentsOfURL: NSURL(string: urlString)!){
img=dataFromURL
}
else{
img=NSData(contentsOfURL: NSURL(string: "http://zhaksy-adam.kz/Images/domalak.png")!)!
}
}
else{
img=NSData(contentsOfURL: NSURL(string: "http://zhaksy-adam.kz/Images/domalak.png")!)!
}
}
else{
img=NSData(contentsOfURL: NSURL(string: "http://zhaksy-adam.kz/Images/domalak.png")!)!
}
//Do something you want
let id=subJson["id"].int!
let name=subJson["name"].string!
let price=subJson["price"].int!
let description=subJson["description"].rawString()
self.product.append(Product(id:id,title: name, img: UIImage(data: img), price: price,desc:description!))
}
spinner.stopAnimating()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
tableView.reloadData()
}
}
}
else{
spinner.stopAnimating()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
tableView.reloadData()
}
}
and this is my second request:
func makeGetFav(userID:Int,completionHandler: (responseObject:JSON) -> ()) {
alamoFireManager?.request(.GET, "http://mobile.unimax.kz/api/Klientapi/?authid=\(userID)")
.responseJSON {response in
guard response.result.error == nil else {
if let httpError = response.result.error {
switch(httpError.code){
case -1009:
let alert = UIAlertView(title: "Ошибка",message: "Нету интернета!!",delegate: nil,cancelButtonTitle: "OK")
alert.show()
break
default:
let alert = UIAlertView(title: "Ошибка",message: "Повторите попытку!!",delegate: nil,cancelButtonTitle: "OK")
alert.show()
break
}
} else { //no errors
let statusCode = (response.response?.statusCode)!
print(statusCode)
}
return
}
completionHandler(responseObject: JSON(response.result.value!))
}
}
func getFavs(userID:Int,tableView:UITableView,spinner:UIActivityIndicatorView){
getFavRequets(userID){(responseObject) in
if responseObject != nil{
self.favs.removeAll()
self.localDB.clearFav()
for (_,subJson):(String, JSON) in responseObject {
self.favs.append(FavModel(id: subJson["id"].int!, title: subJson["name"].string!, price: subJson["price"].int!))
}
spinner.stopAnimating()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
tableView.reloadData()
}
}
}
there are i call it all:
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
indicator.startAnimating()
localDB.getUserInfo()
getRequests.productList(tableView, spinner: indicator, index1: catalog1Index, index2: catalog2Index, index3: catalog3Index)
if localDB.user.count>0{
getRequests.getFavs(localDB.user[0].id, tableView: tableView, spinner: indicator)
}
localDB.checkCart(tableView, tabCtrl: tabBarController!)
You can control it using a singleton to start and stop it according to the number of running requests:
class NetworkActivityIndicator: NSObject {
static let sharedInstance = NetworkActivityIndicator()
private override init() {
}
var count = 0 {
didSet {
self.updateIndicator()
}
}
private func updateIndicator() {
if count > 0 {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
} else {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
}
The you simply call NetworkActivityIndicator.sharedInstance.count += 1 just before the request and NetworkActivityIndicator.sharedInstance.count += 1 when you get the response
the easiest way is to add two variables to your class that indicate whether the associated request is complete, and then to create a function that stops the spinner only if both the variables indicate the calls are complete.
if you want to use the class for more than one ViewController then I suggest adding a struct-enum combo to organise the variables that are indicating which requests are currently underway.
eg
class GetRequests {
var productsLoaded = false
var favoritesLoaded = false
func stopSpinnerIfNeeded(spinner: UIActivityIndicatorView) {
if productsLoaded && favoritesLoaded {
spinner.stopAnimating()
spinner.hidden = true
}
}
func productList(tableView:UITableView,spinner:UIActivityIndicatorView,index1:Int,index2:Int,index3:Int){
defer {
productsLoaded = true
stopSpinnerIfNeeded(spinner)
}
if product.count<=0{
alamoFireManager?.request(.GET, "http://mobile.unimax.kz/api/Default1",parameters: ["type1id":index1,"type2id":index2,"type3id":index3,"name":"","userid":1089])
.responseJSON { response in
guard response.result.error == nil else {
if let httpError = response.result.error {
switch(httpError.code){
case -1009:
let alert = UIAlertView(title: "Ошибка",message: "Нету интернета!!",delegate: nil,cancelButtonTitle: "OK")
alert.show()
break
default:
let alert = UIAlertView(title: "Ошибка",message: "Повторите попытку!!",delegate: nil,cancelButtonTitle: "OK")
alert.show()
break
}
} else { //no errors
let statusCode = (response.response?.statusCode)!
print(statusCode)
}
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
return
}
if let value = response.result.value {
// handle the results as JSON, without a bunch of nested if loops
let product = JSON(value)
for (_,subJson):(String, JSON) in product {
let img:NSData
if let src=subJson["sphoto"].string{
if src.containsString("jpg"){
let range = src.startIndex.advancedBy(2)..<src.endIndex
let substring = src[range]
var urlString = "http://admin.unimax.kz/\(substring)"
urlString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
if let dataFromURL=NSData(contentsOfURL: NSURL(string: urlString)!){
img=dataFromURL
}
else{
img=NSData(contentsOfURL: NSURL(string: "http://zhaksy-adam.kz/Images/domalak.png")!)!
}
}
else{
img=NSData(contentsOfURL: NSURL(string: "http://zhaksy-adam.kz/Images/domalak.png")!)!
}
}
else{
img=NSData(contentsOfURL: NSURL(string: "http://zhaksy-adam.kz/Images/domalak.png")!)!
}
//Do something you want
let id=subJson["id"].int!
let name=subJson["name"].string!
let price=subJson["price"].int!
let description=subJson["description"].rawString()
self.product.append(Product(id:id,title: name, img: UIImage(data: img), price: price,desc:description!))
}
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
tableView.reloadData()
}
}
}
else{
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
tableView.reloadData()
}
}
func getFavs(userID:Int,tableView:UITableView,spinner:UIActivityIndicatorView){
getFavRequets(userID){(responseObject) in
if responseObject != nil{
self.favs.removeAll()
self.localDB.clearFav()
for (_,subJson):(String, JSON) in responseObject {
self.favs.append(FavModel(id: subJson["id"].int!, title: subJson["name"].string!, price: subJson["price"].int!))
}
favoritesLoaded = true
stopSpinnerIfNeeded(spinner)
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
tableView.reloadData()
}
}
}
A very fast way to do it:
// global var
var isIndicatorActive : Bool = false
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
indicator.startAnimating()
self.isIndicatorActive = true
In the line before each alamoFireManager?.request you call :
if isIndicatorActive == false {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
indicator.startAnimating()
self.isIndicatorActive = true
}
And after each line spinner.stopAnimating() add:
self.isIndicatorActive = false
I suggest you to using single network activity indicator and keep a counter which keep track of network activity within the application. I know my answer is more in text, but looking at your code it seems you can implement the following.
0 indicates no activity indicator.
As soon as a new activity starts, increment the counter do a check if counter is greater than 0 then show the indicator.
Decrement the counter when the activity task finishes. On decrement keep a check if counter is 0 then set the indicator visibility to false.
PS: Don't forget to increment/decrement in sync blocks. You may use objc_sync_enter(..) and objc_sync_exit(..) methods for this.
Thx
thanks everybody for help. I solve it like this:
func makeReuest1(){
if localDB.user.count>0{
getRequests.getFavs(localDB.user[0].id)
}
makeRequest2()
}
func makeRequest2(){
getRequests.productList(tableView, spinner: indicator, index1: catalog1Index, index2: catalog2Index, index3: catalog3Index)
}