How to condition segues using CloudKit query? - ios

I created a login page backed up by CloudKit.
I want to know how to create a conditioned segue if the login has a certain value, and then direct the user to a View Controller based on that value
In details, I have three Segues I want to connect:
sign in id segue:LoginSuccessSegue
staff tab bar id segue:idStaffTabBar
student tab bar id segue:idStudentTabBar
First Segue
LoginSuccessSegue:
the sign in view controller has a show segue id LoginSuccessSegue the connects to staff tab bar controller and student tab bar controller.
Second Segue
idStaffTabBar:
After queryProfileType() is executed, it will look for the profile type in a list to check if the user profile is a teacher value or something else. Then if that is true "LoginSuccessSegue" will automatically take the user to staff tab bar controller, "StaffBookedVC.self" by usingits segueidStaffTabBar `
Third Segue
idStudentTabBar:
if the user is not a teacher then after pressing the sign in button redirect to student tab bar controller, "stdBookingVC.self" by using idStudentTabBar or
How can I achieve an automatic conditional sign in in multiple views suing segues and cloud kit query?
This is the code for the sign-in button:
#IBAction func btnSignInTapped(sender: UIButton)
{
let userEmailAddress = userEmailAddressTextField.text
let userPassword = userPasswordTextField.text
if(userEmailAddress!.isEmpty || userPassword!.isEmpty)
{
notifyUser("Empty fields", message: "all fields are required")
}
print("fetching is in progress")
queryProfileType()
print("\nfetching had stopped")
}//end of signInbtn
func queryProfileType()
{
queryCredentials()
print("\nProfile Query starting")
//execute query
let organizers = ["Teacher || Youtuber || Instagrammer || "]
let predicate = NSPredicate(format: "ProfileType = '\(organizers)' ")
print("\nThis is the predicate\n\(predicate)")
let query = CKQuery(recordType: "RegisteredAccounts", predicate: predicate)
publicDatabase!.performQuery(query, inZoneWithID: nil) { results, error in
if (error != nil)
{
print(error)
}else
{
if (results! == organizers)
{
self.performSegueWithIdentifier("idStaffTabBar", sender: StaffBookedVC.self)
}else{
self.performSegueWithIdentifier("idStudentTabBar", sender: stdBookingVC.self)
}
print("\(results)\nthese are the printed results")
}
}
let firstFetch = CKFetchRecordsOperation()
let secondFetch = CKFetchRecordsOperation()
secondFetch.addDependency(firstFetch)
let queue = NSOperationQueue()
queue.addOperations([firstFetch, secondFetch], waitUntilFinished: false)
}
here is a picture of the storyboard segues Storyboard
if your answer will contain these methods, please show me some examples:
shouldPerformSegueWithIdentifier and prepareForSegue
this did not work from me either
self.presentViewController(SignInNavigationVCTabBars, animated: true,
{ results, error in
if (results! == organizers)
{
self.performSegueWithIdentifier("idStaffTabBar", sender: StaffUITABbarVC.self)
}else{
self.performSegueWithIdentifier("idStudentTabBar", sender: StdUITABbarVC.self)
}
}
`

You need to do something like presentModalViewController to show the navigationController first, within your queryProfileType method. And then do some smart logic to determine which route to go, after the navigationController is loaded. So a custom UINavigationController is needed.
Or an easier way:
Move your loginViewController into the navigation controller stack, and then link the two existing segues, i.e. idStaffTabBar and idStudentTabBar to it. That will solve the problem.

Here is the answer
I did not expect valueforkey would have it all
what can I see? never stop trying
//log in function
func queryCredentials()
{
print("*********\nQueryCredentials starting")
// System indicator
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.labelText = "Signing in"
spinningActivity.detailsLabelText = "Please wait"
// querying predicate in cloud kit to check via email and password property
let predicate = NSPredicate(format: "Email = %#", userEmailAddressTextField.text!)
let query = CKQuery(recordType: "RegisteredAccounts", predicate: predicate)
publicDatabase?.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
if (error != nil)
{
dispatch_async(dispatch_get_main_queue())
{
// if the user is not signed, display this error
self.notifyUser("Cloud Access Error",
message: "to fix this error Sign in you icloud \n go to settings\nthen sign in icloud account\n error code:\(error!.localizedDescription)")
}
}else
{
if (results!.count > 0)
{
// the results after success case of the query
let record = results![0] as! CKRecord
// read from the result to navigate via profiletype attribute
let proftype = record.valueForKey("ProfileType") as! String
switch proftype
{
case("Teacher"):
self.staffView()
break;
case("Manager"):
self.staffView()
break;
case("Student"):
// stdView() is a student coded segue as a function to navigate to student view
self.stdView()
break;
case("Worker"):
self.stdView()
break;
default:
break;
}
self.currentRecord = record
dispatch_async(dispatch_get_main_queue())
{
// if credentials are correct, display you are logged in
self.userPasswordTextField!.text =
record.objectForKey("Password") as! String
self.notifyUser("Welcome", message: "You are loogedin")
}
}else
{
dispatch_async(dispatch_get_main_queue())
{
self.notifyUser("No Match Found",
message: "No record matching")
}
}
}
}))
// hiding indicator
spinningActivity.hide(true)
}

Related

sign up flow segues to the wrong view controller and doesn't write to firestore either

So my goal is to have the correct user sign up and be shown the correct segue as well as the user info be written to Firestore. So I have a basic sign up function that gets triggered when the sign up button is pressed:
#IBAction func schoolSignupPressed(_ sender: UIButton) {
let validationError = validateFields()
let schoolName = schoolNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolEmail = schoolEmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolPassword = schoolPasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolID = schoolIDTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let schoolDistrict = schoolDistrictTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let dateCreated = Date()
if validationError != nil {
return
}
Auth.auth().createUser(withEmail: schoolEmail, password: schoolPassword) { (result, error) in
guard let signUpError = error?.localizedDescription else { return }
guard error == nil else {
self.showAlert(title: "Error Signing Up", message: "There was an error creating the user. \(signUpError)")
return
}
let db = Firestore.firestore()
guard let result = result else { return }
db.document("school_users/\(result.user.uid)").setData(["school_name":schoolName,
"school_id":schoolID,
"emailAddress": result.user.email ?? schoolEmail,
"remindersPushNotificationsOn": true,
"updatesPushNotificationsOn": true,
"schoolDistrict":schoolDistrict,
"time_created":dateCreated,
"userID": result.user.uid],
merge: true) { (error) in
guard let databaseError = error?.localizedDescription else { return }
guard error == nil else {
self.showAlert(title: "Error Adding User Info", message: "There was an error adding the user info. \(databaseError)")
return
}
}
let changeRequest = result.user.createProfileChangeRequest()
changeRequest.displayName = schoolName
changeRequest.commitChanges { (error) in
guard error == nil else {
return
}
print("School Name Saved!")
}
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
self.performSegue(withIdentifier: Constants.Segues.fromSchoolSignUpToSchoolDashboard, sender: self)
}
}
}
This is the sign up function for the 'school' user, but the 'student' user is essentially the same thing just different fields and of course a different segue destination. Now maybe like a day ago or 2, I was testing this function out and it was working completely fine the user was succesfully signed up, the user info was written to firestore, the correct view controller was displayed, the only difference was I had some DispatchGroup blocks within the function because when i was running the method in TestFlight, there would be a couple of bugs that would crash the app.
So I figured since everything was working fine in the simulator, I archive the build, upload it to TestFlight and wait for it to be approved. It got approved last night and I ended up testing it out on my phone this morning to see it again, now when I try to sign up as either a school user or a student user, it segues to the wrong view controller every time and no info gets written to firestore, the user just gets saved in Firebase Auth and that is not the outcome I expect in my app.
I've checked the segue identifiers, I've checked the connections tab, and even though it was working amazing 24 hours ago, I still checked it all. I'm trying my best to really appreciate what Apple does for developers but I'm really starting to grow a hatred towards TestFlight, everything I do and run in the simulator works fantastic on Xcode, as soon as I run it in TestFlight, everything just goes out the window. I hate these types of bugs because you genuinely don't know where the issue is stemming from simply because you've used, if not very similar, the exact same method in every other previous situation.
The login process works fine on both student and school user, I'll show an example of the school user login method:
#IBAction func loginPressed(_ sender: UIButton) {
let validationError = validateFields()
let email = schoolEmailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = schoolPasswordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
if validationError != nil {
return
} else {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
guard let signInError = error?.localizedDescription else { return }
let group = DispatchGroup()
group.enter()
guard error == nil else {
self.showAlert(title: "Error Signing In", message: "There was an issue trying to sign the user in. \(signInError)")
return
}
group.leave()
group.notify(queue: .main) {
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
self.performSegue(withIdentifier: Constants.Segues.fromSchoolLoginToSchoolEvents, sender: self)
}
}
}
}
}
Pretty much the same for student users. If anyone can point out possible issues for this bug in the first code snippet that would be amazing. Thanks in advance.
Although it is helpful, removing the error.localizedDescription line brought everything back to normal.

IOS app in swift 3 not navigating to further viewcontrollers

I am doing project on secured authentication....when i try to register my email id and password it is not going to next viewcontroller...when i run my code it is working well and not showing any errors but i am not getting output...here is my code
#IBAction func signInButtonTapped(_ sender: UIButton) {
// TODO: Do some form validation on the email and password
if let email = emailTextField.text, let pass = passwordTextField.text {
// Check if it's sign in or register
if isSignIn {
// Sign in the user with Firebase
FIRAuth.auth()?.signIn(withEmail: email, password: pass, completion: { (user, error) in
// Check that user isn't nil
if let u = user {
// User is found, go to home screen
self.performSegue(withIdentifier: "goToHome", sender: self)
}
else {
// Error: check error and show message
self.displayAlertMessage(messageToDisplay: "Password didn't match");
}
})
}
else {
// Register the user with Firebase
FIRAuth.auth()?.createUser(withEmail: email, password: pass, completion: { (user, error) in
// Check that user isn't nil
if let u = user {
// User is found, go to home screen
self.performSegue(withIdentifier: "goToEnroll", sender: self)
}
else {
// Error: check error and show message
}
})
}
}
Please check the connection in Storyboard, make sure that the segue name of link between Current VC -> Home VC is: "goToHome"
Check the same for Enroll VC
Check here if the idenfier is same as you have given in your story board.
Note: Please check first that the identifier you have mentioned here is same as your storyboard one.

Why did an error occur when I save the data into CoreData?

I am building an app that has a "Playlist" feature. Users can create new, empty playlists and then add contents to them.
I decided to use Core Data to do this. So I did some research and created this object model:
where the Utterance entity represents an item in a playlist.
The view controller that I used to display the playlist is UITableViewController. Here is part of the class:
var playlists: [Playlists] = []
let dataContext: NSManagedObjectContext! = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext
override func viewDidLoad() {
if dataContext != nil {
let entity = NSEntityDescription.entityForName("Playlist", inManagedObjectContext: dataContext)
let request = NSFetchRequest()
request.entity = entity
let playlists = try? dataContext.executeFetchRequest(request)
if playlists != nil {
for item in playlists! {
self.playlists.append(item as! Playlists)
print((item as! Playlists).name)
}
}
}
}
Playlists is a NSManagedObject subclass generated by Xcode. In viewDidLoad, I get all the playlists and put them in self.playlists. Also please note that the tableView are implemented correctly.
Now I am writing the action method when the user taps on the add playlist button. I want to show an alert asking the user for the name of the playlist. If he/she doesn't enter anything, failAlert will be displayed. Otherwise, I create a new Playlist object and set its name to the textfield's text and save it in the database. Here's the code:
#IBAction func addPlaylist(sender: UIBarButtonItem) {
let alert = UIAlertController(title: "新播放列表", message: "请输入播放列表的名字", preferredStyle: .Alert)
alert.addTextFieldWithConfigurationHandler({ (textField) -> Void in
textField.placeholder = "名字"
})
alert.addAction(UIAlertAction(title: "确定", style: .Default, handler: { (action) -> Void in
if alert.textFields?.first?.text == "" || alert.textFields?.first?.text == nil {
let failAlert = UIAlertController(title: "失败", message: "播放列表名不能为空", preferredStyle: .Alert)
failAlert.addAction(UIAlertAction(title: "确定", style: .Default, handler: nil))
self.presentViewController(failAlert, animated: true, completion: nil)
return
}
let newPlaylist = Playlists(entity: NSEntityDescription.entityForName("Playlist", inManagedObjectContext: self.dataContext)!, insertIntoManagedObjectContext: self.dataContext)
newPlaylist.name = alert.textFields?.first?.text
if let _ = try? self.dataContext.save() {
print("Error occured")
}
self.tableView.reloadData()
}))
self.presentViewController(alert, animated: true, completion: nil)
}
As you can see, I wrote this:
newPlaylist.name = alert.textFields?.first?.text
if let _ = try? self.dataContext.save() {
print("Error occured")
}
self.tableView.reloadData()
So if an error occurred in saving process, it would print Error occurred. When I tested the app by clicking on the add playlist button, it asked me for the name of the playlist. So I entered some random letters like ggg and unfortunately it prints Error occured! Also, the table view remained empty.
I really didn't understand why and thought that the data is not saved. But when I ran the app again, I see ggg in the table view! This is so weird! An error occurred but it saved the data successfully! Why is this? What is the error?
Edit:
A lot of the answers says that save returns a Bool. But Xcode says it does not:
That's clearly the word Void!
NSManagedObject's save: method returns a boolean:
Return Value
YES if the save succeeds, otherwise NO.
Therefore, the if let statement is not the right way to go, as the method will always return something (a boolean) even if the save succeeds, causing the statements within the if to be run.
You should use Swift's error handling capabilities with a do-catch statement:
do {
try self.dataContext.save()
} catch let error as NSError {
print("Error: \(error)")
}
More about error handling in Swift 2.0
But this only fixes your problem with the save, not the fact that the new objects aren't appearing until you restart the app. To fix that, you need to look at the way you go about reloading data.
Right now, you are calling self.tableView.reloadData(), except the code you included shows that you are fetching the objects from the database in your viewDidLoad. Given that viewDidLoad is only called when the view is first loaded, the new object you added will not be included in the self.playlists array.
You should add the new playlist to self.playlists in the addPlaylist function, right after the save:
self.playlists.append(item as! Playlists)
According to Apple document, NSManagedObjectContext:save() returns true if the save succeeds, otherwise false.
Will you see error with the following code when saving the data:
do {
try self.dataContext.save()
} catch let error as NSError {
print("Error occurred")
}

How to handle touchID when loading app from background Swift

I'm implementing the login possibility with touchID using Swift.
Following: when the App is started, there is a login screen and a touchID popup - that's working fine. The problem occurs, when the app is loaded from background: I want the touchID popup appear over a login screen if a specific timespan hasn't been exceeded yet - but this time I want the touchID to go to the last shown view before the app entered background. (i.e. if the user wants to cancel the touchID, there is a login screen underneath where he then can authenticate via password, which leads him to the last shown view OR if the touchID authentication succeeded, the login screen should be dismissed and the last shown view presented.)
I really tried everything on my own, and searched for answers - nothing did help me. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
//notify when foreground or background have been entered -> in that case there are two methods that will be invoked: willEnterForeground and didEnterBackground
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "willEnterForeground", name:UIApplicationWillEnterForegroundNotification, object: nil)
notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
password.secureTextEntry = true
if (username != nil) {
username.text = "bucketFit"
}
username.delegate = self
password.delegate = self
if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown == nil){
authenticateWithTouchID()
}
}
}
willEnterForeground:
func willEnterForeground() {
//save locally that the guide already logged in once and the application is just entering foreground
//the variable alreadyShown is used for presenting the touchID, see viewDidAppear method
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
showLoginView()
} else {
def.removeObjectForKey("alreadyShown")
showLoginView()
}
}
}
authenticateWithTouchID():
func authenticateWithTouchID() {
let context : LAContext = LAContext()
context.localizedFallbackTitle = ""
var error : NSError?
let myLocalizedReasonString : NSString = "Authentication is required"
//check whether the iphone has the touchID possibility at all
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
//if yes then execute the touchID and see whether the finger print matches
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString as String, reply: { (success : Bool, evaluationError : NSError?) -> Void in
//touchID succeded -> go to students list page
if success {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.performSegueWithIdentifier("studentsList", sender: self)
})
} else {
// Authentification failed
print(evaluationError?.description)
//print out the specific error
switch evaluationError!.code {
case LAError.SystemCancel.rawValue:
print("Authentication cancelled by the system")
case LAError.UserCancel.rawValue:
print("Authentication cancelled by the user")
default:
print("Authentication failed")
}
}
})
}
}
shouldPerformSegueWithIdentifier:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if (false) { //TODO -> username.text!.isEmpty || password.text!.isEmpty
notify("Login failed", message: "Please enter your username and password to proceed")
return false
} else if (false) { //TODO when backend ready! -> !login("bucketFit", password: "test")
notify("Incorrect username or password", message: "Please try again")
return false
//if the login page is loaded after background, dont proceed (then we need to present the last presented view on the stack before the app leaved to background)
} else if let alreadyShown : AnyObject? = def.objectForKey("alreadyShown") {
if (alreadyShown != nil){
//TODO check whether login data is correct
dismissLoginView()
return false
}
}
return true
}
Thank you in advance.
What you could do is create a AuthenticationManager. This manager would be a shared instance which keep track of whether authentication needs to be renewed. You may also want this to contain all of the auth methods.
class AuthenticationManager {
static let sharedInstance = AuthenticationManager()
var needsAuthentication = false
}
In AppDelegate:
func willEnterForeground() {
def.setObject(true, forKey: "alreadyShown")
if let backgroundEntered : AnyObject? = def.objectForKey("backgroundEntered") {
let startTime = backgroundEntered as! NSDate
//number of seconds the app was in the background
let inactivityDuration = NSDate().timeIntervalSinceDate(startTime)
//if the app was longer than 3 minutes inactiv, ask the guide to input his password
if (inactivityDuration > 2) {
AuthenticationManager.sharedInstance.needsAuthentication = true
}
}
}
Then, subclass UIViewController with a view controller named SecureViewController. Override viewDidLoad() in this subclass
override fun viewDidLoad() {
super.viewDidLoad()
if (AuthenticationManager.sharedInstance().needsAuthentication) {
// call authentication methods
}
}
Now, make all your View Controllers that require authentication subclasses of SecureViewController.

App crashes after login or signup, but works after I swipe to quit and reopen

The title is kind of lengthy and may be a bit convoluted, but I'll try to break it down. My app is built with Swift and uses Parse as the backend. Upon a successful signup or login, the app opens to the main screen and everything is working. I go to profile, and all of the users signup information is there. I go to the camera, and can take a photo, but when I try to post the photo and send it to Parse, the app crashes. If I exit the app and swipe quit it, then reopen it with the cached user already logged in, everything works perfectly. I can take photos, post them, view them, and there are no issues. My problem only appears when I'm signing up for the first time or logging in. I would post code, but I have no idea what to post, if anyone can give me some assistance I'd appreciate it. Thanks!
EDIT
My Apologies for the ambiguity of my first question....I finally have an error message that states: 'NSInvalidArgumentException', reason: 'Can't use nil for keys or values on PFObject. Use NSNull for values.'....not sure what to make of it, but if someone could help I'd be very grateful.
Code
Here is the code of the method that I believe is causing the crash.
#IBAction func postPhoto(sender: AnyObject)
{
var fileData:NSData
var fileName:NSString
var fileType:NSString
if image != nil
{
fileData = UIImageJPEGRepresentation((image), 0.7)
fileName = "image.png"
fileType = "image"
}
else
{
fileData = NSData.dataWithContentsOfMappedFile(videoFilePath) as NSData
fileName = "video.mov"
fileType = "video"
}
let file = PFFile(name: fileName, data: fileData)
var content = PFObject(className: "Content")
content["sender"] = self.currentUser
content["senderObjectId"] = self.currentUser?.objectId
content["senderUsername"] = self.currentUser?["displayUsername"]
content["senderProfilePic"] = self.currentUser?["profilePic"]
content["file"] = file
content["recipients"] = self.photoRecipients
content["caption"] = self.photoCaption.text
content.saveInBackgroundWithBlock { (success:Bool!, error:NSError!) -> Void in
if success != nil
{
self.dismissViewControllerAnimated(false, completion: nil)
self.reset()
}
else
{
var alert:UIAlertController = UIAlertController(title: "Error", message: "There is a poor network connection, please try again", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Close", style: .Cancel, handler: nil))
}
}
}
Answer
The problem was in my viewDidLoad. I was checking for currentUser and then setting photoRecipients based off of that. There is no currentUser when the view controller is initially called, the login screen is shown. Because of that photoRecipients is never set, and when the main view controller shows up again, viewDidLoad has already been called and photoRecipients is still set to nil.
override func viewDidLoad()
{
super.viewDidLoad()
currentUser = PFUser.currentUser()
if currentUser != nil
{
var query = PFUser.query()
query.orderByAscending("username")
query.findObjectsInBackgroundWithBlock({ (NSArray objects, NSError error) -> Void in
if error == nil
{
for user in objects
{
self.contentRecipients.append(user.objectId)
}
}
})
currentUserObjectId = currentUser?.objectId
currentUserDisplayUsername = currentUser?["displayUsername"] as? NSString
}
else
{
self.performSegueWithIdentifier("showLogin", sender: self)
}
}
The issue with your code is that self.user, self.photoRecipients, and/or self.photoCaption.text is/are nil. You need to make sure none are nil and it should work.

Resources