如何使用PHAssetChangeRequest批量编辑iOS照片库中的多张照片。

huangapple go评论65阅读模式
英文:

How to batch edit multiple photos in iOS photo library using PHAssetChangeRequest

问题

我试图对用户从其照片库中选择的照片应用变换编辑。我有以下代码:

let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { adjustmentData -> Bool in
    adjustmentData.formatIdentifier == AdjustmentFormatIdentifier && adjustmentData.formatVersion == "1.0"
}
PHPhotoLibrary.shared().performChanges({
    for asset in imageAssets {
        asset!.requestContentEditingInput(with: options) {contentEditingInput, info in
            // 从完整图像表示创建CIImage
            let url = contentEditingInput!.fullSizeImageURL!
            let orientation = contentEditingInput!.fullSizeImageOrientation
            var inputImage = CIImage(contentsOf: url, options: nil)!
            inputImage = inputImage.oriented(forExifOrientation: orientation)
            
            // 创建要应用的滤镜
            let transformFilter = CIFilter.lanczosScaleTransform()
            transformFilter.inputImage = inputImage
            transformFilter.scale = 1
            transformFilter.aspectRatio = Float(stretchFactor)
            
            // 应用滤镜
            let outputImage = transformFilter.outputImage
            
            // 创建描述已应用滤镜的PHAdjustmentData对象
            let filterData = withUnsafeBytes(of: stretchFactor) { Data($0) }
            let adjustmentData = PHAdjustmentData(formatIdentifier: AdjustmentFormatIdentifier, formatVersion: "1.0", data: filterData)
            
            // 创建PHContentEditingOutput对象并将经过滤镜处理的对象写入renderedContentURL的JPEG表示
            let contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)
            let jpegData = UIImage(ciImage: outputImage!, scale: displayScale, orientation: .up).jpegData(compressionQuality: 1)
            do {
                try jpegData?.write(to: contentEditingOutput.renderedContentURL, options: .atomic)
            } catch { }
            contentEditingOutput.adjustmentData = adjustmentData
            let request = PHAssetChangeRequest(for: asset!)
            request.contentEditingOutput = contentEditingOutput
        }
    }
}, completionHandler: {success, error in
   if success {
       isPresented = false
   }
})

但是,当我尝试保存编辑时,我收到一个错误,指出“此方法只能从-[PHPhotoLibrary performChanges:completionHandler:]内部调用”。

如果我将PHAssetChangeRequest()行从requestContentEditingInput()函数中移出,并直接放入PHPhotoLibrary.shared().performChanges({}),那么它会起作用,但问题是iOS会为用户选择的每张照片显示单独的权限提醒(即,他们必须连续点击“允许” 10 次)。相反,我只想显示一个权限提醒,询问“您是否允许此应用修改 10 张照片?”这绝对是可能的,因为像Pixelmator Photo这样的应用正是这样做的。这是如何完成的?

英文:

I'm trying to apply a transform edit to photos that a user selects from their photo library. I have the following code:

let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { adjustmentData -> Bool in
    adjustmentData.formatIdentifier == AdjustmentFormatIdentifier && adjustmentData.formatVersion == "1.0"
}
PHPhotoLibrary.shared().performChanges({
    for asset in imageAssets {
        asset!.requestContentEditingInput(with: options) {contentEditingInput, info in
            // Create a CIImage from the full image representation
            let url = contentEditingInput!.fullSizeImageURL!
            let orientation = contentEditingInput!.fullSizeImageOrientation
            var inputImage = CIImage(contentsOf: url, options: nil)!
            inputImage = inputImage.oriented(forExifOrientation: orientation)
            
            // Create the filter to apply
            let transformFilter = CIFilter.lanczosScaleTransform()
            transformFilter.inputImage = inputImage
            transformFilter.scale = 1
            transformFilter.aspectRatio = Float(stretchFactor)
            
            // Apply the filter
            let outputImage = transformFilter.outputImage
            
            // Create a PHAdjustmentData object that describes the filter that was applied
            let filterData = withUnsafeBytes(of: stretchFactor) { Data($0) }
            let adjustmentData = PHAdjustmentData(formatIdentifier: AdjustmentFormatIdentifier, formatVersion: "1.0", data: filterData)
            
            // Create a PHContentEditingOutput object and write a JPEG representation of the filtered object to the renderedContentURL
            let contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)
            let jpegData = UIImage(ciImage: outputImage!, scale: displayScale, orientation: .up).jpegData(compressionQuality: 1)
            do {
                try jpegData?.write(to: contentEditingOutput.renderedContentURL, options: .atomic)
            } catch { }
            contentEditingOutput.adjustmentData = adjustmentData
            let request = PHAssetChangeRequest(for: asset!)
            request.contentEditingOutput = contentEditingOutput
        }
    }
}, completionHandler: {success, error in
   if success {
       isPresented = false
   }
})

But when I try to save the edits, I get an error stating This method can only be called from inside of -[PHPhotoLibrary performChanges:completionHandler:]

If I move the PHAssetChangeRequest() line out of the requestContentEditingInput() function and directly into PHPhotoLibrary.shared().performChanges({}) then it works, but the issue is that iOS displays a separate permission alert to allow saving the edited photo for every single photo the user selected (i.e. they have to tap "Allow" 10 times in a row). Instead, I only want to show a single permission alert asking "Do you want to allow this app to modify 10 photos?". This is definitely possible as apps like Pixelmator Photo do exactly this. How is it done?

答案1

得分: 1

你的问题是因为PHAsset requestContentEditingInput方法是异步的。这导致你的PHAssetChangeRequest调用在PHPhotoLibrary.shared().performChanges块完成之后才被执行。换句话说,在performChanges块的范围内,你没有执行任何更改。这就是为什么你一遍又一遍地收到提示,而不是只有一次。

下面是你的代码的修改版本。这段代码遍历资源,创建对应的PHContentEditingOutput实例。只有在所有这些异步过程都完成之后,才会调用performChanges,并创建和设置一组PHAssetChangeRequest实例。

使用DispatchGroup来等待所有异步过程完成。

let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { adjustmentData -> Bool in
    adjustmentData.formatIdentifier == AdjustmentFormatIdentifier && adjustmentData.formatVersion == "1.0"
}

var editingOutputs = [PHAsset: PHContentEditingOutput]()
let group = DispatchGroup()
for asset in imageAssets {
    group.enter()
    asset!.requestContentEditingInput(with: options) { contentEditingInput, info in
        defer { group.leave() }

        // 从完整图像表示中创建CIImage
        let url = contentEditingInput!.fullSizeImageURL!
        let orientation = contentEditingInput!.fullSizeImageOrientation
        var inputImage = CIImage(contentsOf: url, options: nil)!
        inputImage = inputImage.oriented(forExifOrientation: orientation)

        // 创建要应用的滤镜
        let transformFilter = CIFilter.lanczosScaleTransform()
        transformFilter.inputImage = inputImage
        transformFilter.scale = 1
        transformFilter.aspectRatio = Float(stretchFactor)

        // 应用滤镜
        let outputImage = transformFilter.outputImage

        // 创建描述所应用滤镜的PHAdjustmentData对象
        let filterData = withUnsafeBytes(of: stretchFactor) { Data($0) }
        let adjustmentData = PHAdjustmentData(formatIdentifier: AdjustmentFormatIdentifier, formatVersion: "1.0", data: filterData)

        // 创建PHContentEditingOutput对象,并将经过滤镜处理的对象的JPEG表示写入renderedContentURL
        let contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)
        let jpegData = UIImage(ciImage: outputImage!, scale: displayScale, orientation: .up).jpegData(compressionQuality: 1)
        do {
            try jpegData?.write(to: contentEditingOutput.renderedContentURL, options: .atomic)
        } catch { }
        contentEditingOutput.adjustmentData = adjustmentData
        editingOutputs[asset!] = contentEditingOutput
    }
}

group.notify(queue: .main) {
    PHPhotoLibrary.shared().performChanges({
        for (asset, contentEditingOutput) in editingOutputs {
            let request = PHAssetChangeRequest(for: asset)
            request.contentEditingOutput = contentEditingOutput
        }
    }, completionHandler: { success, error in
       if success {
       }
    })
}
英文:

Your issue is due to the asynchronous nature of the PHAsset requestContentEditingInput method. This results in your calls to PHAssetChangeRequest being made long after the PHPhotoLibrary.shared().performChanges block is finished. In other words, you end not performing any changes within the scope of the performChanges block. This is why you get prompted over and over instead of once.

The following rework of your code should work. This code iterates through the assets creating each of the corresponding PHContentEditingOutput instances. Once all of those asynchronous processes complete, only then is performChanges called and a set of PHAssetChangeRequest instances are created and setup.

A DispatchGroup is used to wait until all asynchronous processes are complete.

let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { adjustmentData -> Bool in
    adjustmentData.formatIdentifier == AdjustmentFormatIdentifier && adjustmentData.formatVersion == "1.0"
}

var editingOutputs = [PHAsset:PHContentEditingOutput]()
let group = DispatchGroup()
for asset in imageAssets {
    group.enter()
    asset!.requestContentEditingInput(with: options) {contentEditingInput, info in
        defer { group.leave() }

        // Create a CIImage from the full image representation
        let url = contentEditingInput!.fullSizeImageURL!
        let orientation = contentEditingInput!.fullSizeImageOrientation
        var inputImage = CIImage(contentsOf: url, options: nil)!
        inputImage = inputImage.oriented(forExifOrientation: orientation)

        // Create the filter to apply
        let transformFilter = CIFilter.lanczosScaleTransform()
        transformFilter.inputImage = inputImage
        transformFilter.scale = 1
        transformFilter.aspectRatio = Float(stretchFactor)

        // Apply the filter
        let outputImage = transformFilter.outputImage

        // Create a PHAdjustmentData object that describes the filter that was applied
        let filterData = withUnsafeBytes(of: stretchFactor) { Data($0) }
        let adjustmentData = PHAdjustmentData(formatIdentifier: AdjustmentFormatIdentifier, formatVersion: "1.0", data: filterData)

        // Create a PHContentEditingOutput object and write a JPEG representation of the filtered object to the renderedContentURL
        let contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!)
        let jpegData = UIImage(ciImage: outputImage!, scale: displayScale, orientation: .up).jpegData(compressionQuality: 1)
        do {
            try jpegData?.write(to: contentEditingOutput.renderedContentURL, options: .atomic)
        } catch { }
        contentEditingOutput.adjustmentData = adjustmentData
        editingOutputs[asset!] = contentEditingOutput
    }
}

group.notify(queue: .main) {
    PHPhotoLibrary.shared().performChanges({
        for (asset, contentEditingOutput) in editingOutputs {
            let request = PHAssetChangeRequest(for: asset)
            request.contentEditingOutput = contentEditingOutput
        }
    }, completionHandler: {success, error in
       if success {
       }
    })
}

huangapple
  • 本文由 发表于 2023年3月8日 14:20:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/75669928.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定