macOS 12 beta | Updating metadata silently fails at file level

I use ImageI/O call - CGImageDestinationCopyImageSource() to update image metadata and noticed it's failing on updating existing metadata fields since macOS 12 beta 5, and upgrading to beta 6 is not fixing it. MacOS 11 and below don't have this issue. Do you plan to fix it in coming betas and when?

Precondition:

  • have a file that has any IPTC. For example, IPTC Title = "Old".

Steps:

Update the metadata (e.g. change IPTC Title to "New") with following code:

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:(id)metadataToApply,kCGImageDestinationMetadata, kCFBooleanTrue,kCGImageDestinationMergeMetadata, nil]; CFErrorRef metaError = NULL;

BOOL isSuccess = CGImageDestinationCopyImageSource(idst, srf, (__bridge CFDictionaryRef)options, &metaError);

Observe: At file level, silently fails, IPTC Title = "Old".

I think I'm also running into this problem but on iOS 15. I use CGImageDestinationCopyImageSource to update some metadata. I ran an XCode test to make sure it was correctly set, but since I updated to XCode 13 and iOS 15, the test fails and the metadata is not updated in the file

That shouldn't have been an answer, missed the comment button.

Anyway, I have replicated the issue in a single test. For me the issue manifests itself in iOS 15, if I run the test on a device running ios 14, the test passes.

Here is the full code for a test, you'll need to supply your own image and change a tag that exists for you:

func testCGImageDestinationCopyImageSource() throws {
    guard let imageURL = Bundle(for: self.classForCoder).url(forResource: "Image_000001", withExtension: "jpg") else {
      XCTFail()
      return
    }
    // Work with the image data
    let originalData = try Data(contentsOf: imageURL)
    // Create source from data
    guard let imageSource = CGImageSourceCreateWithData(originalData as CFData, nil) else {
      XCTFail()
      return
    }
    guard let UTI: CFString = CGImageSourceGetType(imageSource) else {
      XCTFail()
      return
    }
    // Setup a new destination to copy data too
    let imageData: CFMutableData = CFDataCreateMutable(nil, 0)
    guard let destination = CGImageDestinationCreateWithData(imageData as CFMutableData, UTI, 1, nil) else {
      XCTFail()
      return
    }
    // Get the metadata
    var mutableMetadata: CGMutableImageMetadata
    if let imageMetadata = CGImageSourceCopyMetadataAtIndex(imageSource, 0, nil) {
      mutableMetadata = CGImageMetadataCreateMutableCopy(imageMetadata) ?? CGImageMetadataCreateMutable()
    } else {
      mutableMetadata = CGImageMetadataCreateMutable()
    }
    // Inspect and check the old value
    guard let tag = CGImageMetadataCopyTagMatchingImageProperty(mutableMetadata,
                                  kCGImagePropertyExifDictionary,
                                  kCGImagePropertyExifLensModel) else {
      XCTFail()
      return
    }
    guard let originalValue = CGImageMetadataTagCopyValue(tag) as? String else {
      XCTFail()
      return
    }
    XCTAssertEqual(originalValue, "iOS.0")
    // Set a new value in the metadata
    CGImageMetadataSetValueMatchingImageProperty(mutableMetadata,
                           kCGImagePropertyExifDictionary,
                           kCGImagePropertyExifLensModel, "iOS" as CFString)
    // Ensure new value is set in the metadata
    guard let newTag = CGImageMetadataCopyTagMatchingImageProperty(mutableMetadata,
                                  kCGImagePropertyExifDictionary,
                                  kCGImagePropertyExifLensModel) else {
      XCTFail()
      return
    }
    guard let newValue = CGImageMetadataTagCopyValue(newTag) as? String else {
      XCTFail()
      return
    }
    XCTAssertEqual(newValue, "iOS")
    // Combine the new metadata with the original image
    let options = [
      kCGImageDestinationMetadata as String : mutableMetadata,
      kCGImageDestinationMergeMetadata as String : true
      ] as [String : Any]
    guard CGImageDestinationCopyImageSource(destination, imageSource, options as CFDictionary, nil) else {
      XCTFail()
      return
    }
    // Create a new source from the copied to data
    guard let newSource = CGImageSourceCreateWithData(imageData as CFData, nil) else {
      XCTFail()
      return
    }
    // Get the metadata from the copied to data
    var mutableMetadata2: CGMutableImageMetadata
    if let imageMetadata2 = CGImageSourceCopyMetadataAtIndex(newSource, 0, nil) {
      mutableMetadata2 = CGImageMetadataCreateMutableCopy(imageMetadata2) ?? CGImageMetadataCreateMutable()
    } else {
      mutableMetadata2 = CGImageMetadataCreateMutable()
    }
    // Inspect and check the value in the copied to data
    guard let updatedTag = CGImageMetadataCopyTagMatchingImageProperty(mutableMetadata2,
                                  kCGImagePropertyExifDictionary,
                                  kCGImagePropertyExifLensModel) else {
      XCTFail()
      return
    }
    guard let updatedValue = CGImageMetadataTagCopyValue(updatedTag) as? String else {
      XCTFail()
      return
    }
    XCTAssertEqual(updatedValue, "iOS")
  }
macOS 12 beta | Updating metadata silently fails at file level
 
 
Q