Before iOS 11 came out I created a share extension to my social media app. It worked perfectly fine. Once iOS 11 came out, the share extension quit working. I searched and debugged the extension until I found the source of the problem. When looping through the attachments inside the extensionContext.inputItems[0].attachments, none of the attachments has an item conforming to kUTTypeImage. So none of my code was running from that point on. I also had another strange outcome yesterday. This is part of my code inside the didSelectPost function.
guard let content = extensionContext?.inputItems[0] as? NSExtensionItem else { return }
guard let contentAttachments = content.attachments as? [NSItemProvider] else { return }
let skyName = self.textView.text
for attachment in contentAttachments {
if attachment.hasItemConformingToTypeIdentifier(imageType) {
attachment.loadItem(forTypeIdentifier: imageType, options: nil) { (data, error) in
guard error == nil, let url = data as? NSURL else { return }
self.imageFromAsset(url: url as URL)
if !self.selectedType.isEmpty {
do {
let imageData = try Data(contentsOf: url as URL)
self.skyImage = UIImage(data: imageData)
self.saveSkyImage()
guard let skyOriginalImageURL = self.skyOriginalImageURL else { return }
guard let skyImageURL = self.skyImageURL else { return }
let newSky = Sky(name: skyName ?? "Another Sky",
type: self.selectedType,
date: self.date,
location: self.location,
picture: CKAsset(fileURL: skyImageURL),
likes: 0, flags: 0,
likedBy: [CKReference](), flaggedBy: [CKReference](),
originalImage: CKReference(record: CKRecord(recordType: "SkyImage"), action: .none))
let newSkyImage = SkyImageFullResolution(picture: CKAsset(fileURL: skyOriginalImageURL))
self.saveSky(sky: newSky, skyImage: newSkyImage)
} catch {
print(error.localizedDescription)
self.closePostWindow()
}
}
}
}
}
defer {
closePostWindow()
}
I don't have a direct answer to your problem but recently in iOS 11 I have resolved a problem to display a share extension involving PDF files.
My problem was that my expected type identifiers were not found among the attachments.
NSItemProvider has an instance property registeredTypeIdentifiers to show you the type identifiers that can be found when your extension is activated.
That's what I do :
1) I use TRUEPREDICATE as NSExtensionActivationRule to force display my share extension in the context of my interest.
2) After you select your share extension, your extension code will be triggered.
Then you print all the type registeredTypeIdentifiers of each attachment, by looping through your contentAttachments.
Once you have identified all the identifiers, you will be able to find a solution to your problem.
Related
Inside my app I have a CustomShareViewController and with that the workflow should be like this:
User is in Safari on some website and looks up any kind of product
User taps on "share" and selects my App
I parse data from the current URL and use it/store it inside my app
Problem:
How do I get the data in Swift? I managed to get the current URL and with that the HTML like this:
#objc func actionButtonTapped(){
var html: String?
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if (url as? URL) != nil {
html = (self.getHTMLfromURL(url: url as? URL))
self.doStuff(html: html)
}
}
}
}
But is there any way to get the data that Apple provides? (image and title) ?
That's the data from Apple I mean:
With #Filip's hint I found this Git-Repo:
URLEmbeddedView
With this I can simply get the data like this:
let urlString = ...
OpenGraphDataDownloader.shared.fetchOGData(urlString: urlString) { result in
switch result {
case let .success(data, isExpired):
// do something
case let .failure(error, isExpired):
// do something
}
}
I am attempting to load photos located on external storage (SD card) using core graphics in iOS 13 (beta). The code below works fine when the files are on the device. When the files are on external storage however it fails returning nil and I don't know why.
I believe I am using the correct security scoping.
I loaded the file URLs from a security scoped folder url as per Providing Access to Directories
guard folderUrl.startAccessingSecurityScopedResource() else {
return nil
}
defer { folderUrl.stopAccessingSecurityScopedResource() }
guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, options) else {
throw Error.failedToOpenImage(message: "Failed to open image at \(imageURL)")
}
So... for my own project, where I ran into the same issue, I now have the following function to give me a thumbnail, going from elegant and quick to brute force.
static func thumbnailForImage(at url: URL, completion: (Result<UIImage, Error>) -> Void) {
let shouldStopAccessing = url.startAccessingSecurityScopedResource()
defer { if shouldStopAccessing { url.stopAccessingSecurityScopedResource() } }
let coordinator = NSFileCoordinator()
var error: NSError?
coordinator.coordinate(readingItemAt: url, options: .withoutChanges, error: &error) { url in
var thumbnailImage: UIImage?
var storedError: NSError?
var imageSource: CGImageSource?
print("Strategy 1: Via URL resource key")
do {
let resourceKeys = Set([URLResourceKey.thumbnailDictionaryKey])
let resources = try url.resourceValues(forKeys: resourceKeys)
if let dict = resources.thumbnailDictionary, let resource = dict[URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey] {
thumbnailImage = resource
} else {
throw "No thumbnail dictionary"
}
} catch let error {
storedError = error as NSError
}
let options = [kCGImageSourceCreateThumbnailFromImageIfAbsent: true, kCGImageSourceShouldAllowFloat: true, kCGImageSourceCreateThumbnailWithTransform: true]
if thumbnailImage == nil {
print("Strategy 2: Via CGImageSourceCreateWithURL")
imageSource = CGImageSourceCreateWithURL(url as CFURL, options as CFDictionary)
}
if thumbnailImage == nil && imageSource == nil {
print("Strategy 3: Via CGImageSourceCreateWithData")
let data = try? Data.init(contentsOf: url)
if let data = data {
imageSource = CGImageSourceCreateWithData(data as CFData, options as CFDictionary)
}
}
if let imageSource = imageSource, thumbnailImage == nil {
print("Attempting thumbnail creation from source created in strategy 2 or 3")
if let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
thumbnailImage = UIImage(cgImage: image)
}
}
if let thumbnailImage = thumbnailImage {
print("Success")
completion(.success(thumbnailImage))
} else {
print("Failure")
if let error = storedError { completion(.failure(error)) }
else { completion(.failure("Everything just fails...")) }
}
}
if let error = error { completion(.failure(error)) }
}
Basically it works by trying to get a thumbnail via the URL resources first. This is the quickest and nicest way, of it works. If that fails, I try CGImageSourceCreateWithURL. That works most of the time, except on remote storage. I suspect that's still a bug and submitted a feedback ticket to apple for this. I suggest you do the same. Last attempt, just try to read the entire file using NSData and creating an image source via CGImageSourceCreateWithData...
So far, if it's an image file I, this seems to produce a thumbnail most of the time. It can be quite slow though, having to read the entire file.
I see different behavior on iOS 11 vs 12.
On iOS 11 - I get the filepath of files shared in completion handler.
On iOS 12 - I get a URL domain error. But if i handle it based on the type (eg: UIImage), then I get the file content.
Is this behaviour only on simulator or on device as well ?
Do we need to handle this per iOS version ?
Yes you will get both thing (file path or data) on device also. You did not need to add any check on iOS version.
Please flow my code. It is in swift but you can understand it.
func share() {
let inputItem = extensionContext!.inputItems.first! as! NSExtensionItem
let attachment = inputItem.attachments!.first as! NSItemProvider
if attachment.hasItemConformingToTypeIdentifier( kUTTypeImage as String) {
attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: [:]) { (data, error) in
var image: UIImage?
if let someURl = data as? URL {
image = UIImage(contentsOfFile: someURl.path)
}else if let someImage = data as? UIImage {
image = someImage
}
if let someImage = image {
guard let compressedImagePath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("shareImage.jpg", isDirectory: false) else {
return
}
let compressedImageData = UIImageJPEGRepresentation(someImage, 1)
guard (try? compressedImageData?.write(to: compressedImagePath)) != nil else {
return
}
}else{
print("bad share data")
}
}
}
}
Below is code from a share extension for iOS part of a flutter app.
I have literally hours of experience working with XCode so please pardon my noob mistakes.
This is the viewDidLoad method from my SLComposeServiceViewController implementation
override func viewDidLoad() {
super.viewDidLoad();
let content = self.extensionContext!.inputItems[0] as! NSExtensionItem;
let contentTypeImage = kUTTypeImage as String;
let contentTypeText = kUTTypeText as String;
for attachment in content.attachments as! [NSItemProvider] {
if attachment.hasItemConformingToTypeIdentifier(contentTypeImage) {
// Verify that the content type is image.
attachment.loadItem(forTypeIdentifier: contentTypeImage, options: nil) {
data, error in if error == nil {
let url = data as! NSURL
if let imageData = NSData(contentsOf: url as URL) {
let image = UIImage(data: imageData as Data)
// Do something with the image.
if(thingGoWrong) {
//show error message.
self.showErrorMessage(text: "Failed to read image.")
return
}
if (finalOperationSucceeded) {
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
}
}
} else {
// Display error dialog for not supported content. Though we should never receive any such thing.
self.showErrorMessage(text: error?.localizedDescription)
}
}
}
if attachment.hasItemConformingToTypeIdentifier(contentTypeText) {
attachment.loadItem(forTypeIdentifier: contentTypeText, options: nil) {
data, error in if error == nil {
let text = data as! String
// do something with the text
if (textOpSucceeded) {
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
}
}
}
}
}
}
The text portion of the share extension works as expected, but if I try and send an image to the app. I get this response.
Note:
The same code runs fine when testing on iOS 11.4
I've tested on iPhone 6S simulator iOS 12.0 where it failed.
Have you tried converting the data directly to an UIImage?
I am trying to get the imageURL the ios share extension uses for the thumbnail generated in the action sheet.
I am retrieving the URL fine but cannot seem to figure out how to get the imageURL.
Here is how I get the normal URL,
if let item = extensionContext?.inputItems.first as? NSExtensionItem {
if let itemProvider = item.attachments?.first as? NSItemProvider {
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeItem as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeItem as String, options: nil, completionHandler: { (url, error) -> Void in
if let shareURL = url as? NSURL {
let components = URLComponents(url:shareURL as URL, resolvingAgainstBaseURL: true)
if let host = components?.host { self.shareTitle = host }
self.shareURL = shareURL.absoluteString!
self.POSTShareData(completion: nil)
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
}
}
I have tried to changing the typeIdentifier to kUTTypeImage to no avail. I have my info.plist set to NSExtensionActivationRule to TRUEPREDICATE to see what I can retrieve. I am thinking maybe I have to be more explicit in the .plist ??
I am targeting iOS 9.3
A workaround is that you can store that URL link in common UserDefaults
eg:- let defaults = UserDefaults(suiteName: "group.com.common.Notification")
Then access it in-app or extension