Retreiving data with Core Data - ios

I'm working on a project where I should save data locally with Core Data.
Here is my workflow :
To start, I ask the user to fill a form, let's say his firstname and lastname.
He clicks on the submit button, then data is saved on the device using Core Data
User is redirected to the "last filled form" view controller.
I have a bar button item that when clicked can show the latest filled form.
I should test if the array of filled forms is empty, then the button should be disabled.
Otherwise, the button should be enabled ...
I tried this piece of code, where I fetch data from the database and affected to an array but the button seams not working at all and it never gets disabled ...
class ViewController: UIViewController {
var userIdentity: UserIDentity = UserIDentity(context: PersistanceService.context)
var identityArray = [UserIDentity]()
override func viewDidLoad() {
super.viewDidLoad()
self.fetchIdentityHistoryArray()
}
func fetchIdentityHistoryArray(){
let fetchRequest: NSFetchRequest<UserIDentity> = UserIDentity.fetchRequest()
do {
let identityArray = try PersistanceService.context.fetch(fetchRequest)
if identityArray.isEmpty {
self.identityHistoryButton.isEnabled = false
}
else {
self.identityHistoryButton.isEnabled = true
}
}
catch {
print("Error fetching sworn statement history !")
}
}
}
So I have 2 questions :
What's wrong with my code ?
How can I manage that when the user clicks on the "back button" for the first form filled ever, the "history button" can refresh itself and turn from disabled to enabled button ?
Any help is much appreciated. Thank you

You are initialising a core data object directly in your code
var userIdentity: UserIDentity = UserIDentity(context: PersistanceService.context)
This new object will exist in Core Data and will be included everytime you execute the fetch request. You must understand that Core Data is not a database layer it is an object mapping layer so even if you haven't called save() yet the object exists in the Core Data context.
Either change the declaration to
var userIdentity: UserIDentity?
or remove it completely if it isn't used.

Related

Observing the changes to Object Swift 4

I have Swift object with about 20 Properties. In the app, there is a screen to get the user input and create the above swift object from the user entered value. Right now, if the user clicks the back button, all the user entered data will lose. So I want to alert the user to save the details if he/she has made any changes. How do we identify if the user has made any changes to the properties. Is it possible to use KVO in this case as we have too many properties?
What you need is a data model to hold the information in that particular screen, and then compare it with the original data when leaving the screen.
For the sake of simplicity, let's assume your screen has 2 text fields. One holds a name and another the age of a person.
struct Person: Equatable {
var name: String
var age: Int
}
When you first open this screen, the model will have the default values. Create a copy of this model and whenever the user makes any changes to the values on the screen, update the copy.
class YourViewController: UIViewController {
// Populate these 2 values when creating your view controller
var person: Person!
var personCopy: Person!
.
.
.
// You need to add this target to your text fields
#objc func textFieldDidChange(_ textField: UITextField) {
switch textField {
case personTextField:
personCopy.name = personTextField.text!
case ageTextField:
personCopy.age = Int(ageTextField.text!)!
default:
// Handle other text fields here or write separate cases for them
}
func dismissView() {
if person == personCopy {
// Dismiss your view
} else {
// Show alert
}
}
}
If the user presses the back button, all you need to do is compare these 2 models and check if they are the same. If they are the same, you can go back, if not, prompt an alert asking the user to save changes or discard.
I think KVO would be overkill here. Use KVO only for distant objects in your app.
Here you have the UITextFields in your viewController and you must have reference to the user object anyway.
Easier is: On the back button press you would check all text properties of your UITextField objects to the (existing) values of your user object. If one of them has changed then present the alert.

do network call from current ViewController or parent ViewController?

I'm writing an app that contains network call in every other screen. The result of calls would be the dataSource for a specific screen.
The question is, should I do network call in the parent viewController and inject the data before pushing current viewController or push currentViewController and do network call on viewDidLoad()/viewWillAppear()?
Both the methods makes sense to me.
Where you make the request to network should actually make no difference. You are requesting some data which you will have to wait for and present it. Your question is where should you wait for the data to be received.
As #Scriptable already mentioned you can do either of the two. And which to use depends on what kind of user experience you wish to have. This varies from situation to situation but in general when we create a resource we usually wait for it on current screen and when we are reading resources we wait for it on the next screen:
For instance if you are creating a new user (sign up) after you will enter a new username and password an indicator will appear and once the request is complete you will either navigate to next screen "enter your personal data" or you will receive a message like "User already exists".
When you then for instance press "My friends" you will be navigated to the list first where you will see activity indicator. Then the list appears or usually some screen like "We could not load your data, try again."
There are still other things to consider because for the 2nd situation you can add more features like data caching. A lot of messaging applications will for instance have your chats saved locally and once you press on some chat thread you will be navigated directly to seeing whatever is cached and you may see after a bit new messages are loaded and shown.
So using all of this if we get back to where you should "call" the request it seem you best do it before you show the new controller or at the same time. At the same time I mean call it the load on previous view controller but load the new view controller before you receive the new data.
How to do this best is having a data model. Consider something like this:
class UsersModel {
private(set) var users: [User]?
}
For users all we need is a list of them so all I did was wrapped an array. So in your case we should have an option to load these users:
extension UsersModel {
func fetchUsers() {
User.fetchAll { users, error in
self.users = users
self.error = error // A new property needed
}
}
}
Now a method is added that loads users and assigns them to internal property. And this is enough for what we need in the first view controller:
func goToUsers() {
let controller = UserListViewController()
let model = UserModel()
controller.model = model
model.fetchUsers()
navigationController.push(controller...
}
Now at this point all we need is to establish the communication inside the second view controller. Obviously we need to refresh on viewDidLoad or even on view will appear. But we would also want some delegate (or other type of connections) so our view controller is notified of changes made:
func viewDidLoad() {
super.viewDidLoad()
self.refreshList()
self.model.delegate = self
}
And in refresh we should now have all the data needed:
func refreshList() {
guard let model = model else {
// TODO: no model? This looks like a developer bug
return
}
if let users = model.users {
self.users = users
tableView?.reloadData()
if users.count.isEmpty {
if let error = model.error {
// TODO: show error screen
} else {
// TODO: show no data screen
}
}
} else {
// TODO: show loading indicator screen
}
}
Now all that needs to be done here is complete the model with delegate:
extension UsersModel {
func fetchUsers() {
User.fetchAll { users, error in
self.users = users
self.error = error // A new property needed
self.delegate?.usersModel(self, didUpdateUsers: self.users)
}
}
}
And the view controller simply implements:
func usersModel(_ sender: UserModel, didUpdateUsers users: [User]?) {
refreshList()
}
Now I hope you can imagine the beauty of such a system that your model could for instance first asynchronously load users from some local cache or database and call the delegate and then call the request to server and call the delegate again while your view controller would show appropriate data for any situation.

Create a favorite button that connects to a favorite tableview in swift

I was wondering how to create a favorite button in swift that when that button is pressed the object that is "favorited" becomes part of a tableview and stays that way. I hear that core data is a way to do this, but I am still struggling to have the button change when pressed.
When I was creating a button all I get is a button that only changes the first time it presses. An example is I have an empty star button, which means that it is not a favorite, and when i pressed it it changes to a filled heart, which means that it is favorited. When I press the button a second time, when it is currently a filled star, it doesn't change and still remains a filled star.
The other problem that I am having is sending the information from the object to a favorite tableview. I am not sure how to keep something in a tableview as long as the favorite button is switched to filled star.
What I am seeing a lot of is this topic but it is all about making a favorite button in table views. I am not looking for tableviews. An example of what I am trying to do is like a favorite book app. The app opens as a tableview full of books, and when a cell is pressed it opens a new view controller with that book's information, and at the top is a favorite button that is normally an empty star. If I like that book I would like to press that button and the empty star becomes a filled star button, indicating that I like it. The book's information is then sent to a new table view that holds all of the liked books i have done before. If I no longer like that book i would like to press the filled star button again and it is removed from the favorite tableview list.
I am some experience with mysql in regards to swift, and I know that someone people have made comments about using some kind of persistence to save the state of the button.
My biggest problem is that I don't even know where to start. Every attempt I make seems to end the same way, so i don't really have any source code for others to see. I have looked online and through github but the closest thing I could find was a cocoapod named DoButton, but it hasn't been updated in a long time and I am worried that it won't last with another swift update. If someone could lead me in the right path or know a good tutorial, I would greatly appreciate it. If there are any questions I can answer i will answer them to the best of my ability.
Another Update: I managed to get the button to work. It connects to core data and saves the state of the button even when it is quit. Now all that is left is creating a favorite tableView to store the favorites.
Here is the code:
class ViewController: UIViewController {
var buttonIsSelected = false
var favorites = [Favorite]()
#IBOutlet var onOffButton: UIButton!
let image1 = UIImage(named: "empty") as UIImage?
let image2 = UIImage(named: "filled") as UIImage?
override func viewDidLoad() {
super.viewDidLoad()
print("buttonIsSelected: \(buttonIsSelected)")
let fetchedRequest: NSFetchRequest<Favorite> = Favorite.fetchRequest()
do {
let favorite = try PersistenceService.context.fetch(fetchedRequest)
for fav in favorite {
resetAllRecord(entity: fav.favorite)
buttonIsSelected = fav.favorite
print("fav.favorite: \(fav.favorite)")
print("button: \(buttonIsSelected)")
if fav.favorite == true {
onOffButton.setImage(image2, for: .normal)
}
}
} catch {
}
}
//button Action
#IBAction func onOffButtonPressed(_ sender: Any) {
buttonIsSelected = !buttonIsSelected
if buttonIsSelected == true {
onOffButton.setImage(image2, for: .normal)
} else if buttonIsSelected == false {
onOffButton.setImage(image1, for: .normal)
}
saveBool(bool: buttonIsSelected)
}
//save to core data
func saveBool(bool: Bool) {
if bool == true {
print("favorite")
print("buttonIsSelected \(buttonIsSelected)")
let liked = Favorite(context: PersistenceService.context)
liked.favorite = bool
PersistenceService.saveContext()
favorites.append(liked)
} else if bool == false {
print("unfavorite")
print("buttonIsSelected \(buttonIsSelected)")
let liked = Favorite(context: PersistenceService.context)
liked.favorite = bool
PersistenceService.saveContext()
favorites.append(liked)
}
}
//clears core data so it doens't get full
func resetAllRecord(entity: Bool) {
let context = PersistenceService.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Favorite")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do
{
try context.execute(deleteRequest)
try context.save()
}
catch
{
print ("There was an error")
}
}
}
In the object class create a bool parameter called isFavorite and default set it to false. When the favorite button is pressed the bool should change from false to true. When you have to access only the "favorite" objects, with a for loop iterate through your array of objects and through an if statement check if the isFavorite parameter is true. If it is, the element must be appended to a new object array maybe called favoriteObjects, and there you have all your favorite objects. The rest is pretty straightforward.

Parse array seems to store locally to my app session

I have a weird bug in my app that I haven't seen before. Basically I have two views - User Profile and Edit Profile. In the User Profile view, I retrieve two things from Parse:
An array of strings telling the order of the user's profile photos (e.g. ["pic1", "pic3", "pic2"])
The photo files themselves (up to 6)
In the Edit Profile View, the user is able to upload new photos as well as rearrange the order array. The view also has a button to close the view and discard any changes as seen here:
#IBAction func cancelTapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
The problem is that if I make any changes to photos order array such as rearranging it or appending to it, then hit the "Cancel" button, the view dismisses as it should. However, when I go back into the Edit Profile view or even if I close the User Profile then reopen that, both views show the new order array that wasn't supposed to be saved because I hit the "Cancel" button. The only way I can fix is by closing the app and reopening.
Is the array being saved locally to the iPhone? Please let me know if you need any more info or to see more code!
Edit: Forgot to mention, when I look in Parse the values aren't stored on there, but the Xcode logs say the array is changed when reloading the view.
Edit 2: Here is the instantiation of self.photoOrder:
var photoOrder:NSMutableArray = []
currentuser instantiation from Constants.swift
var currentuser = PFUser.currentUser()
I don't know how your reorder method looks like and if your calling the save method on the PFUser object when reording is done. It seems like you're overwritting the photoOrder attribute in your edit profile view somehow.
Solution 1
A simple solution would be to backup the current value when you load the view:
var photoOrderBackup:NSMutableArray = []
// viewDidLoad
self.photoOrder = currentuser.objectForKey("photoOrder") as! NSMutableArray
self.photoOrderBackup = currentuser.objectForKey("photoOrder") as! NSMutableArray
Then you got the chance to apply the backup values when the user cancels the operations.
#IBAction func cancelTapped(sender: AnyObject) {
currentUser.photoOrder = self.photoOrderBackup; // Apply backup value
// Perhaps add the save method here after you've overwritten the photoOrder value
self.dismissViewControllerAnimated(true, completion: nil)
}
Solution 2
If the changes aren't saved to the currentuser object yet, refreshing the object could help to dismiss pending changes.
#IBAction func cancelTapped(sender: AnyObject) {
currentUser.refresh() // Refresh object with local data
self.dismissViewControllerAnimated(true, completion: nil)
}

Problems with storing UISwitch state

I have a setting in my app that I am having issues retaining the state of. Basically, the UISwitch is on by default, however when updating my app to a newer version, it seems to switch off. I figured out that if the user has opened the settings menu in version 1.0, then this isn't an issue, however if they have never changed a setting, or even opened the settings menu, then it's a problem. Therefore, it must be an issue somewhere in viewWillAppear() but I can't figure out what it is. The code I have for storing the switches state seems really overcomplicated.
This is the code I have in my settings menu:
#IBAction func dupOffOnSwitch(sender: AnyObject) {
if dupSwitch.on == true {
autoAdjust = true
println(autoAdjust)
} else {
autoAdjust = false
println(autoAdjust)
}
NSUserDefaults.standardUserDefaults().setBool(autoAdjust, forKey: "autoAdjustSettings")
}
override func viewWillAppear(animated: Bool) {
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
if autoAdjust == true {
dupSwitch.on = true
} else {
dupSwitch.on = false
}
}
if userReturnedAuto == false {
dupSwitch.on = true
themeSwitch.on = false
userReturnedAuto = true
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "userReturnedAuto")
NSUserDefaults.standardUserDefaults().setBool(userReturnedAuto, forKey: "autoAdjustSettings")
}
I am declaring the bool 'autoAdjust' in a different view controller as a global variable. In that same view controller in viewWillAppear() I have this code:
autoAdjust = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
Can anyone suggest a better way of storing the switches state while also having it on by default when the app is first launched? Or a fix to my current solution.
NSUserDefaults.standardUserDefaults().boolForKey("key") defaults to false. Therefore, as in your case, if the user never sets the key, then it will be false and the switch will default to off.
Instead, rename the key and use it as the negative. So instead, call it autoAdjustSettingsOff which will default to false and the switch will default to on. Don't forget to switch around the true/false settings in your conditional blocks.
Not sure if this is over kill(or just stupid). But why not use CoreData to save all the users settings. Then when the app becomes active(in your appDelegate) you can set the NSUserDefaults from these stored values. Just a thought :)
For shortness I will only make an example for one switch, you can then fill out and add the rest. I will also assume you will be updating the NSUserDefaults when the switch changes state. We will only update/save the core data when the app goes to in-active and load settings once when app becomes active.
Also if anyone sees something I missed or could be done better please let me know and I will update the answer.
First off you need to make sure you import the CodeData libraries into your view controller:
import CoreData
class fooBarViewController: UIViewController {
You need to create and entity in core data: lets assume it is called "Settings"
then once you have created the entity, you need to add some attributes. Press the "+" symbol.
Now add the attribute and set the name to "dupSwitch" and type to "Boolean". Add an attribute for each setting you need to store.
Next: You need to create a new swift file. Type NSObject. name it something like "dataObjects".
Inside you you will use the following code: (first remove all current code - make it blank)
import UIKit
import CoreData
#objc(settingObj)
class settingObj: NSManagedObject {
#NSManaged var dupSwitch:NSNumber //do this for each setting
}
Next you need to go back to your CoreData dataModel and do the following.
1. click on "Default" under the section "Configuration"
2. You will then get a list of all your "entities" select "Settings"
3. click on class and edit the field to "settingObj"
Now that you have you CoreData set up. You can now save/edit/delete data as need be.
For example when you need to load all saved user settings when app goes will become active.
in your AppDelegate.swift file: import the CoreData libraries.
import CoreData
then add this function if it is not already present
func applicationWillEnterForeground(application: UIApplication) {
}
inside "applicationWillEnterForeground" you will load any settings that have been stored in CoreData
var results:[settingObj] = []
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let req = NSFetchRequest(entityName: "Settings")
let tapResults = context.executeFetchRequest(req, error: nil)
if let presentResults = tapResults {
if let castedResults = presentResults as? [settingObj] {
for item in castedResults {
let dupSwitch = item.dupSwitch
let settingSwitch = item.whateverElseYouNeedExample
//now based on this we can set the NSDefault
NSUserDefaults.standardUserDefaults().setBool(dupSwitch, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().setBool(settingSwitch, forKey: "whateverElseYouNeedExample")
}
}
}
Now when you want to save the users settings. I would would just it when app goes in-active.
First we will check if the settings have already been saved. If so, then we will just perform
an update. If not, we will save a new entry into yuor CoreData Entity.
add the following func to your appDelegate(if not already there):
func applicationDidEnterBackground(application: UIApplication) {
}
EDIT I HAVE TO GO TO WORK WILL COMPLETE REST OF ANWSER WHEN I GET BACK HOME. REALLY SORRY.

Resources