My code has a weird bug I'm having trouble solving.
I've built an app using swift and parse. You log in, and are taken to the main page thats a table view. Now at this point if you are logged in, and leave and comeback to the app, everything is fine and dandy.
However, when I log out, the user is then taken back to the log in screen. Now if the user leaves the app, but comes back, the app crashes giving me the error unexpectedly found nil while unwrapping an optional value.
It makes no sense to me what could be happening. When the app launches fresh from scratch the user is set to nil, then the user is logged in and everything is cool. When you log out, the user is then set back to nil and taken to the log in screen. If you resume the app, crash.
Is this an issue with how I'm logging out the user?
The relevant code is posted below..
On the login page:
#IBAction func loginButton(sender: AnyObject) {
//checks if there is a matching username with a matching password. if so, lets the user log in.
PFUser.logInWithUsernameInBackground(username.text, password: userPassword.text) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// Do stuff after successful login.
//go to main table view
//get current user
//display current users locations
println("login success")
//shows home screen after successful login.
self.performSegueWithIdentifier("showHomeFromLogin", sender: self)
} else {
// The login failed. Check error to see why.
//display error?
self.displayAlert("Login Failed", alertMessage: "Double check to make sure the username and password are correct.")
println("login failed")
}
}
}
var activeField: UITextField?
override func viewDidLoad() {
super.viewDidLoad()
username.delegate = self
userPassword.delegate = self
registerForKeyboardNotifications()
// Do any additional setup after loading the view.
//setup so when we tap outside the edit, we close keyboard.
var tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "DismissKeyboard")
view.addGestureRecognizer(tap)
}
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("showHomeFromLogin", sender: self)
}
}
On the home page where the user logs out:
#IBAction func logoutButton(sender: AnyObject) {
PFUser.logOut()
var currentUser = PFUser.currentUser() // this will now be nil
self.performSegueWithIdentifier("logoutSegue", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "launchSync", name: UIApplicationDidBecomeActiveNotification, object: nil)
//setting table view datasource and delegate.
self.tableView.dataSource = self
self.tableView.delegate = self
var currentUser = PFUser.currentUser()
println(currentUser)
}
Breaks in here I think on the line query.whereKey("User", equalTo:PFUser.currentUser()!):
func launchSync() {
var query = PFQuery(className:"ParseLighthouse")
query.whereKey("User", equalTo:PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
}
}
println(objects?.count)
self.syncToLighthouse(objects!)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
This launchsync function is called in the view did appear method on my home screen. Is it possible that when the logout segue is performed, the main view controllers is still running in the background so when I resume, that code cant find the user now that its set back to nil?
On the following line
NSNotificationCenter.defaultCenter().addObserver(self, selector: "launchSync", name: UIApplicationDidBecomeActiveNotification, object: nil)
you say to call the launchSync function whenever the app becomes active.
As mentioned by Paulw11, inside that function you are force unwrapping (using !) on PFUser.currentUser() which will return nil when the user is logged out.
You can address this by ensuring that the current user isn't nil
func launchSync() {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"ParseLighthouse")
query.whereKey("User", equalTo:PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
}
}
println(objects?.count)
self.syncToLighthouse(objects!)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
If there is the potential for a conditional value to be nil then you need to check the conditional before you use it. When there is no currently logged in user, PFUser.currentUser() will return nil. When you force unwrap this value with ! you get an exception.
You can change your code to conditionally unwrap the value -
func launchSync() {
if let currentUser=PFUser.currentUser() {
var query = PFQuery(className:"ParseLighthouse")
query.whereKey("User", equalTo:currentUser)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) lighthouses.")
// Do something with the found objects
if let light = objects as? [PFObject] {
for object in light {
println(object.objectId)
}
}
println(objects?.count)
self.syncToLighthouse(objects!)
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
Related
iOS rookie here struggling through the early stages of an app that was initially led by a mentor. Issue at the moment occurs in the login view controller:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let myRootRef = Firebase(url:"https://xxxx.firebaseio.com")
if myRootRef.authData.uid != nil {
self.performSegueWithIdentifier("loginToGreet", sender: nil)
}
Error message beside the second to last line reads "Thread 1: EXC_BAD_INSTRUCTION and below reads "fatal error: unexpectedly found nil while unwrapping an Optional value(lldb)"
This was working when we initially put the project on the shelf. The next chunk of code, if it matters:
#IBAction func loginPress(sender: UIButton) {
let myRootRef = Firebase(url:"https://xxx.firebaseio.com")
myRootRef.authUser(self.patientLoginName.text, password:self.patientLoginPassword.text,
withCompletionBlock: { error, authData in
if error != nil {
// There was an error logging in to this account
print(error)
} else {
// We are now logged in
print(authData)
self.performSegueWithIdentifier("loginToGreet", sender: nil)
}
})
Try
if myRootRef.authData != nil {
self.performSegueWithIdentifier("loginToGreet", sender: nil)
}
authData might be nil and you are checking for authData.uid. Since authData is nil it will crash at the uid access so change your error checking like above.
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.
I have a custom button that I use for Facebook login, and it was working fine until recently. The access token was cached and the next time the user launched the app, the continue button was displayed in its place.
Recently however the marked line returns nil regardless of whether the user has previously logged in. I'm at a loss as to why - I haven't made any code changes in this part of the app?
Occasionally the login will fail with the following error also:
Error Domain=com.facebook.sdk.login Code=308 "(null)"
Here's my code:
override func viewDidLoad() {
super.viewDidLoad()
if (FBSDKAccessToken.currentAccessToken() == nil){ // <<<< ALWAYS RETURNS NIL
self.continueButton.hidden = true
} else {
self.loginButton.hidden = true
self.notYouButton.hidden = false
}
}
#IBAction func loginPressed(sender: AnyObject) {
let permissions = ["user_about_me","user_relationships","user_birthday","user_location","user_status","user_posts", "user_photos"]
let login = FBSDKLoginManager()
login.logInWithReadPermissions(permissions, handler: {
(FBSDKLoginManagerLoginResult result, NSError error) -> Void in
if(error == nil){
self.loginButton.hidden = true
self.continueButton.hidden = false
self.notYouButton.hidden = false
self.notYouButton.enabled = false
//self.performSelector("showBrowse", withObject: nil, afterDelay: 1.0)
} else {
print(error)
}
})
}
EDIT: On further testing it seems that calling FBSDKAccessToken.currentAccessToken() is returning nil if called in viewDidLoad(), but if I call it from a button press it returns the Facebook token as expected.
override func viewDidLoad() {
super.viewDidLoad()
if let token = FBSDKAccessToken.currentAccessToken() {
print (token)
} else {
print ("no token") <<<<< RETURNS
}
}
#IBAction func buttonPressed(sender: AnyObject) {
if let token = FBSDKAccessToken.currentAccessToken() {
print (token) <<<<< RETURNS
} else {
print ("no token")
}
}
It turns out that there was a problem in my appDelegate where I was setting up a custom View Controller. I reverted the code to use storyboards and the issue was resolved - not a resolution per se for anyone with similar issues but it's enough for me to get on.
I've been getting an EXC_BAD_ACCESS error/crash when I engage in a specific action within my application. Figuring that this was a memory management issue, I enabled NSZombies to help me decipher the issue. Upon the crash, my console gave me the following message:
heres my stack trace:
and the new error highlighting my app delegate line:
Now being the debugger is referring to a UIActivityIndicatorRelease, and the only line of code highlighted in my stack trace is the 1st line in my delegate, is there an issue with my Activity Indicator UI Element? Here is the logic within my login action ( which forces the crash every time ):
#IBAction func Login(sender: AnyObject) {
activityIND.hidden = false
activityIND.startAnimating()
var userName = usernameText.text
var passWord = passwordText.text
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
} else {
self.activityIND.stopAnimating()
if let message: AnyObject = error!.userInfo!["error"] {
self.message.text = "\(message)"
}
}
}
}
is there an error within it?
All your code that manipulates UI objects absolutely, positively must be done from the main thread. (and so it should be in a call to dispatch_async(dispatch_get_main_queue()) as #JAL says in his comment.
That includes not just the self.activityIND.stopAnimating() line, but the code that sets label text as well (any code that manipulates a UIKit object like a UIView).
Your if...else clause should look something like this:
if user != nil
{
dispatch_async(dispatch_get_main_queue())
{
self.activityIND.stopAnimating()
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
else
{
dispatch_async(dispatch_get_main_queue())
{
self.activityIND.stopAnimating()
if let message: AnyObject = error!.userInfo!["error"]
{
self.message.text = "\(message)"
}
}
}
So it turns out, in my viewDidLoad() I had the following code in attempt to hide the indicator on the load:
UIActivityIndicator.appearance().hidden = true
UIActivityIndicatorView.appearance().hidesWhenStopped = true
not knowing this would deallocate the indicator for the remainder of the application so when i called the following in my login logic:
activityIND.hidden = false
activityIND.startAnimating()
i was sending a message to an instance that was no longer available, causing the crashes. So all i did was adjust my code in viewDidLoad()
to :
activityIND.hidden = true
activityIND.hidesWhenStopped = true
using the name of the specific outlet I created rather than the general UIActivityIndicatorView
All UI related operation should execute in Main Thread, i.e., within
dispatch_async(dispatch_get_main_queue(){}
block
#IBAction func Login(sender: AnyObject) {
activityIND.hidden = false
activityIND.startAnimating()
var userName = usernameText.text
var passWord = passwordText.text
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self) //UI task
}
}
else {
dispatch_async(dispatch_get_main_queue())
{
self.activityIND.stopAnimating() //UI task
if let message: AnyObject = error!.userInfo!["error"]
{
self.message.text = "\(message)" //UI task
}
};
}
}
}
Refer some good articles on Concurrency here and here
I am making a to do app using parse.com. I have got data which is set by a user and they should be able to assign that data to another user too. So the user sets a title, text and the name of the user they wish to assign it to. All of this is stored in the database and works well. When the same user logs in the data they set is displayed for him in the table view controller.
However the user they assigned to see the data does not display. So let me explain with code. The code underneath allows the user to see the data they set when they go to Table view screen. The code under is in the viewDidAppear function.
func fetchAllObjectsFromLocalDatastore() {
var query: PFQuery = PFQuery(className: "toDo")
query.fromLocalDatastore()
query.whereKey("username", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
var temp: NSArray = objects as NSArray
self.toDoObjects = temp.mutableCopy() as NSMutableArray
self.tableView.reloadData()
}else {
println(error.userInfo)
}
}
With this, I have another function:
func fetchAllObjects() {
PFObject.unpinAllObjectsInBackgroundWithBlock(nil)
var query: PFQuery = PFQuery(className: "toDo")
query.whereKey("username", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil)
self.fetchAllObjectsFromLocalDatastore()
}else {
println(error.userInfo)
}
}
}
The user he wants to see that data to is assigned in the add new to do screen and that works. It even displays on the Parse.com database. But how can I query it or get it to show that data for the user who the data has been assigned to not just the user who set it.
So when (for example) UserOne sets data, it appears for him when he logs in and goes to table view, but even when he assigns to (for example) UserTwo and UserTwo logs in and goes to table view where the data is meant to be, I just do not know HOW TO DO??!
Please do help me or give me guidance, I am still searching on the solution to this, I feel like it is really simple but I cannot put my finger on it.
UPDATE *2
When the data is set, it is set with who the UserOne would like to give access to the data to.
So here is the saving process I guess:
#IBAction func saveAction(sender: UIBarButtonItem) {
self.object["username"] = PFUser.currentUser().username
self.object["title"] = self.titleField?.text
self.object["text"] = self.textView?.text
self.object["forUser"] = self.userToAssignTo?.text
self.object.saveEventually { (success, error) -> Void in
if (error == nil) {
}else{
println(error.userInfo)
}
}
So then I tried to query the forUser:
func fetchAllObjects() {
PFObject.unpinAllObjectsInBackgroundWithBlock(nil)
var query: PFQuery = PFQuery(className: "toDo")
query.whereKey("forUser", equalTo: PFUser.currentUser().username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if (error == nil) {
PFObject.pinAllInBackground(objects, block: nil)
self.fetchAllObjectsFromLocalDatastore()
}else {
println(error.userInfo)
}
}
}
This still did not seem to work. I'm not sure what I'm doing now! Damn I just don't know how I can let another user see the data that one user has set.
UPDATE 3**
I found it! It was all about the viewDidAppear function, so here is the code:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser() == nil) {
var logInViewController = PFLogInViewController()
logInViewController.delegate = self
var signUpViewController = PFSignUpViewController()
signUpViewController.delegate = self
logInViewController.signUpController = signUpViewController
self.presentViewController(logInViewController, animated: true, completion: nil)
}else {
//self.fetchAllObjectsFromLocalDatastore()
//self.fetchAllObjects()
self.fetchAllObjectsFromLocalDatastore2()
self.fetchAllObjects2()
}
}
It is basically because the fetchAllObjects was kind of wiping it out for the "forUser" key. But now I need to figure out how to do it so the fetchAllObjects don't wipe each other off because it almost like a refresh button and then all the data is wiped off the screen.
I have been working on a similar issue however with push notifications from parse. I can provide code examples later today. There are a couple ways to approach. When the user creates a new to do you can set a custom object with the user they want to assign to and you can query based on the field. You can save the to do with saveinbackground with block. Another way is to use PFRelation. Watched a few courses on team tree house to review and see different techniques and they have one that is a self destructing messaging app that uses parse for functions much similar to what you want to do.