Handing a response in a controller after segueing to another controller - ios

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

Related

Intermittent data loss with background fetch - could NSKeyedUnarchiver return nil from the documents directory?

I have a simple app that stores an array of my custom type (instances of a class called Drug) using NSCoding in the app’s documents folder.
The loading and saving code is an extension to my main view controller, which always exists once it is loaded.
Initialisation of array:
var drugs = [Drug]()
This array is then appended with the result of the loadDrugs() method below.
func saveDrugs() {
// Save to app container
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(drugs, toFile: Drug.ArchiveURL.path)
// Save to shared container (for iMessage, Spotlight, widget)
let isSuccessfulSaveToSharedContainer = NSKeyedArchiver.archiveRootObject(drugs, toFile: Drug.SharedArchiveURL.path)
}
Here is the code for loading data.
func loadDrugs() -> [Drug]? {
var appContainerDrugs = NSKeyedUnarchiver.unarchiveObject(withFile: Drug.ArchiveURL.path) as? [Drug]
return appContainerDrugs
}
Data is also stored in iCloud using CloudKit and the app can respond to CK notifications to fetch changes from another device. Background fetch also triggers this same method.
// App Delegate
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Code to get reference to my main view controller
// This will have called loadDrugs() to populate local array drugs of type [Drug]
mainVC.getZoneChanges()
}
Finally, there is the getZoneChanges() method, which uses a stored CKServerChangeToken to get the changes from the private user database with CKFetchRecordZoneChangesOperation. The completion block calls saveDrugs().
The problem
All of this seems to work fine. However, sometimes all local data disappears between uses of the app, especially if it has not been used for some time. Deleting and reinstalling the app does pull the backed-up data from iCloud thankfully.
It seems to happen if the app has not been used for a while (presumably terminated by the system). Something has to have changed, so I presume it is the calling of a background fetch when the app is terminated that may be the problem. Everything works fine while debugging and when the app has been in foreground recently.
Possible causes
I’m guessing the problem is that I depend on background fetch (or receiving a CK notification) loading my main view controller in the background and then loading saved local data.
I have heard that UserDefaults does not work correctly in the background and there can be file security protections against accessing the documents directory in this context. If this is the case here, I could be loading an empty array (or rather initialising the array and not appending the data to it) and then saving it, overwriting existing data, all without the user knowing.
How can I circumvent this problem? Is there a way to check if the data is being loaded correctly? I tried making a conditional load with a fatal error if there is a problem, but this causes problems on the first run of the app as there is no data anyway!
Edit
The archive URLs are obtained dynamically as shown below. I just use a static method in my main data model class (Drug) to access them:
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("drugs")
The most common cause of this kind of issue is being awakened in the background when the device is locked and protected data are encrypted.
As a starting point, you can check UIApplication.isProtectedDataAvailable to verify that protected data is available. You can also lower the protection levels of data you require to .completeUntilFirstUserAuthentication (the specifics of how to do that depends on the how you create your files).
As a rule, you should minimize reducing data protection levels on sensitive information, so it's often best to write to some other location while the device is locked, and then merge that once the device is unlocked.
The problem is that you are storing the full path of the file and not just the fileName (or relative path). The document directory URL can change, and then if you stored that URL persistently you will not be pointing to the correct location of the file. Instead just store the filename and use NSFileManager URLsForDirectory to get the documents directly every time you need it.

Not able to update profile pic in MFSideMenu

I am trying to update my profile picture on side menu (MFSideMenu) and as soon as I update profile picture over the web server, I want to set it on my iOS app. URL of the image being the same, I am unable to upgrade it. I guess the problem is that every time I change image, image url being the same is loaded from the cache memory so it doesn't change. Is there any way to download changed image as when it changes without the need to change imageUrl?
In response I always get
imageUrl :"user_image" = "profile/local/images/user_id/myimage";
Accordingly I update my model and fetch information from model as I open the side menu.
`func menuAction(){
self.getUserDetails()
}
func getUserDetails() {
NotificationCenter.default.post(name: Notification.Name("NotificationIdentifier"), object: self)
self.menuContainerViewController.toggleLeftSideMenuCompletion(nil)
}`
This may be a quick fix. But this is not the best way.
Add different query parameters to the url when you going to set(download) the image.
For eg:
If your image url is http://www.yourserver.com/images/my_image.png then change it to http://www.yourserver.com/images/my_image.png?any_name=timestamp

Swift 3 and JSON – Updating the database by running a URL in the background

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.

Pass data from TodayExtension to app

Is it possible to pass data from a today extension to an app? (Even when its not currently running). I wish to pass an array of objects to the main app and instantiate a viewController based on the objects passed from the today extension. I know how to open the app from the extension just not to too sure how to send data to the app.
var arrayToBePassed: [MyDataSource]
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var url = ""
if currentSort == .recent{
url = "ext://recent"
}else{
url = "ext://popular"
}
//HOW DO I PASS arrayToBePassed to my app?
let myAppUrl = NSURL(string: url)!
extensionContext?.openURL(myAppUrl, completionHandler: { (success) in
if (!success) {
}else{
}
})
}
There's a few routes you could go down:
• In your app, implement a URL scheme that can quite literally take that array of objects, formatted as some sort of URL parameter. e.g. myAwesomeApp://objectArray=[these, are, strings]. You'll have to parse that URL yourself which should be a bit of fun.
• Look into NSUserDefaults. You can initialise a 'shared' user defaults object that both your app and your today extension can use, see here for more info. You could then store your array in here, and access it from the app when opened.
It really depends on what you're trying to do, but from the impression I get, I feel like the first option may be the best answer. I haven't provided any code, just an outline of how I'd go about it - but hopefully that should be enough to get you off to a good start.
Sharing of data is achieved through a new concept called “App Groups”. App Groups are allowed to share some data, including files, but it is worth noting that file access needs to be marshalled to avoid concurrent writes and so forth. This can be achieved through NSFileCoordination, but CoreData and NSUserDefaults handle this out of the box.
You can find the detial Steps here

Disable confirmation on delete request in PHPhotoLibrary

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.

Resources