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
Related
This question is regarding the iOS 10.3’s new feature of giving user the ability to customize the app logo they see on the homescreen. (Check out MLB at Bat app for reference where they let the user pick which icon will be app logo: http://m.mlb.com/apps/atbat)
According to my research, we need to submit for apple review all the possible logo options. Then user can customize the logo using any of these options. Now in my specific use case, I might not always want all the logo options to be available to all the users. I need help figuring out how to control which logos are shown to all users?
eg. if we have 10 images, for user A we may want to only show Image 1 and 2 to pick from; and for user B we may want to show only Image 3 and Image 4 to pick from as their app logo. Is this possible? Thanks so much in advance!
You can control which icon is set using the setAlternateIconName(_:completionHandler:) method on UIApplication.
Example usage:
UIApplication.shared.setAlternateIconName("myImage", completionHandler: { error in
print("completed")
})
You get to call this whenever it is appropriate in your application. So if you only want to only show a few options, you can do so with your own views, and only call this method when needed.
More information in the documentation here: https://developer.apple.com/reference/uikit/uiapplication/2806818-setalternateiconname
Here is another SO answer with helpful code and images: https://stackoverflow.com/a/41951096/6658553
Add all icons to some folder in your application, with the help of below method you should manage which user should see which icons.
if([user isEqualToString:#"user1"]){
[UIApplication.sharedApplication setAlternateIconName:#"icon1" completionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(#"---> error - %#",error.description);
}
else{
NSLog(#"---> icon1 ");
}
}]
}
If you want to give user an option to choose from the icons, then also you can write the code accordingly so then he/she can only choose from the filtered ones.
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 have the exact same issue as "Paul" posted here: Can not export audiofiles via "open in:" from Voice Memos App - no answers have yet been posted on this topic.
Essentially what I'm trying to do is simple:
After having recorded a Voice Memo on iOS, I select "Open With" and from the popup that is shown I want to be able to select my app.
I've tried everything I can think of and experimented with LSItemContentTypes without success.
Unfortunately I don't have enough reputation to comment on the existing post above, and I'm getting quite desperate for a solution to this. Any help is hugely appreciated, even just to know whether it's doable or not.
Thanks!
After some experimentation and much guidance from this blog post ( http://www.theappguruz.com/blog/share-extension-in-ios-8 ), it appears that it is possible to do this using a combination of app extensions (specifically an Action Extension) and app groups. I'll describe the first part which will enable you to get your recording from Voice Memos to your app extension. The second part -- getting the recording from the app extension to the containing app (your "main" app) -- can be done using app groups; please consult the blog post above for how to do this.
Create a new target within your project for the app extension, by selecting File > New > Target... from Xcode's menu. In the dialog box that prompts you to "Choose a template for your new target:" choose the "Action Extension" and click "Next".
CAUTION: Do not choose the "Share Extension" as is done in the blog post example above. That approach is more appropriate for sharing with another user or posting to a website.
Fill in the "Product Name:" for your Action Extension, e.g., MyActionExtension. Also, for "Action Type:" I selected "Presents User Interface" because this is the way Dropbox appears to do it. Selecting this option adds a view controller (ActionViewController) and storyboard (Maininterface.storyboard) to your app extension. The view controller is a good place to provide feedback to the user and to give the user an opportunity to rename the audio file before exporting it to your app.
Click "Finish." You will be prompted to "Activate “MyActionExtension” scheme?". Click "Activate" and this new scheme will be made active. Building it will build both the action extension and the containing app.
Click the disclosure triangle for the "MyActionExtension" folder in the Project Navigator (Cmd-0) to reveal the newly-created storyboard, ActionViewController source file(s), and Info.plist. You will need to customize these files for your needs. But for now ...
Build and run the scheme you just created. You will be prompted to "Choose an app to run:". Select "Voice Memos" from the list and click "Run". (You will probably need a physical device for this; I don't think the simulator has Voice Memos on it.) This will build and deploy your action extension (and its containing app) to your device. and then proceed to launch "Voice Memos" on your device. If you now make a recording with "Voice Memos" and then attempt to share it, you should see your action extension (with a blank icon) in the bottom row. If you don't see it there, tap on the "More" button in that row and set the switch for your action extension to "On". Tapping on your action extension will just bring up an empty view with a "Done" button. The template code looks for an image file, and finding none does nothing. We'll fix this in the next step.
Edit ActionViewController.swift to make the following changes:
6a. Add import statements for AVFoundation and AVKit near the top of the file:
// the next two imports are only necessary because (for our sample code)
// we have chosen to present and play the audio in our app extension.
// if all we are going to be doing is handing the audio file off to the
// containing app (the usual scenario), we won't need these two frameworks
// in our app extension.
import AVFoundation
import AVKit
6b. Replace the entirety of override func viewDidLoad() {...} with the following:
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
// For example, look for an image and place it into an image view.
// Replace this with something appropriate for the type[s] your extension supports.
print("self.extensionContext!.inputItems = (self.extensionContext!.inputItems)")
var audioFound :Bool = false
for inputItem: AnyObject in self.extensionContext!.inputItems {
let extensionItem = inputItem as! NSExtensionItem
for attachment: AnyObject in extensionItem.attachments! {
print("attachment = \(attachment)")
let itemProvider = attachment as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMPEG4Audio as String)
//|| itemProvider.hasItemConformingToTypeIdentifier(kUTTypeMP3 as String)
// the audio format(s) we expect to receive and that we can handle
{
itemProvider.loadItemForTypeIdentifier(kUTTypeMPEG4Audio as String,
options: nil, completionHandler: { (audioURL, error) in
NSOperationQueue.mainQueue().addOperationWithBlock {
if let audioURL = audioURL as? NSURL {
// in our sample code we just present and play the audio in our app extension
let theAVPlayer :AVPlayer = AVPlayer(URL: audioURL)
let theAVPlayerViewController :AVPlayerViewController = AVPlayerViewController()
theAVPlayerViewController.player = theAVPlayer
self.presentViewController(theAVPlayerViewController, animated: true) {
theAVPlayerViewController.player!.play()
}
}
}
})
audioFound = true
break
}
}
if (audioFound) {
break // we only handle one audio recording at a time, so stop looking for more
}
}
}
6c. Build and run as in the previous step. This time, tapping on your action extension will bring up the same view controller as before but now overlaid with the AVPlayerViewController instance containing and playing your audio recording. Also, the two print() statements I've inserted in the code should give output that looks something like the following:
self.extensionContext!.inputItems = [<NSExtensionItem: 0x127d54790> - userInfo: {
NSExtensionItemAttachmentsKey = (
"<NSItemProvider: 0x127d533c0> {types = (\n \"public.file-url\",\n \"com.apple.m4a-audio\"\n)}"
);
}]
attachment = <NSItemProvider: 0x127d533c0> {types = (
"public.file-url",
"com.apple.m4a-audio"
)}
Make the following changes to the action extension's Info.plist file:
7a. The Bundle display name defaults to whatever name you gave your action extension (MyActionExtension in this example). You might wish to change this to Save to MyApp. (By way of comparison, Dropbox uses Save to Dropbox.)
7b. Insert a line for the key CFBundleIconFile and set it to Type String (2nd column), and set its value to MyActionIcon or some such. You will then need to provide the corresponding 5 icon files. In our example, these would be: MyActionIcon.png, MyActionIcon#2x.png, MyActionIcon#3x.png, MyActionIcon~ipad.png, and MyActionIcon#2x~ipad.png. (These icons should be 60x60 points for iphone and 76x76 points for ipad. Only the alpha channel is used to determine which pixels are gray, the RGB channels are ignored.) Add these icon files to your app extension's bundle, NOT the containing app's bundle.
7c. At some point you will need to set the value for the key NSExtension > NSExtensionAttributes > NSExtensionActivationRule to something other than TRUEPREDICATE. If you want your action extension to only be activated for audio files, and not for video files, pdf files, etc., this is where you would specify such a predicate.
The above takes care of getting the audio recording from Voice Memos to your app extension. Below is an outline of how to get the audio recording from the app extension to the containing app. (I'll flesh it out later, time permitting.) This blog post ( http://www.theappguruz.com/blog/ios8-app-groups ) might also be useful.
Set up your app to use App Groups. Open the Project Navigator (Cmd-0) and click on the first line to show your project and targets. Select the target for your app, click on the "Capabilities" tab, look for the App Groups capability, and set its switch to "On". Once the various entitlements have been added, click on the "+" sign to add your App Group, giving it a name like group.com.mycompany.myapp.sharedcontainer. (It must begin with group. and should probably use some form of reverse-DNS naming.)
Repeat the above for your app extension's target, giving it the same name as above (group.com.mycompany.myapp.sharedcontainer).
Now you can write the url of the audio recording to the app group's shared container from the app extension side. In ActionViewController.swift, replace the code fragment that instantiates and presents the AVPlayerViewController with the following:
let sharedContainerDefaults = NSUserDefaults.init(suiteName:
"group.com.mycompany.myapp.sharedcontainer") // must match the name chosen above
sharedContainerDefaults?.setURL(audioURL, forKey: "SharedAudioURLKey")
sharedContainerDefaults?.synchronize()
Similarly, you can read the url of the audio recording from the containing app's side using something like this:
let sharedContainerDefaults = NSUserDefaults.init(suiteName:
"group.com.mycompany.myapp.sharedcontainer") // must match the name chosen above
let audioURL :NSURL? = sharedContainerDefaults?.URLForKey("SharedAudioURLKey")
From here, you can copy the audio file into your app's sandbox, e.g., your app's Documents directory or your app's NSTemporaryDiretory(). Read this blog post ( http://www.atomicbird.com/blog/sharing-with-app-extensions ) for ideas on how to do this in a coordinated fashion using NSFileCoordinator.
References:
Creating an App Extension
Sharing Data with Your Containing App
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.
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.