iOS - Swift 3 Share Extension Preview Image - ios

I'm currently building a share extension that accepts URLs. As part of this I've customised my share screen as outlined in a previous question to create a full screen view controller. This is all working fine. However in the default share composer view I noticed there was a preview image of the web page. I'm trying to access this in my extension but I can't seem to get hold of it.
Specifically I've been trying to use the method
loadPreviewImage
https://developer.apple.com/reference/foundation/nsitemprovider/1403925-loadpreviewimage
You will note in the docs that this says the following for the completion handler
completion​Handler
A completion handler block to execute with the results. The first parameter of this block must be a parameter of type NSData, NSURL, UIImage (in iOS), or NSImage (in macOS) for receiving the image data. For more information about implementing the block, see Completion​Handler.
However if I try to set this as a UIImage in my completion block I get an error of
Cannot convert value of type '(UIImage, _) -> ()' to expected argument
type 'NSItemProvider.CompletionHandler!'
example code where itemProvider is confirmed to be an instance of NSItemProvider via guard statements
itemProvider.loadPreviewImage(options: nil) { (image: UIImage, error) in
}
The docs for the completion Handler say to set this to what type you want and it will attempt to coerce the data to the type you specify. Has anyone seen this before? I'm not sure what to do here as I can't see what I'm doing wrong.
https://developer.apple.com/reference/foundation/nsitemprovider/completionhandler
If all else fails I'll look at using some Javascript to get an image from the dom but I would have liked the preview image that Apple seemed to provide

I don't know why the code in
itemProvider.loadPreviewImage(options: nil) { (image: UIImage, error) in
}
not called when Post button tapped.
My round way is saving the preview image in method
override func configurationItems() -> [Any]! {
}
as
let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
let itemProvider = inputItem.attachments![0] as! NSItemProvider
if (itemProvider.hasItemConformingToTypeIdentifier("public.url")) {
itemProvider.loadPreviewImage(options: nil, completionHandler: { (item, error) in // 画像を取得する
if let image = item as? UIImage {
if let data = UIImagePNGRepresentation(image) {
self.photoNSURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("preview.png") as NSURL!
do {
try data.write(to: self.photoNSURL as URL, options: .atomic)
} catch {
print("\(#file)#\(#function)(\(#line)): error: \(error.localizedDescription)")
}
}
}
})
}

Related

Post to Instagram by opening Instagram app – iOS, Swift

I have an Instagram scheduling app and I am trying to open this (see image below) in Swift 5.x. The goal is simple: save Image to Firebase, once it is time to post, notification!, user clicks on the notification and this (image below) opens up with the appropriate image/video to post. Everything works except for opening Instagram with the appropriate photo/video. I have tried this:
func postToInstagram(image: URL) {
let videoFileUrl: URL = image
var localId: String?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoFileUrl)
localId = request?.placeholderForCreatedAsset?.localIdentifier
}, completionHandler: { success, error in
// completion handler is called on an arbitrary thread
// but since you (most likely) will perform some UI stuff
// you better move everything to the main thread.
DispatchQueue.main.async {
guard error == nil else {
// handle error
print(error)
return
}
guard let localId = localId else {
// highly unlikely that it'll be nil,
// but you should handle this error just in case
return
}
let url = URL(string: "instagram://library?LocalIdentifier=\(localId)")!
guard UIApplication.shared.canOpenURL(url) else {
// handle this error
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
})
}
and this:
func postToInstagram(image: URL, igURL: String) {
let urlStr: String = "instagram://app"
let url = URL(string: igURL)
if UIApplication.shared.canOpenURL(url!) {
print("can open")
UIApplication.shared.open(url!, options: [:], completionHandler: nil)
}
}
To no avail. The latter code works, but only opens the Instagram app itself, which is fine, but I would like to open the View in the image below rather than Instagram's home screen. I also tried changing the URL to "instagram://share" and this works but goes to publish a regular post, whereas I want the user to decide what they want to do with their image.
This is where I want to go:
Note: For everyone who will be telling me this and whoever will wonder: Yes, my URL schemes (LSApplicationQueriesSchemes) are fine. And, just to clarify, I need to fetch the image/video from Firebase before posting it.

Swift 3 DispatchQueue.global (download images from Firebase and UI frozen)

I have a problema with this method:
func DownloadImages(uid: String, indice: Int) {
DispatchQueue.global(qos: .background).async {
let refBBDD = FIRDatabase.database().reference().child("users").child(uid)
refBBDD.observeSingleEvent(of: .value, with: { snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let profileImageUrl = snapshotValue?.value(forKey: "profileImageUrl") as! String
let storage = FIRStorage.storage()
var reference: FIRStorageReference!
if(profileImageUrl == "") {
return
}
print("before")
reference = storage.reference(forURL: profileImageUrl)
reference.downloadURL { (url, error) in
let data = NSData(contentsOf: url!)
let image = UIImage(data: data! as Data)
print("image yet dowload ")
self.citas[indice].image = image
DispatchQueue.main.async(execute: { () -> Void in
self.tableView.reloadRows(at: [IndexPath(row: indice, section: 0)], with: .none)
//self.tableView.reloadData()
print("image loaded")
})
}
print("after")
})
}
}
I want to download images in background mode. I want follow using app, but the UI has frozen until methods not entry in reloadRows.
Is it possible run in true background mode and can i follow using the app??
Trace program:
before
after
before
after
...
before
after
before
after
image yet dowload --> here start UI frozen
image loaded
image yet dowload
image yet dowload
...
image yet dowload
image yet dowload
image loaded
image loaded
...
image loaded
image loaded
image yet dowload ------> here UI is not frozen
image loaded
The problem is caused by this line: let data = NSData(contentsOf: url!).
That constructor of NSData should only be used to read the contents of a local URL (a file path on the iOS device), since it is a synchronous method and hence if you call it to download a file from a URL, it will be blocking the UI for a long time.
I have never used Firebase, but looking at the documentation, it seems to me that you are using the wrong method to download that file. You should be using func getData(maxSize size: Int64, completion: #escaping (Data?, Error?) -> Void) -> StorageDownloadTask instead of func downloadURL(completion: #escaping (URL?, Error?) -> Void), since as the documentation states, the latter only "retrieves a long lived download URL with a revokable token", but doesn't download the contents of the URL itself.
Moreover, you shouldn't be force unwrapping values in the completion handler of a network request. Network requests can often fail for reasons other than a programming error, but if you don't handle those errors gracefully, your program will crash.
Your problem is in this line let data = NSData(contentsOf: url!) and let's now see what apple says about this method below
Don't use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
So it clearly states that this method will block your User Interface.

Open URL from UIButton in CollectionViewCell

I have a UIButton in my UICollectionViewCell and it's getting data from JSON. Now I need to open a URL from each button (each button have a different url that also comes from JSON).
I managed to open the URL with:
let weburl = "http://example.com"
UIApplication.shared.openURL(URL(string: weburl)!)
But now I need to kinda pass an url to each button. Any ideas of how can i achieve this?
You can have an array of urls:
let urls = [url1, url2, ...]
And then assign the tag property of each button to the index of its corresponding url. Now you can easily manage what you want:
#IBAction func handleTouch(_ sender: UIButton) {
// assumes that the buttons' tags start at 0, which isn't a good idea.
// see #rmaddy comment bellow
let url = urls[sender.tag]
// use the version of the open method shown bellow because the other one becomes deprecated in iOS 10
UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
}
EDIT
Other solution would be to just store the url in the cell itself, and in the button handler open the url corresponding to its cell.
FYI openURL is deprecated in iOS 10. I suggest the following if you need to support older versions of ios:
let url = URL(string: "alexa://")!
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:], completionHandler: {
(success) in
guard success else {
//Error here
}
//Success here
})
} else {
if let success = UIApplication.shared.openURL(url) {
//Success here
} else {
//Error here
}
}
Otherwise just use UIApplication.shared.open. Also I would add a URL field to the data model you are passing to your tableViewCell and just look up the URL from the model.

How to Upload video files in background?

I am doing a app in which i need to upload videos taken from iPhone Camera and upload it to a server. When the app is in foreground the video gets uploaded but i don't know how to do it in background when the app is inactive. I used AFNetworking to do the multipart data upload. Here is the code i tried
var task:NSURLSessionUploadTask!
let FILEPICKER_BASE_URL = "My server Url"
var isUploading : Bool = false
var videoPath : String!
func upload(dictMain: NSMutableDictionary)
{
isUploading = true
let uuid = NSUUID().UUIDString + "-" + (getUserId().description)
let request = AFHTTPRequestSerializer().multipartFormRequestWithMethod("POST", URLString: FILEPICKER_BASE_URL, parameters: nil, constructingBodyWithBlock: { (fromData) in
do {
try fromData.appendPartWithFileURL(NSURL(fileURLWithPath: videoPath) , name: "fileUpload", fileName: uuid, mimeType: "video/quicktime")
}catch{
print(error)
}
}, error: nil)
let manager:AFURLSessionManager = AFURLSessionManager(sessionConfiguration: NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("Your video is uploading"));
task = manager.uploadTaskWithStreamedRequest(request, progress: nil , completionHandler: { (response, responseObject, error) in
NSLog("Resposne Object: \(responseObject)")
post(dictMain,response: responseObject!)
})
task.resume()
}
Also I don't know how to know my upload progress. I tried to use the following block in the progress parameter
{ (progress) in
print("\((progress))")
}
But it does not work the complier shows error
Cannot convert value of type '(_) -> _' to expected argument type 'AutoreleasingUnsafeMutablePointer<NSProgress?>' (aka 'AutoreleasingUnsafeMutablePointer<Optional<NSProgress>>')
Could any one share a snippet that really works. As i googled there are very very few basic tutorials on NSURLSession.uploadTaskWithStreamedRequest and all of them i tried didn't work on swift 2.2
I am using Xcode 7.3 Swift 2.2
Points I Know:
Background upload works only with File paths and not NSData. so i took the video url path from the UIImagePickerController function func video(videoPath: NSString, didFinishSavingWithError error: NSError?, contextInfo info: AnyObject)
To do background file transfer i have to enable them in the Target->Capabilities -> Background Modes -> Background fetch
Must set the Session manager with NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(""). But i don't know why and where that identifier is being used.
I know this is a long post. But if it is answered this will surely be helpful to many developers.
I fixed it by calling the upload function inside a DispatchQueue Background queue
DispatchQueue.global(qos: .background).async { upload() }

Import PDF into own App with iOS Action Extension

I'm looking for a possibility to import a PDF in order to do some further tasks with it, just like described in this Question: Importing PDF files to app
After two days of looking around in the inter webs I found that an action extension might be the solution, this is how far I got:
override func viewDidLoad() {
super.viewDidLoad()
let fileItem = self.extensionContext!.inputItems.first as! NSExtensionItem
let textItemProvider = fileItem.attachments!.first as! NSItemProvider
let identifier = kUTTypePDF as String
if textItemProvider.hasItemConformingToTypeIdentifier(identifier) {
textItemProvider.loadItemForTypeIdentifier(identifier, options: nil, completionHandler: handleCompletion)
}
}
func handleCompletion(pdfFile: NSSecureCoding?, error: NSError!) {
print("PDF loaded - What to do now?")
}
The completion handler is called properly so I assume the PDF is loaded - but then I don't now how to proceed. If the action extension only handles images or text it could easily be downcasted, but the only way to work with files I know is with path names - which I do not have and don't know how to obtain. Plus, I'm pretty sure Sandboxing is also part of the party.
I guess I only need a push in the right direction which Class or Protocol could be suitable for my need - any suggestions highly appreciated.
For anyone else looking for an answer - I found out by myself, and it's embarrassingly easy:
func handleCompletion(fileURL: NSSecureCoding?, error: NSError!) {
if let fileURL = fileURL as? NSURL {
let newFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory().stringByAppendingString("test.pdf"))
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.copyItemAtURL(fileURL, toURL: newFileURL)
// Do further stuff
}
catch {
print(error)
}
}
}

Resources