2

My goal (besides learning how to write an iOS app extension) is to allow a user to share an image using the share button from a variety of apps including Photos and automatically rename them. Lastly then I want to save the image to the "documents" folder of the app for further use.

I'm having some problems trying to get the actual didSelectPost portion working since it seems that, unlike Objective-C examples I've seen, the loadItem operation returns a NSURL instead of an UIImage. When attempting to copy the NSUrl to my apps documents folder I get an error:

Error Domain=NSCocoaErrorDomain Code=260 "The file “IMG_0941.JPG” couldn’t be opened because there is no such file." UserInfo={NSFilePath=file:///var/mobile/Media/PhotoData/OutgoingTemp/B79263E5-9512-4317-9C5D-817D7EBEFA9A/RenderedPhoto/IMG_0941.JPG, NSUnderlyingError=0x283f89080 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

This happens when I push the share button on a photo in the "photos" app, tap my extension and then press the "post" button.

I get the same error regardless if it's running in a simulator or real device.

Here's my hacked together progress so far:

    override func didSelectPost() {
        // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
        let inputItem = extensionContext?.inputItems.first as! NSExtensionItem
        let attachment = inputItem.attachments!.first!
        if attachment.hasItemConformingToTypeIdentifier(kUTTypeJPEG as String) {
            attachment.loadItem(forTypeIdentifier: kUTTypeJPEG as String, options: nil) { data, error in
                var image: UIImage?
                if let someUrl = data as? NSURL {
                    do {
                      // a ends up being nil in both of these cases
                      let a = NSData(contentsOfFile: someUrl.absoluteString!)
                      image = UIImage(data: a as! Data)
                      // let a = try Data(contentsOf: someUrl)
                      // image = UIImage(contentsOfFile: someUrl.absoluteString)
                    } catch {
                        print(error)
                    }
                } 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("theimage.jpg", isDirectory: false) else {
                        return
                    }

                    let compressedImageData = someImage.jpegData(compressionQuality: 1)
                    guard (try? compressedImageData?.write(to: compressedImagePath)) != nil else {
                        return
                    }
                } else {
                    print("Bad share data")
                }
            }
        }
        // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
        self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }

Notice I'm casting the img variable as an NSURL. I've tried to cast it as a UIImage but that throws an exception.

I have some other things I'd like to do to the image, like read it's EXIF data but for now this is what I have. Any suggestions would be great as I'm really struggling to wrap my head around and learn this environment.

Similar but unsuccessful posts I've tried, notice they are all Objective-C:

iOS Share Extension issue when sharing images from Photo library

Share image using share extension in ios8

How to add my app to the share sheet action

[edit] Matched the layout of one of the better answers, still with no luck.

Chris
  • 1,418
  • 2
  • 16
  • 34

3 Answers3

2

I have review your code and there is some mistake in the code. I have fixed it . Replace your code with 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")
            }
        }

    }
}
Rishi Indolia
  • 176
  • 2
  • 6
  • Thanks! I'll give this a look later. The code is clear and I can generally see what's going on. I think when I have the IDE open it'll make even more sense. – Chris Feb 26 '19 at 15:01
  • Unfortunately this didn't work. The `someUrl` always has a direct path to the image which is good but the `image` always ends up nil. I tried `NSURL` with `absoluteString` like I had as well. None of them seem to work. I'm picking images from the photos app. – Chris Feb 27 '19 at 03:27
  • @Chris Did you ever get this to work? The exact same happens to me, `image` always ends up nil. – einarnot Sep 02 '20 at 09:41
1

I have the same issue. The solution I was able to implement:

  1. Get URL to image. This URL is useless because I got 260 error when try to load image using this URL. Interesting that this comes after some recent updates because it works before
  2. Get file name with extension from this URL
  3. Iterate over all images in user's photo library and find the image name == name from ULR
  4. Extract the image data
- (void)didSelectPost {
    
    for (NSItemProvider* itemProvider in ((NSExtensionItem*)self.extensionContext.inputItems[0]).attachments ) {
        // get type of file extention (jpeg, file, url, png ...)
        NSArray *registeredTypeIdentifiers = itemProvider.registeredTypeIdentifiers;
        if ([itemProvider hasItemConformingToTypeIdentifier:registeredTypeIdentifiers.firstObject]) {
           [itemProvider loadItemForTypeIdentifier:registeredTypeIdentifiers.firstObject options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
                
                NSData *imgData;
                NSString* imgPath = ((NSURL*) item).absoluteString;
                if(imgPath == nil)
                    imgPath = [NSString stringWithFormat:@"%@", item];
                
                NSCharacterSet* set = [NSCharacterSet URLHostAllowedCharacterSet];
                NSString* imgPathEscaped = [imgPath stringByAddingPercentEncodingWithAllowedCharacters:set];
                
                NSString* fileName = [imgPath lastPathComponent];
                
                NSError* error2 = nil;
//try load from file path
                __block NSData* data2 = [NSData dataWithContentsOfFile:imgPath options: NSDataReadingUncached error:&error2];
                if(data2 == nil) //try load as URL
                    data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgPath] options: NSDataReadingUncached error:&error2];
               
               if(data2 == nil) //all failed so try hacky way 
               {
                   NSString* searchFilename = [fileName lowercaseString];
                   
                   PHFetchResult *results = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
                   
                   [results enumerateObjectsUsingBlock:^(PHAsset *obj, NSUInteger idx, BOOL * _Nonnull stop) {
                       
                       NSArray* resources = [PHAssetResource assetResourcesForAsset:obj];
                       NSString* fileName2 = [NSString stringWithFormat:@"%@", ((PHAssetResource*)resources[0]).originalFilename].lowercaseString;
                       
                       if ([fileName2 isEqual:searchFilename])
                       {
                           NSLog(@"found %@", fileName2);
                           
                           PHImageManager* mgr = [PHImageManager defaultManager];
                           PHImageRequestOptions * options = [PHImageRequestOptions alloc];
                           options.synchronous = YES;
                           [mgr requestImageDataForAsset:obj options:options resultHandler:^(NSData * _Nullable imageData33, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info)
                           {
                               //imageData33 is your image
                               data2 = imageData33;
                           }];
                       }
                   }];
                   
               }
            }];
        }
    }
    
    // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
    [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
}
Sergey
  • 43
  • 6
0
func getPhotofolder() -> String{
    let fileManager = FileManager.default
    let paths = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("hsafetyPhoto")
    if !fileManager.fileExists(atPath: paths){
        try! fileManager.createDirectory(atPath: paths, withIntermediateDirectories: true, attributes: nil)

    }else{
        print("Already dictionary created.")
    }

    return  paths
}


func saveImageDocumentDirectory(photo : UIImage, photoUrl : String) -> Bool{
    let fileManager = FileManager.default
    let paths =  Utility.getPhotofolder().stringByAppendingPathComponent(pathComponent: photoUrl)
    print("image's path \(paths)")
    if !fileManager.fileExists(atPath: paths){
        print("file already exits \(paths)")
        let imageData = UIImageJPEGRepresentation(photo, 0.5)
        fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
         if !fileManager.fileExists(atPath: paths){
            return false
         }else{
            return true
        }
     }else{
        print(paths)
        let imageData = UIImageJPEGRepresentation(photo, 0.5)
        fileManager.createFile(atPath: paths as String, contents: imageData, attributes: nil)
        if !fileManager.fileExists(atPath: paths){
            return false
        }else{
            return true
        }
    }
}

 func showimage(image_name : String) {
 let documentsUrl = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]) 
 let imgUrl =  documentsUrl.appendingPathComponent(image_name)

 if(FileManager.default.fileExists(atPath:imgUrl.path))
 {
do {
     let data = try Data(contentsOf:imgUrl)
     self.imageView.image = UIImage(data:data)
    }catch {
   print(error)
   } } else{
  self.imageView.image = UIImage(named:"default.jpg") //Display any default image 
    }
  }
Sham Dhiman
  • 1,348
  • 1
  • 21
  • 59
Radhe Yadav
  • 112
  • 11
  • I'm pretty new at this whole thing, I'm having difficulty reading the answer or knowing what exactly it's doing. Could you comment around things to explain what's going on? – Chris Feb 26 '19 at 14:59