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.
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 :(
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!
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?
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)
}