英文:
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 {
}
})
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论