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?
Related
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")
}
}
}
}
I am currently learning about how to upload/download images to/from Firebase. However, I am encountering two problems:
When I upload the image to my Firebase console it says it's type is "application/octet-stream" which is not exactly what I want (I uploaded a jpeg file)
Here's the code:
#IBAction func completeSignUpButton(_ sender: UIButton) {
let userProfileImageRef = storage.child("userProfileImage")
//UPloading the photo into firebase
let data = Data()
//uploading user profile picture
if self.userInitialPhoto.image != nil {
if let uploadData = UIImagePNGRepresentation(self.userInitialPhoto.image!) {
userProfileImageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error?.localizedDescription)
return
} else {
print("Image upload success!")
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "completeRegistrationSegue", sender: self)
}
})
}
} else {
print("You need to pick a picture!")
}
}
So this might be the problem that's causing my second problem:
When I try to load the image from Firebase to my UIImage in my app, it says Object Optional(\"userID\")/userProfileImage does not exist."
Here's my code for that:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
docRef = Firestore.firestore().collection("user Status").document("Current Status")
self.userStatusTableView.reloadData()
let referencePath = Storage.storage().reference(withPath: "\(userID)/userProfileImage")
let userProfileImageRef = Storage.storage().reference().child("\(userID)/userProfileImage")
userProfileImageRef.getData(maxSize: 1 * 1024 * 1024) { (data, error) in
if error != nil {
print("There's an eror downloading the image\(error?.localizedDescription)")
} else {
self.userProfileImage.image = UIImage(data: data!)
}
}
}
I am wondering what I should do to fix these issues.
Thanks for your help in advance and happy new year to you all.
while uploading the image to firebase you passed metadata as nil. you need to pass image metadata there
let metaData = FIRStorageMetadata()
metaData.contentType = "image/jpg"
and in upload complitionBlock, you can get URL for that image using
if error == nil , let meta = meta {
if let url = meta.downloadURL()?.absoluteString {
//use this url to access your image
}
}
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.
I'm trying to make an application that uses a web service to get some data from a directory, but I also need to save the data into the device, include images, to do so I'm using Alamofire and AlamofireImage framework for consuming the webservice. I save the generated objects in a database with Realm framework and for images I save the UIImage into a file.
Basically, the ViewController has a tableView which displays de data, but it seems laggy because of the images writing into files.
This is my writing function:
func saveImage(_ image: UIImage) {
if let data = UIImagePNGRepresentation(image) {
let name = "images/person_directory_\(id).png"
let docsDir = getDocumentsDirectory()
let filename = docsDir.appendingPathComponent(name)
let fm = FileManager.default
if !fm.fileExists(atPath: docsDir.appendingPathComponent("images").path) {
do {
try fm.createDirectory(at: docsDir.appendingPathComponent("images"), withIntermediateDirectories: true, attributes: nil)
try data.write(to: filename)
try! realm?.write {
self.imageLocal = name
}
}
catch {
print(error)
}
}
else {
do {
try data.write(to: filename, options: .atomic)
try! realm?.write {
self.imageLocal = name
}
}
catch {
print(error)
}
}
}
}
I call this function when Alamofire downloads the image
if person.imageLocal != nil, let image = person.loadLocalImage() {
print("Load form disk: \(person.imageLocal)")
cell.imgProfile.image = image
}
else if !(person.image?.isEmpty)! {
Alamofire.request(person.image!).responseImage(completionHandler: { (response) in
if response.result.isSuccess {
if let image = response.result.value {
person.saveImage(image)
cell.imgProfile.image = image
print("Downloaded: \(person.imageLocal)")
}
}
})
}
But the tableView looks laggy when scrolled and I was trying to make the writing operation into a diferent thread so it could get written without affecting the application performance by using DispatchQeue
DispatchQueue.global(qos: .background).async {
do {
try data.write(to: filename)
}
catch {
print(error)
}
}
But even so the applications stills laggy.
UPDATE:
I tryed this as Rob suggested:
func saveImage(_ image: UIImage) {
if let data = UIImagePNGRepresentation(image) {
let name = "images/person_directory_\(id).png"
do {
try realm?.write {
self.imageLocal = name
}
}
catch {
print("Realm error")
}
DispatchQueue.global().async {
let docsDir = self.getDocumentsDirectory()
let filename = docsDir.appendingPathComponent(name)
let fm = FileManager.default
if !fm.fileExists(atPath: docsDir.appendingPathComponent("images").path) {
do {
try fm.createDirectory(at: docsDir.appendingPathComponent("images"), withIntermediateDirectories: true, attributes: nil)
}
catch {
print(error)
}
}
do {
try data.write(to: filename)
}
catch {
print(error)
}
}
}
}
I can't dispatch the Realm writing becase Realm doesn't suport multithreading.
It stills scrolling laggy but not as much as the first time.
So the proper answer as #Rob gave it is
DispatchQueue.global().async {
do {
try data.write(to: filename)
}
catch {
print(error)
}
}
But just as important is to never use a reference to a UITableViewCell from an asynchronous call (again credit #Rob). For example, setting a cell value from the asynchronous parts of your code.
cell.imgProfile.image = image
UITableViewCells are re-used, so you don't know that the original cell is still used at the same index. For a test, scroll your list really fast so your images get loaded, if you see images appear in the wrong cell, its the re-use problem.
So from an asynchronous callback you need to figure out if the cell index for the new image is visible, go get the current cell for that index to set it's image. If the index isn't visible, then store/cache the image until it's index is scrolled into view. At that point it's cell will be created with UITableView.cellForRowAtIndexPath, and you can set the image there.