Core data doesn't seem to be saved - ios

I am trying to create an app that requires the user to successfully enter a pin before being allowed onto the rest of the app.
I did some searching around and found a basic existing coredata example app that works here.
I went into the xcdatamodel and deleted their attributes and replaced with "pin" which is a String. Here is a screenshot of the xcdatamodel.
Then I modified the ViewController so that the createData UIbutton opens a alertController that prompts the user to enter a new pin twice, checks they are the same, and if they are it creates a coredata entry with that pin.
Here is the relevant code of the ViewController:
import UIKit
import CoreData
class ViewController: UIViewController {
var firstPinNumber:String = ""
var secondPinNumber:String = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func createData(_ sender: Any) {
let enterPinAlertController = UIAlertController(title: "Enter New PIN", message: "", preferredStyle: .alert)
enterPinAlertController.addTextField{ (textField1:UITextField)->Void in
textField1.placeholder = "Enter PIN"
textField1.isSecureTextEntry = true
}
enterPinAlertController.addTextField{ (textField2:UITextField)->Void in
textField2.placeholder = "Re-Enter PIN"
textField2.isSecureTextEntry = true
}
let okAction = UIAlertAction(title: "OK", style: .cancel) {(action) in
if let textFields = enterPinAlertController.textFields {
let theTextFields = textFields as [UITextField]
self.firstPinNumber = theTextFields[0].text!
self.secondPinNumber = theTextFields[1].text!
if self.firstPinNumber != self.secondPinNumber {
print ("PINs dont match!")
let pinsDontMatchAlertController = UIAlertController(title: "PINs don't match!", message: "Try again", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .cancel) {(action) in
}
pinsDontMatchAlertController.addAction(okAction)
self.present(pinsDontMatchAlertController, animated: true, completion: nil)
}
}
}
enterPinAlertController.addAction(okAction)
self.present(enterPinAlertController, animated: true, completion: nil)
createPIN(pinNum: secondPinNumber)
}
func createPIN(pinNum: String){
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Now let’s create an entity and new user records.
let userEntity = NSEntityDescription.entity(forEntityName: "User", in: managedContext)!
let user = NSManagedObject(entity: userEntity, insertInto: managedContext)
user.setValue(pinNum, forKeyPath: "pin")
print(user.value(forKey: "pin") as Any)
//Now we have set the pin. The next step is to save it inside the Core Data
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
#IBAction func retrieveData(_ sender: Any) {
let storedPin = retrievePIN()
print(storedPin)
}
func retrievePIN()->String {
var storedPin:String = ""
//As we know that container is set up in the AppDelegates so we need to refer that container.
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return "" }
//We need to create a context from this container
let managedContext = appDelegate.persistentContainer.viewContext
//Prepare the request of type NSFetchRequest for the entity
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "User")
fetchRequest.fetchLimit = 1
// fetchRequest.predicate = NSPredicate(format: "username = %#", "Ankur")
// fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "email", ascending: false)]
//
do {
let result = try managedContext.fetch(fetchRequest)
for data in result as! [NSManagedObject] {
if data.value(forKey: "pin") != nil {
storedPin = data.value(forKey: "pin") as! String
print(storedPin)
} else {
print ("Found nil")
}
}
} catch {
print("Failed")
}
return storedPin
}
Using breakpoints I have ascertained that it enters the createPin() function, but it seems to enter that function BEFORE it presents the enterPinAlertController to enter the new pin, even though createPin() is called AFTER the enterPinAlertController is presented.
Also if I use the retrieveData UIButton it prints out "Found nil"
So if what I'm thinking is correct, its creating a coredata entry with an empty string, or nothing at all?
How can I fix this so that it creates a coredata entry with the string the user enters as the new pin, and also retrieves it later?

Your call to createPin needs to be inside the action handler for okAction. As you have it now, secondPinNumber will be called before the alert has been shown, so it will be empty or nil, depending on how you initialise it.
IBAction func createData(_ sender: Any) {
let enterPinAlertController = UIAlertController(title: "Enter New PIN", message: "", preferredStyle: .alert)
enterPinAlertController.addTextField{ (textField1:UITextField)->Void in
textField1.placeholder = "Enter PIN"
textField1.isSecureTextEntry = true
}
enterPinAlertController.addTextField{ (textField2:UITextField)->Void in
textField2.placeholder = "Re-Enter PIN"
textField2.isSecureTextEntry = true
}
let okAction = UIAlertAction(title: "OK", style: .cancel) {(action) in
if let textFields = enterPinAlertController.textFields,
let firstPinNumber = textFields[0].text,
let secondPinNumber = textFields[1].text,
firstPinNumber == secondPinNumber {
createPIN(pinNum: secondPinNumber)
} else {
print ("PINs dont match!")
let pinsDontMatchAlertController = UIAlertController(title: "PINs don't match!", message: "Try again", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .cancel)
pinsDontMatchAlertController.addAction(okAction)
self.present(pinsDontMatchAlertController, animated: true, completion: nil)
}
}
}
enterPinAlertController.addAction(okAction)
self.present(enterPinAlertController, animated: true, completion: nil)
}

Related

Checking if entity exist before saving it to core data swift4.

I'm trying to add data to Core Data in my application. When I save it without checking if it already exists it works fine. But I don't what duplicates in my core data so what I do I fetch all the entities with the name of entity im trying to add first and then check if it's 0 I add it else no. But I'm keep getting an error when trying to save. If anyone could help me with the problem.
This is my checking function:
func entityExists(name: String) -> Bool {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "name")
fetchRequest.includesSubentities = false
var entitiesCount = 0
do {
entitiesCount = try coreDataController.mainContext.count(for: fetchRequest)
}
catch {
print("error executing fetch request: \(error)")
}
if entitiesCount == 0{
return true
} else {
return false
}
}
This is my code when I save the data.
if entityExists(name: (scrimmagePassedOver?.name)!) == true{
coreDataController.saveContext()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
let alert = UIAlertController(title: "Saved!", message: "You have saved your Scrimmage.", preferredStyle: UIAlertControllerStyle.alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "hey", message: "You have saved this Scrimmage before.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
You can check the record from your core data like that:
func checkRecordExists(entity: String,uniqueIdentity: String,idAttributeName:String) -> Bool {
let context = getManagedObjectContext()
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: entity)
fetchRequest.predicate = NSPredicate(format: "\(idAttributeName) CONTAINS[cd] %#", createdDate)
var results: [NSManagedObject] = []
do {
results = try context.fetch(fetchRequest)
}
catch {
print("error executing fetch request: \(error)")
}
return results.count > 0
}
and managedObjectContext is:
func getManagedObjectContext() -> NSManagedObjectContext{
let delegate = UIApplication.shared.delegate as? AppDelegate
return delegate!.persistentContainer.viewContext
}
If you get false then save it.

Controller initialized before app even goes to it

So the problem seems to be with my profileViewController. I set the current user after I am done signing up in this snippet of code.
class func setCurrent(_ user: User, writeToUserDefaults: Bool = true) {
print(user)
print("")
if writeToUserDefaults {
let data = NSKeyedArchiver.archivedData(withRootObject: user)
UserDefaults.standard.set(data, forKey: "currentUser")
UserDefaults.standard.synchronize()
}
_current = user
print(_current)
}
Then After That it goes to my profileViewController and tries to look for the user which turns out to be null. Why would it go there before I even went to the viewcontroller. Why is it nil?
Below is my profile view controller and my viewDidLoad
class ProfileeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var profileHandle: DatabaseHandle = 0
var profileRef: DatabaseReference?
let cellID = "cellID"
let profileSetupTransition = AlterProfileViewController()
let settingView = SettingsViewController()
var userEvents = [Event]()
var userId: String?
var user: User?
var emptyLabel: UILabel?
var currentUserName: String = ""
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.white
let user = self.user ?? User.current
profileHandle = UserService.observeProfile(for: user) { [unowned self](ref, user, events) in
self.profileRef = ref
self.user = user
self.userEvents = events
// self.jobs = allJobs
// self.reciepts = allReciepts
// print(self.userEvents)
// print(self.reciepts)
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
The method where I call setCurrent is below
// will handle the sign up of a user
#objc func handleSignUp(){
// first we cant to take sure that all of the fields are filled
var profilePic: String = ""
// will take the user selected image and load it to firebase
let imageName = NSUUID().uuidString
guard let username = self.nameTextField.text,
let confirmPassword = self.confirmPasswordTextField.text,
let email = self.emailTextField.text,
let password = self.passwordTextField.text,
!username.isEmpty,
!email.isEmpty,
!password.isEmpty,
!confirmPassword.isEmpty
else {
print("Required fields are not all filled!")
return
}
if self.validateEmail(enteredEmail:email) != true{
let alertController = UIAlertController(title: "Error", message: "Please Enter A Valid Email", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
// will make sure user is validated before it even tries to create user
// will make sure the password and confirm password textfields have the same value if so it will print an error
if self.passwordTextField.text != self.confirmPasswordTextField.text {
let alertController = UIAlertController(title: "Error", message: "Passwords Don't Match", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
//create a reference to the sotrage database in firebase
let storageRef = Storage.storage().reference().child("profile_images").child("\(imageName).PNG")
//following function does the work of putting it into firebase
//notice I also set the value of profilepic oo it can be saved in the updated user instance in the database
if let userImage = selectedImageFromPicker,let uploadData = UIImageJPEGRepresentation(userImage, 0.1){
AuthService.createUser(controller: self, email: email, password: password) { (authUser) in
guard let firUser = authUser else{
return
}
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error ?? "")
return
}
profilePic = (metadata?.downloadURL()!.absoluteString)!
//printing to make sure values are contained in these strings
print(profilePic)
print(username)
UserService.create(firUser, username: username, profilePic: profilePic, location: self.userLocation!, completion: { (user) in
guard let user = user else {
print("User not loaded into firebase db")
return
}
User.setCurrent(user, writeToUserDefaults: true)
// will set the current user for userdefaults to work
print(user.profilePic ?? "")
print(user.username ?? "")
// self.delegate?.finishSigningUp()
self.finishSigningUp()
})
})
}
}
}
Then this method is called to move the user to the homeviewController and set it as root
func finishSigningUp() {
print("Finish signing up from signup view controller")
print("Attempting to return to root view controller")
let homeController = HomeViewController()
//should change the root view controller to the homecontroller when done signing up
self.view.window?.rootViewController = homeController
self.view.window?.makeKeyAndVisible()
}
When you do User.current is going to retrieve on the user default the current user that you previously saved on the user defaults on your setCurrent method right ?
Did you check if the user is correctly saved on the User defaults with the key "currentUser" ? Maybe that's why it's nil.
Why would it go there before I even went to the viewcontroller ? Maybe you are doing some kinf of asynchronous method computation and even when it is not finished, you move to the vie controller. You move to it without the result of your asynchronous method and it's logic that your values are nil because you don't let them to be set. That's an hypothesis as I see your piece of code and you call some asynchronous methods on your code flow.

How to use a UIAlertController in MVVM?

I have a VC with code to show an alert:
func showMessage() {
let alertView = UIAlertController(title: "TEST",
message: self.loginViewModel.errorText,
preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil))
present(alertView, animated: true, completion: nil)
}
and I have this login logic in my viewModel which needs to trigger this function:
func submitLoginRequest(userLogin: String, loginPassword: String, loginSecret: String, deviceToken: String) {
let userLogin = UserServices.init()
manager.userServicesApiRequest(url: Endpoints.login, request: userLogin) { (data, error) in
if let data = data {
let status = data["status"].stringValue
if status == "success" {
guard let userObject = UserProfileModel.init(data) else { return }
let encodedUserObject: Data = NSKeyedArchiver.archivedData(withRootObject: userObject)
UserDefaults.standard.set(encodedUserObject, forKey: "userProfile")
print("Login Succeeded") self.coordinatorDelegate?.loginViewModelDidLogin(viewModel: self)
} else {
self.errorText = data["reason"].stringValue
// here is where the controller needs calling!
}
}
I wanted to know how i should have them interact correctly to trigger the VC when the VM case is hit?

If let statement error

Im trying to build a todo list with coreData, my goal is to have an error pop-up notification if a user tries to just click the "add" button without entering any text in the text-field.
I currently have the pop-up notification working but once i dismiss the notification and then add text into the text-field and click the "add" button the application crashes.
#IBAction func addBtnTaskPressed(_ sender: Any) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context)
//have to add an if let here.
if let text = textField.text, !text.isEmpty{
task.name = textField.text
//save data to coredata
(UIApplication.shared.delegate as! AppDelegate).saveContext()
} else {
let alert = UIAlertController(title: "Error:", message: "Cannot Add Empty Task", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
I think you are not saving coreData properly. Try this (I haven't tested it though)
#IBAction func addBtnTaskPressed(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "Task", in: context)
let task = NSManagedObject(entity: entity!, insertInto: context) as! Task
//have to add an if let here.
if let text = textField.text, !text.isEmpty{
task.name = textField.text
//save data to coredata
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
} else {
let alert = UIAlertController(title: "Error:", message: "Cannot Add Empty Task", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}

UIAlertView actions not happening swift

I have a login page in my app. The user enters their username and their password. I have an API that tells me if the username and password are correct and the user's id if they are. If they are not correct it shows a UIAlertView() that asks if you would like to create an account. The view has two buttons. A "No" button which dismisses the view and a "Yes" button which is supposed to contact an API to create the user's account. I have created alert actions before but it will not work with the code I have below. If you wouldn't mind could you please take a look and help me diagnose the problem?
//
// File.swift
// Reading Logs
//
// Created by Andrew on 12/8/15.
// Copyright © 2015 Wilson Apps for Education. All rights reserved.
//
import UIKit
class UserLogin {
var loginAlert = UIAlertView()
var user: String = ""
var pass: String = ""
func checkLogin() -> Bool{
let defaults = NSUserDefaults.standardUserDefaults()
let stat = defaults.valueForKey("loggedIn")
if(String(stat!) == "0"){
return false
}else{
return true
}
}
func logout(){
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("0", forKey: "loggedIn")
defaults.setValue("", forKey: "logKey")
defaults.setValue("0", forKey: "userKey")
}
func login(username username: String, password: String, completion: (result: String) -> Void){
self.user = username
self.pass = password
let url = "http://www.wilsonfamily5.org/rlog/checkLogin.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if(contents as String == "0"){
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:nil, cancelButtonTitle: "No")
self.loginAlert.addButtonWithTitle("Yes")
self.loginAlert.show()
}else{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
completion(result: "1")
}
})
}.resume()
}
func register(username: String, password: String){
let url = "http://www.wilsonfamily5.org/rlog/newAccount.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
})
}.resume()
}
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
print("ButtonClicked")
if(buttonIndex == 1){
print("1ButtonClicked")
register(user, password: pass)
}
}
}
Step-1
Add UIAlertViewDelegate to your class;
class UserLogin, UIAlertViewDelegate {
....
Step-2
Set delegate and implement "Yes" button loginAlert object;
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate: self, cancelButtonTitle: "No", otherButtonTitles: "Yes")
self.loginAlert.show()
Now clickedButtonAtIndex function will be triggered.
You should use UIAlertViewController instead of UIAlertView because
UIAlertView is deprecated in iOS 9
Here is a code of UIAlertController in Swift and its pretty simple to use.The main thing is that it's Block based and No need to use any delegate
let alertController = UIAlertController(title: "Default AlertController", message: "A standard alert", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action:UIAlertAction!) in
println("you have pressed the Cancel button");
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction!) in
println("you have pressed OK button");
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
You need to set Delegate, so you can receive alerts callbacks:
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:self, cancelButtonTitle: "No"

Resources