What I am trying to do is to save videos to PHPhotoLibrary, and then remove them when upload to clients remote server in the application completes (basically, photo library serves as temporary storage to add additional layer of security in case anything at all fails (I already save my vides it in the applications directory).
Problem:
The problem is for that to work, everything has to work without input from the user. You can write video to photos library like this:
func storeVideoToLibraryForUpload(upload : SMUpload) {
if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.Authorized {
// Don't write to library since this is disallowed by user
return
}
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
// Write asset
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(NSURL(fileURLWithPath: upload.nonsecureFilePath!)!)
let assetPlaceholder = assetRequest.placeholderForCreatedAsset
let localIdentifier = assetPlaceholder.localIdentifier
// Store local identifier for later use
upload.localAssetIdentifier = localIdentifier
}, completionHandler: { (success, error) -> Void in
....
})
}
And that works flawlessly, I get local identifier, I store it for later use.. Unicorns and rainbows.
Now when I want to remove that video immediately after upload finishes, I call following:
func removeVideoFromLibraryForUpload(upload : SMUpload) {
// Only proceed if there is asset identifier (video previously stored)
if let assetIdentifier = upload.localAssetIdentifier {
// Find asset that we previously stored
let assets = PHAsset.fetchAssetsWithLocalIdentifiers([assetIdentifier], options: PHFetchOptions())
// Fetch asset, if found, delete it
if let fetchedAssets = assets.firstObject as? PHAsset {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
// Delete asset
PHAssetChangeRequest.deleteAssets([fetchedAssets])
}, completionHandler: { (success, error) -> Void in
...
})
}
}
}
Which successfully deletes the video, BUT user have to confirm deletion first. That is a problem as that backing up won't work.
I obviously know why there is confirmation (so you don't clear entire user library for example, but the thing is, My app made the video - and so I thought there will be way around it, since as an "owner" I should not be doing that, or at least have option to disable confirmation.
Thanks in advance!
TLDR: How can I disable confirmation on delete request, if my application created that content? (I don't want to delete anything else).
Note: Somebody can probably say this is rather strange thing to do but the application is distributed internally and there is good reason to do it like this (the video content is too valuable to be lost, even if user deletes the application for some reason, or there is anything at all that goes wrong, we need to be able to preserve the videos), so please don't question that and just focus your attention on the question :)
I cannot see a way to avoid the delete confirmation. It is an implementation detail of the Photos framework, similar to the way you cannot prevent the device from asking the user's permission to use the microphone when your app tries to use it, and is a matter of security & trust. Once you have saved an asset to the device photo library your app is no longer the owner of that asset, so as you noted in your question the device must of course ensure the app has the user's permission before it goes about deleting such data.
You can never entirely safeguard your users' data against their own unpredictable behaviour - if they decide to remove your app, or delete a particular asset from within Photos, it is up to them. I think your best option is to either put up with the built-in delete confirmation, or to provide a guide to your users that makes it clear that they should be careful to protect this important data by backing up their device, and not deleting the app!
If you did decide to stick to this approach, perhaps the best thing you could do is to prepare the user for the fact that their device may ask them for confirmation to delete a file that is being uploaded to your own servers. For example, put up your own modal alert just before trying to delete the asset. I wouldn't normally suggest that kind of approach for a public shipping app, but since you're only distributing internally it may be acceptable for your team.
Related
1. Clicking the link causes a database update.
There is a certain link I have access to (let's pretend it's www.google.com), such that when I open it up in my browser, it updates a certain section of the JSON code in my database. Based on the numbers that make up a portion of the link, it adjusts a certain value in the data.
2. How do I run this link in the background of my iOS app?
I need to be able to "open" this link within the app, without actually opening up a UIWebview and visually visiting the site. I just need this JSON data inside the database to update on its own (with the user unaware that it even happened).
The problem I'm having here is that I simply don't know how this is done. How do I cause this link to be visited without opening up a Safari browser?
The best approach I've found for such functions is to treat them as if they were "AJAX" (or "REST", "API", etc.) - while these terms are often used (and for more seasoned programmers instantly give a certain thought), the end result is that they take information from your 'originator' and send to the 'server' for processing, which then replies with a 'response'. Once you get that concept in your head, this becomes a fairly simple activity.
(for our example, I will call this "API", as that does really suit {as #Mortiz suggested} this question best)
For Swift 3, there are several ways to do this, I'll show you two I've found and use for various functions:
DispatchQueue
For a 'one-time shot to a url that I know exists and will connect reliability', this is a good one to use (think of it as a 'quick-n-dirty' if you like....!)
DispatchQueue.global().async {
let data = try? Data(contentsOf: theURL!) //make sure your url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
// do stuff here with the data if you need
// you can get the response from the server and parse it out, set buttons in the app, etc.
}
}
Alamofire
For Swift 3, Alamofire is extremely popular and does a lot of great stuff. Check it out if you haven't already!
Alamofire.request("\(theURL!)").responseJSON { response in
print("result is ", response.result)
switch response.result {
case .success(let value):
// do stuff with the returned data
// like updating your internal database, etc.
print(value)
case .failure(let error):
print("There was an error")
// you can see the error response in various ways....
print(requested)
print(error)
print(response)
print(response.result)
}
}
Once you have your buttons in place (from your description it sounds like that is what your #1 is about), then in the function you call when it is clicked, drop in the code from above and 'do stuff' as you need.
This will make the update to the server automatically in the background (answering your #2) - the user won't notice anything unless there are connection issues to the internet, etc. (much too complex to get into here, though if you expect to have much of it, Alamofire is a great choice as it automatically retries, etc. (part of the great features you should check out)
A key piece of this is that you can take the response from the URL (send various bits of JSON data back from the server, then break it back down in the phone) and do 'whatever' with it.
Some things you can do (to hopefully give you more ideas - which is just about anything.....):
update data in the app (local storage, local variables, etc.)
update text (color, background) inside Buttons or Labels
process Alerts to the user (not your case, but sometimes you want to let them know what went on - certainly if it was an error in updating your server)
change Images (various things)
switch Views
Well, the list is as long as "things you can do in an app", so decide for yourself what you need to mod/update - this is "the" way to do it!
You could also use the UIWebView without ever showing it, like this (Swift 3):
func webView() {
let theWebView: UIWebView
theWebView = UIWebView(frame: UIScreen.main.bounds)
theWebView.delegate = self
if let theURL = URL(string: "your URL") {
let request = URLRequest(url: theURL)
theWebView.loadRequest(request)
}
}
Just don't add it to the view.
So, I have been coming across a problem where my Firebase app does not update user values when a user makes an update. To be more clear: Lets say user 1 has a photo of a dog and then changes it to a cat.
Once they change it to a cat, my node value in Firebase is successfully updated but the user themselves won't be able to see the change in other previously loaded areas in the app (other places with the dog picture) until they log out and then log back in.
For this reason I was wondering if there was any way to conduct a background app refresh that way all previous dog values in the app are changed to cat values without the user having to log out and then log back in. Please note that this same problem occurs not only with my user's profile picture but also any other user field I have setup.
Here is how I am updating a node value for my user in Firebase:
let storageRef = FIRStorage.storage().reference()
_ = FIRStorageMetadata()
let filePath = "\(FIRAuth.auth()?.currentUser?.uid)/\("userPhoto")"
let profileImageData = UIImageJPEGRepresentation(self.profilePicture.image!, 1.0)
if let data = profileImageData {
storageRef.child(filePath).put(data, metadata: nil){(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
} else {
let downloadURL = metaData!.downloadURL()!.absoluteString
let userPhotoUpdateRef = FIRDatabase.database().reference().child("users").child(self.currentUser).child("userPhoto")
userPhotoUpdateRef.setValue(downloadURL)
}
}
}
If you have any questions please ask! Any help would be appreciated!
The Firebase SDK for Cloud Storage provides an easy way to read file from and write files to cloud storage. It does not provide a way to monitor those files.
The easiest way to provide a monitoring approach is to write the metadata of the files to the Firebase Realtime Database. See this short section in the Storage docs for a brief mention of that: https://firebase.google.com/docs/storage/ios/file-metadata#custom_metadata
When you write data to a location in the Firebase Database, all apps that are actively monitoring that location will be instantly updated. When they get that update, you can reload the image from Cloud Storage for Firebase.
I'm using Cloudinary to store images that the user uploads in my Swift 2 app. I have a question about the flow/architecture. Currently what I'm doing is that as soon as the user uploads the image, I send the image to Cloudinary, send the image id to my server, and immediately segue the user to the next screen. With the id, I can later reference the image on Cloudinary, so I technically don't need to wait for the response back from Cloudinary and keep the user waiting. Yes, if there is an upload failure, I won't know, but those are edge cases. This is the code I have:
#IBAction func btnRegister(sender: AnyObject) {
let pictureId = self.btnImage.currentImage == NSUUID().UUIDString
self.uploadToCloudinary(pictureId)
//send to my server
User.register(_userName, password: _password, picture: pictureId)
.success { (value) in
self.performSegueWithIdentifier("Home", sender: self)
}
}
func uploadToCloudinary(pictureId:String){
let image = UIImagePNGRepresentation(btnImage.currentImage!)! as NSData
let uploader = CLUploader(_cloudinary, delegate: self)
uploader.upload(image, options: ["public_id":pictureId], withCompletion:onCloudinaryCompletion, andProgress:onCloudinaryProgress)
}
func onCloudinaryCompletion(successResult:[NSObject : AnyObject]!, errorResult:String!, code:Int, idContext:AnyObject!) {
//
}
However, I now realized that I need to save some additional details such as "version" that Cloudinary sends me back, so I might need to do something on onCloudinaryCompletion to send those details to my server. The thing is--that means I have to now make the user wait till the image finishes uploading before I can segue them to the next screen.
So my questions:
In Swift, can I still do stuff in the completion handler even if the user has segeued away to another view controller? That way I can keep my current flow, and just save the successResult whenever it returns, but I don't have to keep my user waiting.
Is there another recommended flow for doing something like this? Any suggestions welcome.
For those of you who are familiar with Cloudinary--is there a way to know the version before uploading? Because all I really need is the version, and if I can know that before uploading, I can save that along with the picture Id, and forget about the onCloudinaryCompletion.
From Cloudinary perspective you could use one of the following approaches:
Avoid using versions - URL versions are basically just a mean for busting the cache in case the image is updated. If this isn't essential then you can just remove it from the URL.
See: https://support.cloudinary.com/hc/en-us/articles/202520912-What-are-image-versions-
Use our Webhooks support - this will tell Cloudinary to notify your server with all the necessary details (including Public ID and Version) regarding the uploaded file, which you can then store together on your DB.
See: http://cloudinary.com/blog/webhooks_upload_notifications_and_background_image_processing
I'm building an app for my school which includes a lunch menu, however since the lunch menu changes every month I don't want to keep updating my app just for that. I know you can open PDFs with Xcode either locally or online, but is there a way to change the url path of the pdf manually and have it update real time within the app. Any help would be much appreciated and I'm new to developing IOS apps, so any links that may help would be great.
If you control the website or have a line of communication with the person who does then you could agree that the latest menu always gets returned from example.com/current/lunch.pdf but really this should be set up as a GET request example.com/index.php?lunchmenu=current so that it is down to the website to respond with the file or the file location rather than the app's responsibility to guess the filename. So for example:
if let url = NSURL(string: "http://www.example.com/index.php?lunchmenu=current") {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let dataTask = session.dataTaskWithURL(url, completionHandler: {(d,_,_) in
if let data = d {
String(data:data, encoding:NSUTF8StringEncoding) // this is could be your file name
}
})
dataTask.resume()
}
It would then be possible to perform other requests like: example.com/index.php?lunchmenu=nextmonth. Working in this way means that if for some reason the naming pattern or location of the files changed your app would still keep working.
If you don't have access to the school website but do know where the file is stored, you could query your own website for the file information and either update this automatically (or if there was no other way - manually). Again, this would prevent an app update if the structure of the school website changed.
Only rely on static file locations and naming systems if you really have to.
So I have code that will delete images from the camera roll. It works fine, and can delete single images from a burst, however one of the images, if deleted, will delete the entire batch and I can't figure out how to stop that. It usually seems to be the last image in the burst group. And in my request options, I turn on includeAllBurstAssets.
func deletePhotos(assetsToDelete: [PHAsset]){
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(assetsToDelete)
return
}, completionHandler: { success, error in
guard let error = error else {
return
}
print(error)
}
})
}
I can confirm this behavior. I guess the API is designed to delete the entire batch, if one burst photo is deleted.
Please note, that the Apple Photos app also has no method to delete single burst photos.
It would make sense to make that behavior customizable and I would suggest you fill a bug report /enhancement request.