I'm writing a Swift extension on FIRStorageReference to detect if a file exists or not. I am calling metadataWithCompletion(). If the completion block's optional NSError is not set, I think it's safe to assume that the file exists.
If the NSError is set, either something went wrong or the file doesn't exist. The storage documentation on handling errors in iOS states that FIRStorageErrorCodeObjectNotFound is the type of error that I should be checking, but is doesn't resolve (possibly Swiftified into a shorter .Name-style constant?) and I'm not sure what I should be checking it against.
I'd like to be calling completion(nil, false) if FIRStorageErrorCodeObjectNotFound is set somewhere.
Here's my code so far.
extension FIRStorageReference {
func exists(completion: (NSError?, Bool?) -> ()) {
metadataWithCompletion() { metadata, error in
if let error = error {
print("Error: \(error.localizedDescription)")
print("Error.code: \(error.code)")
// This is where I'd expect to be checking something.
completion(error, nil)
return
} else {
completion(nil, true)
}
}
}
}
Many thanks in advance.
You can check the error code like so:
// Check error code after completion
storageRef.metadataWithCompletion() { metadata, error in
guard let storageError = error else { return }
guard let errorCode = FIRStorageErrorCode(rawValue: storageError.code) else { return }
switch errorCode {
case .ObjectNotFound:
// File doesn't exist
case .Unauthorized:
// User doesn't have permission to access file
case .Cancelled:
// User canceled the upload
...
case .Unknown:
// Unknown error occurred, inspect the server response
}
}
Thats a simple code that I use to check if user has already got a user photo by hasChild("") method ,and the reference is here:
https://firebase.google.com/docs/reference/ios/firebasedatabase/interface_f_i_r_data_snapshot.html
hope this can help
let userID = FIRAuth.auth()?.currentUser?.uid
self.databaseRef.child("users").child(userID!).observeEventType(.Value, withBlock: { (snapshot) in
// Get user value
dispatch_async(dispatch_get_main_queue()){
let username = snapshot.value!["username"] as! String
self.userNameLabel.text = username
// check if user has photo
if snapshot.hasChild("userPhoto"){
// set image locatin
let filePath = "\(userID!)/\("userPhoto")"
// Assuming a < 10MB file, though you can change that
self.storageRef.child(filePath).dataWithMaxSize(10*1024*1024, completion: { (data, error) in
let userPhoto = UIImage(data: data!)
self.userPhoto.image = userPhoto
})
}
Swift 5
let storageRef = Storage.storage().reference().child("yourPath").child("\(someFile)") // eg. someVideoFile.mp4
print(storageRef.fullPath) // use this to print out the exact path that your checking to make sure there aren't any errors
storageRef.getMetadata() { (metadata: StorageMetadata?, error) in
if let error = error {
guard let errorCode = (error as NSError?)?.code else {
print("problem with error")
return
}
guard let err = StorageErrorCode(rawValue: errorCode) else {
print("problem with error code")
return
}
switch err {
case .objectNotFound:
print("File doesn't exist")
case .unauthorized:
print("User doesn't have permission to access file")
case .cancelled:
print("User cancelled the download")
case .unknown:
print("Unknown error occurred, inspect the server response")
default:
print("Another error occurred. This is a good place to retry the download")
}
return
}
// Metadata contains file metadata such as size, content-type.
guard let metadata = metadata else {
// an error occured while trying to retrieve metadata
print("metadata error")
return
}
if metadata.isFile {
print("file must exist becaus metaData is a file")
} else {
print("file for metadata doesn't exist")
}
let size = metadata.size
if size != 0 {
print("file must exist because this data has a size of: ", size)
} else {
print("if file size is equal to zero there must be a problem"
}
}
A shorter version without the detailed error check:
let storageRef = Storage.storage().reference().child("yourPath").child("\(someFile)")
storageRef.getMetadata() { (metadata: StorageMetadata?, error) in
if let error = error { return }
guard let metadata = metadata else { return }
if metadata.isFile {
print("file must exist because metaData is a file")
} else {
print("file for metadata doesn't exist")
}
let size = metadata.size
if size != 0 {
print("file must exist because this data has a size of: ", size)
} else {
print("if file size is equal to zero there must be a problem"
}
}
Related
My database is currently organized into two collections: male_users & female_users. When the app is first launched AND the user is already logged in, I attempt to pull their usernode from the database. The problem I am facing is, at this time I don't know whether to search the MALE_COLLECTION or FEMALE_COLLECTION to find the user. What would be the proper way of working around this? Should I use user defaults to save the gender of the last user?
static func fetchUser(withUid uid: String, completion: #escaping (User) -> Void) {
COLLECTION_MALE_USERS.document(uid).getDocument { (snapshot, error) in
if let userNode = snapshot?.data() {
guard let user = User(with: userNode) else {
print("DEBUG: Failed to create user")
return
}
completion(user)
}
else {
COLLECTION_FEMALE_USERS.document(uid).getDocument { snapshot, error in
guard let userNode = snapshot?.data() else {
print("DEBUG: No user node found")
return
}
guard let user = User(with: userNode) else {
print("DEBUG: Failed to create user")
return
}
completion(user)
}
}
}
}
I would suggest doing something like Ahmed Shendy suggested but I'm not familiar with geofire.
You could use something like shown below or better yet, move the second fetch to a new function and call that new function after fetching for male users produces no results.
func fetchUser(uid: String, onSuccess: #escaping(_ user: User) -> Void, onError: #escaping(_ errorMessage: String) -> Void) {
let maleDocRef = COLLECTION_MALE_USERS.document(uid)
let femaleDocRef = COLLECTION_FEMALE_USERS.document(uid)
maleDocRef.getDocument { (document, error) in
if let error = error {
onError(error.localizedDescription)
return
}
if let document = document, document.exists {
print("male user exists")
guard let data = document.data() else { return }
let user = User(with: data)
onSuccess(user)
} else {
print("male user does no exists")
// BOF second fetch
femaleDocRef.getDocument { (document, error) in
if let error = error {
onError(error.localizedDescription)
return
}
if let document = document, document.exists {
print("female user exists")
guard let data = document.data() else { return }
let user = User(with: data)
onSuccess(user)
} else {
print("no user either male or female exist")
}
}
// EOF second fetch
}
}
}
So I'm trying to get all documents from a specific collection from Firestore, but when stepping through the code, it skipped over the line and doesn't throw an error. I've used the exact same code in other parts of my app with success but it is not working here for some reason? Any help would be appreciated.
func getClientEmail() -> String? {
var clientEmail: String?
self.db.collection("Clients").getDocuments() { (querySnapshot, err) in
if let err = err {
print("error getting docs: \(err)")
} else {
for document in querySnapshot!.documents {
let result = Result {
try document.data(as: User.self)
}
switch result {
case .success(let client):
if let client = client {
if client.placementIDs[0] == self.user?.placementIDs[0] {
clientEmail = client.email
}
} else {
print("Document doesn't exist")
}
case .failure(let error):
print("Error decoding user: \(error)")
}
}
}
}
return clientEmail
}
After some testing, I moved this code to within the viewDidLoad() function and it worked... So I think it has something to do within it being wrapped in a function but not sure why, hope this information is helpful to anyone that might be able to fix this problem.
Thanks to Jay's comment, I managed to fix the problem by having clientEmail as a global variable and using a completion handler to assign the value in this function.
func getClientEmail() {
// Get email of client for corresponding contractor
db.collection("Clients").getDocuments(completion: { (querySnapshot, err) in
if let err = err {
print(err.localizedDescription)
return
} else {
for document in querySnapshot!.documents {
let result = Result {
try document.data(as: User.self)
}
switch result {
case .success(let client):
if let client = client {
if client.placementIDs[0] == self.user?.placementIDs[0] {
self.clientEmail = client.email
}
} else {
print("Document doesn't exist")
}
case .failure(let error):
print("Error decoding user: \(error)")
}
}
}
})
}
I currently have a for loop used to download images from urls stored in arrays. I keep having the issue of seeing the same image after every 2 or 3 new image fetches. Every time I fetch new images, I can see it's using different urls to download images but the images in the arrays are the same.
here is my code below:
func downloadImageDetailsFromFlickr(completion: #escaping (Bool, Error?) -> Void) {
self.imageLoading(is: true)
flickrClient.getImageDetailsFromFlickr(lat: latitude, lon: longitude) { (photo, error) in
if error == nil {
self.photoStore = []
for image in photo {
if image.url_m.count == 0 {
self.imageLabel.isHidden = false
self.imageLoading(is: false)
completion(false, nil)
} else {
self.photoStore.append(image.url_m)
print(self.photoStore)
completion(true, nil)
}
}
} else {
print(error?.localizedDescription ?? "Error in the fetch image block")
print("problem in downloading details.")
completion(false, error)
}
}
}
func downloadImage(completion: #escaping(Bool, Error?) -> Void ) {
let flickrImage = FlickrImage(context: self.dataController.viewContext)
for flickr in photoStore {
self.photoUrl = ""
self.photoUrl = flickr
}
flickrClient.getImage(imageUrl: photoUrl ?? "") { (data, error) in
if error == nil {
self.imageLoading(is: false)
flickrImage.photo = data
flickrImage.url = self.photoUrl
flickrImage.locations = self.location
flickrImage.locations?.latitude = self.latitude
flickrImage.locations?.longitude = self.longitude
}
else {
print(error?.localizedDescription ?? "Something went wrong downloading an image")
}
do {
try self.dataController.viewContext.save()
self.photoStore.removeAll()
completion(true, nil)
} catch {
print("Error saving into Core Data")
completion(false, error)
}
}
}
Please ignore the red box, the white box shows the images being fetched all over again. My guess is, it has to do with core data.
i am not sure if this will fix your problem but, may be you try use dispatchgroup()
so it will goes like this
let group = DispatchGroup()
for flickr in photoStore {
//fetch image from url here
group.enter()
flickrClient.getImage(imageUrl: flickr ?? "") { (data, error) in
if error == nil {
self.imageLoading(is: false)
flickrImage.photo = data
flickrImage.url = self.photoUrl
flickrImage.locations = self.location
flickrImage.locations?.latitude = self.latitude
flickrImage.locations?.longitude = self.longitude
group.leave()
}
else {
print(error?.localizedDescription ?? "Something went wrong downloading an image")
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
//save your dowloaded image here
try self?.dataController.viewContext.save()
self?.photoStore.removeAll()
} catch {
print("Error saving into Core Data")
}
}
so basically by using dispatchgroup you can group the task and run it asynchronously. It just to make sure the request is not repeted and you save the image to core data once.
I want to test faceId/touchId mechanism in my app.
I have following:
func auth(){
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
var reason: String = Strings.Common.unknownBiometryTip.value
if BiometricService.biometricType() == .touchID {
reason = Strings.Common.touchIDEnterTip.value
}
if BiometricService.biometricType() == .faceID {
reason = Strings.Common.faceIDEnterTip.value
}
context.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: reason) { [weak self] (success, error) in
print("error \(error)")
if let error = error as? LAError, error.errorCode == Int(kLAErrorUserCancel) {
self?.cancelTapped?()
return
}
if success {
self?.authSucceed?()
}
else {
self?.authFailure?()
}
}
}
else {
print("Touch ID neither Face ID not available")
}
}
I actually can't test authFailure block, because when i hit "not-matching face" button in XCode, it shows up pin entry view, and look like anything i type in handled as "correct".
I actually want callback anytime user have "wrong" face/touchId/pin entry attempt, but i can't figure out how to get it.
After updating Firebase to Version 4 and correcting all 200 errors, I am left with 2 warnings which make my app crash. I looked up this error and tried the resolution with no success:
storageReference.downloadURLWithCompletion()
I must be doing it wrong:
func setUserInfo(_ user: User!, usersname: String, email: String, password: String, cell: String, data: Data!) {
// Create Path for User Image
let imagePath = "images/riders/\(user.uid)/Profile Pic/userPic.jpg"
// Create image Reference
let imageRef = rDataService.Instance.storageRef.child(imagePath)
// Create Metadata for the image
let metaData = StorageMetadata()
metaData.contentType = "image/jpeg"
// Save the user Image in the Firebase Storage File
imageRef.putData(data as Data, metadata: metaData) { (metaData, error) in
if error == nil {
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = usersname
changeRequest.photoURL = metaData?.downloadURL()
changeRequest.commitChanges(completion: { (error) in
if error == nil {
self.saveUser(user, usersname: usersname, email: email, password: password, cell: cell)
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
}
}
The error is happening on this line:
changeRequest.photoURL = metaData?.downloadURL()
Edit
After adjustments, getting warning on this line:
if let profilepicMetaData = profilepicMetaData {
error: Value 'profilepicMetaData' was defined but never used; consider replacing with boolean test
App is still crashing:
// Save the user profile Image in the Firebase Storage File
imageRef.putData(data as Data, metadata: profilepicMetaData) { (profilepicMetaData, error) in
if let profilepicMetaData = profilepicMetaData {
imageRef.downloadURL(completion: { (url, error) in
guard let url = url else {
if let error = error {
print(error)
}
return
}
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = usersname
changeRequest.photoURL = url
changeRequest.commitChanges(completion: { (error) in
if error == nil {
self.saveUser(user, usersname: usersname, email: email, password: password, year: year, makeAndModel: makeAndModel, cell: cell, plateNo: plateNo)
} else {
print(error!.localizedDescription)
}
})
})
} else {
print(error!.localizedDescription)
}
}
Crash!
You need to use the original storage reference object imageRef to obtain the download url. (please check the comments through the code):
imageRef.putData(data, metadata: profilepicMetaData) {
// use if let to unwrap the metadata returned to make sure the upload was successful. You can use an underscore to ignore the result
if let _ = $0 {
// start the async method downloadURL to fetch the url of the file uploaded
imageRef.downloadURL {
// unwrap the URL to make sure it is not nil
guard let url = $0 else {
// if the URL is nil unwrap the error, print it
if let error = $1 {
// you can present an alert with the error localised description
print(error)
}
return
}
// your createProfileChangeRequest code needs to be run after the download url method completes. If you place it after the closure it will be run before the async method finishes.
let changeRequest = user.createProfileChangeRequest()
changeRequest.displayName = usersname
changeRequest.photoURL = url
changeRequest.commitChanges {
// unwrap the error and print it
if let error = $0 {
// again you might present an alert with the error
print(error)
} else {
// user was updated successfully
self.saveUser(user, usersname: usersname, email: email, password: password, year: year, makeAndModel: makeAndModel, cell: cell, plateNo: plateNo)
}
}
}
} else if let error = $1 {
// present an alert with the error
print(error)
}
}