I am getting a weird message in the console after I run this code:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let pickUp = sortedPickUps[indexPath.item]
let alert = UIAlertController(title: "Delete", message: "Permanently delete your order?", preferredStyle: .alert)
let yes = UIAlertAction(title: "Yes", style: .destructive) { (actio) in
DispatchQueue.main.async {
self.pickUpController.delete(pickUp: pickUp)
self.pickUpController.deleteFirebasePickUp(pickUp: pickUp, completion: { (error) in
if let error = error {
NSLog("Error deleting pick up \(error)")
}
})
self.sortedPickUps = self.pickUpController.pickUps.sorted(by: { $0.timestamp > $1.timestamp })
self.collectionView.reloadData()
}
self.sendNotification()
}
let no = UIAlertAction(title: "No", style: .default) { (action) in }
alert.addAction(yes)
alert.addAction(no)
present(alert, animated: true, completion: nil)
}
The URLSession in the deleteFirebasePickUp looks like this
URLSession.shared.dataTask(with: request) { (data, _, error) in
if let error = error {
NSLog("Error deleting entry from server: \(error)")
DispatchQueue.main.async {
completion(error)
}
return
}
DispatchQueue.main.async {
guard let index = self.pickUps.index(of: pickUp) else {
NSLog("Something happened to the entry")
completion(NSError())
return
}
self.pickUps.remove(at: index)
completion(nil)
}
}.resume()
The app doesn't crash, and in fact does what it's supposed to do - removes the pickUp from the device and from the database. The error is error = (Error?) domain: nil - code:0
The debugger says
Something happened to the entry 2018-10-13 14:14:37.608435-0700 chute[36293:4218837] -[NSError init] called; this results in an invalid NSError instance. It will raise an exception in a future release. Please call errorWithDomain:code:userInfo: or initWithDomain:code:userInfo:. This message shown only once.
Any ideas?
Reread the debugger's message:
[NSError init] called; this results in an invalid NSError instance.
If you reread your second code block
DispatchQueue.main.async {
guard let index = self.pickUps.index(of: pickUp) else {
NSLog("Something happened to the entry")
completion(NSError()) // THIS LINE
return
}
self.pickUps.remove(at: index)
completion(nil)
}
The NSError() constructor corresponds to [NSError init], which is deprecated. You should use the init(domain:code:userInfo:) initializer instead: https://developer.apple.com/documentation/foundation/nserror/1417063-init
Related
I am calling a method that executes a URLSession but before it does anything, presents a UIAlertController blocking the UI until some sort of response from the request is achieved. Logic tells me that dismissing the UIAlertController in the completion block of that method where it is called on the main thread would be the best option. Am I wrong to assume this? Apparently so, as variably the presented UIAlertController will indeed display, but never dismiss. Help?
Block:
getCostandIV { output in
let cost = output["ask"] as! NSNumber
let IV = output["IV"] as! NSNumber
self.enteredCost = cost.stringValue
self.enteredIV = IV.stringValue
DispatchQueue.main.async {
self.progress.dismiss(animated: true, completion: nil)
self.tableView.reloadSections(IndexSet(integer: 1), with: UITableView.RowAnimation.none)
self.canWeSave()
}
}
Function:
func getCostandIV (completionBlock: #escaping (NSMutableDictionary) -> Void) -> Void {
DispatchQueue.main.async {
self.progress = UIAlertController(title: "Retrieving ask price and volatility...", message: nil, preferredStyle: UIAlertController.Style.alert)
self.present(self.progress, animated: true, completion: nil)
}
guard let url = URL(string: "https://api.tdameritrade.com/v1/marketdata/chains?apikey=test&symbol=\(symbol)&contractType=\(type)&strike=\(selectedStrike)&fromDate=\(selectedExpiry)&toDate=\(selectedExpiry)") else {
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
//print(error?.localizedDescription ?? "Response Error")
DispatchQueue.main.async {
self.presentedViewController?.dismiss(animated: true, completion: {
let alert = UIAlertController(title: "There was an error retrieving ask price and volatility.", message: "Please try again later.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
})
}
return }
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with:
dataResponse, options: [])
// //print(jsonResponse) //Response result
guard let jsonDict = jsonResponse as? NSDictionary else {
return
}
// //print(jsonDict)
var strikeMap : NSDictionary = [:]
if self.type == "CALL" {
strikeMap = jsonDict["callExpDateMap"] as! NSDictionary
} else {
strikeMap = jsonDict["putExpDateMap"] as! NSDictionary
}
self.strikes.removeAllObjects()
let inner = strikeMap.object(forKey: strikeMap.allKeys.first ?? "<#default value#>") as! NSDictionary
let innerAgain = inner.object(forKey: inner.allKeys.first ?? "<#default value#>") as! NSArray
let dict : NSDictionary = innerAgain[0] as! NSDictionary
let dict2 = ["ask" : dict["ask"] as! NSNumber, "IV" : dict["volatility"] as! NSNumber] as NSMutableDictionary
completionBlock(dict2)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
Edit: Using self.presentedViewController?.dismiss(animated: true, completion: nil) did not fix the issue. In addition, the completion block of the dismiss function for self.progress is not being called.
Edit 2: presentedViewController right before dismiss code in the callback is nil, even though present is called on the alert controller before dismiss?
use this , for dismiss your alert you should add dismiss method in an async block and for setting timer for that you should tell async block to start being async from now to 5 seconds and after that do some thing :
alert.addAction(UIAlertAction(title: "ok", style: .default,
handler: nil))
viewController.present(alert, animated: true, completion: nil)
// change to desired number of seconds (in this case 5 seconds)
let when = DispatchTime.now() + 5
DispatchQueue.main.asyncAfter(deadline: when){
// your code with delay
alert.dismiss(animated: true, completion: nil)
}
If you call the getCostandIV method more than once, the second alert won't be presented and self.progress will have the reference of unpresented alert.
Change
self.progress.dismiss(animated: true, completion: nil)
To
self.presentedViewController?.dismiss(animated: true, completion: nil)
Your alert would be dismissed only if everything goes well.
I suggest you to change your function to something like this:
func getCostandIV (completionBlock: #escaping (NSMutableDictionary?, Error?) -> Void) -> Void
and make sure that your completionBlock is called when your guard statements fail or an error is thrown. In your current code the alert is only dismissed when it network request fails, but not when something goes wrong when parsing JSON.
I a implementing face ID in my iOS app. When authentication fails a alert shows containing cancel and try again buttons, cancel button works but try again button not working. How can I edit this button or to be worked try again button. When i Click on try again button nothing happens if i click on cancel button that is working and even title of cancel button also be changing but no action is applying on try again.I have tried the following code and had a lot of search on internet but nothing found.
enter image description here
func startFaceIDTest(){
attempted = true
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Identify yourself!"
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason, reply: {
successfaceID, authenticationError in
DispatchQueue.main.async {
if successfaceID {
self.success = true
} else {
self.success = false
guard let errorr = authenticationError else {return}
switch(errorr) {
case LAError.authenticationFailed:
print("Failed")
break
case LAError.userCancel:
print("User cancel")
break
case LAError.userFallback:
print("Fallback")
break
case LAError.systemCancel:
print("System cancel")
break
default:
break
}
}
}
})
} else {
if let err = error {
if #available(iOS 11.0, *) {
self.success = false
switch err.code {
case LAError.Code.biometryNotEnrolled.rawValue:
notifyUser("Your device not enrolled for biometric",
err: err.localizedDescription)
case LAError.Code.passcodeNotSet.rawValue:
notifyUser("A passcode has not been set",
err: err.localizedDescription)
case LAError.Code.biometryNotAvailable.rawValue:
notifyUser("Biometric authentication not available",
err: err.localizedDescription)
default:
notifyUser("Unknown error",
err: err.localizedDescription)
}
} else {
// Fallback on earlier versions
}
}
}
}
func notifyUser(_ msg: String, err: String?) {
let alert = UIAlertController(title: msg, message: err, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
}
func bioandpasscodeAuthentication() {
let context = LAContext()
var error:NSError?
// edit line - deviceOwnerAuthentication
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
//showAlertViewIfNoBiometricSensorHasBeenDetected()
return
}
let reason = "Identify yourself!"
// edit line - deviceOwnerAuthentication
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
// edit line - deviceOwnerAuthentication
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason, reply: { (success, error) in
if success {
DispatchQueue.main.async {
print("Authentication was successful")
}
}else {
DispatchQueue.main.async {
//self.displayErrorMessage(error: error as! LAError )
print("Authentication was error")
}
}
})
}else {
// self.showAlertWith(title: "Error", message: (errorPointer?.localizedDescription)!)
}
}
try this code
Is there a cleaner, swiftier solution to handle the optional chaining happening in my code below? I'm setting up the user for CloudKit access in my custom function runCKsetup():
func runCKsetup() {
container.requestApplicationPermission(.userDiscoverability) { (status, error) in
guard error == nil else {
if let error = error as? NSError {
if let errorDictionary: AnyObject = error.userInfo as? Dictionary<String, AnyObject> as AnyObject? {
let localizedDescription = errorDictionary["NSLocalizedDescription"]! as! String!
if localizedDescription! == "This operation has been rate limited" {
// Create an alert view
let alert = UIAlertController(title: "Network Error", message: localizedDescription!, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Test for Connection", style: UIAlertActionStyle.default) { (action) in
self.runCKsetup()
})
self.present(alert, animated: true, completion: nil)
} else {
// Create an alert view
let alert = UIAlertController(title: "Sign in to iCloud", message: localizedDescription!, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default) { (action) in
})
self.present(alert, animated: true, completion: nil)
}
}
}
return
}
if status == CKApplicationPermissionStatus.granted {
self.container.fetchUserRecordID { (recordID, error) in
guard error == nil else {
self.presentMessageAlert((error?.localizedDescription)!, title: "Error", buttonTitle: "Ok")
return }
guard let recordID = recordID else { return }
self.container.discoverUserIdentity(withUserRecordID: recordID, completionHandler: { (info, fetchError) in
//do something with the users names: e.g. print("\(info?.nameComponents?.givenName) \(info?.nameComponents?.familyName)")
})
}
}
}
}
I want to check if there is already a user with the chosen username in Firebase and I've created a function checkUsernameAlreadyTaken(username: String) -> Bool that do this.
Here is the code pf the function:
func checkUsernameAlreadyTaken(username: String) -> Bool {
databaseRef.child("usernames").child("\(username)").observe(.value, with: { (snapshot) in
print(username)
if snapshot.exists() {
print("Snapshot exist")
self.alreadyTaken = true
}
})
if alreadyTaken == true {
print("Username already taken")
return false
}
else {
return true
}
}
The problem is that the method observe(_ eventType: FIRDataEventType, with block: (FIRDataSnapshot) -> Void) -> Uint is an async method and so I can not use the strategy you can see above. But I can't return the value from the Firebase method because it's a void method...
How can I solve this problem?
One more thing. How can I return false also if there is a connection error or no connection with the server?
You have to employ asynchronous completion handler yourself and verify if there is Internet connection:
func checkUsernameAlreadyTaken(username: String, completionHandler: (Bool) -> ()) {
databaseRef.child("usernames").child("\(username)").observe(.value, with: { (snapshot) in
print(username)
if snapshot.exists() {
completionHandler(false)
} else {
let connectedRef = FIRDatabase.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool, connected {
completionHandler(true)
} else {
completionHandler(false)
// Show a popup with the description
let alert = UIAlertController(title: NSLocalizedString("No connection", comment: "Title Internet connection error"), message: NSLocalizedString("No internet connection, please go online", comment: "Internet connection error saving/retriving data in Firebase Database"), preferredStyle: .alert)
let defaultOkAction = UIAlertAction(title: NSLocalizedString("No internet connection, please go online", comment: "Internet connection error saving/retriving data in Firebase Database"), style: .default, handler: nil)
alert.addAction(defaultOkAction)
self.present(alert, animated: true, completion: nil)
}
})
}
})
}
Then you call your method with:
checkIfUserExists(username: text, completionHandler: { (value) in
// ...
})
I am following the "Learning Swift" book from O'reilly, and I have some code that looks like:
func deleteDocumentAtURL(url: NSURL) {
NSLog("Got to top of deleteDocumentAtURL, url: \(url)")
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinateWritingItemAtURL(url, options: .ForDeleting, error: nil, byAccessor: { (urlForModifying) -> Void in
NSLog("Here I am")
do {
NSLog("Got inside deleteDocumentBlock")
try NSFileManager.defaultManager().removeItemAtURL(urlForModifying)
// Remove the URL from the list
self.availableFiles = self.availableFiles.filter {
$0 != url
}
// Update the collection
self.collectionView?.reloadData()
} catch let error as NSError {
let alert = UIAlertController(title: "Error deleting", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
}
When I run this code, which is triggered by clicking a button in the interface, the application hangs. Also, the log inside the do{} is not firing, which makes me think there's a problem with the whole block? Help appreciated.
Because
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
is local in the function. The variable is already destroyed, if you finished the function.
So, the async block can't be executed.
Put fileCoordinator as an instance variable to the class.
You are not handling error and probably have an error
Documentation for coordinateReadingItemAtURL:
outError: On input, a pointer to a pointer for an error object. If a file presenter encounters an error while preparing for this read operation, that error is returned in this parameter and the block in the reader parameter is not executed. If you cancel this operation before the reader block is executed, this parameter contains an error object on output.
add error handler and see if you get an error
func deleteDocumentAtURL(url: NSURL) {
NSLog("Got to top of deleteDocumentAtURL, url: \(url)")
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
var error: NSError?
fileCoordinator.coordinateWritingItemAtURL(url, options: .ForDeleting, error: &error, byAccessor: { (urlForModifying) -> Void in
NSLog("Here I am")
do {
NSLog("Got inside deleteDocumentBlock")
try NSFileManager.defaultManager().removeItemAtURL(urlForModifying)
// Remove the URL from the list
self.availableFiles = self.availableFiles.filter {
$0 != url
}
// Update the collection
self.collectionView?.reloadData()
} catch let error as NSError {
let alert = UIAlertController(title: "Error deleting", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
//Error:
if(error != nil){
print(error)
}
}