问题
My app allows a user to multi select images from the camera roll and apply edits to these images. However, it prompts the user for permission for each image edit. Is it possible to only display one permission dialog when editing a user's images? If yes, how do I go about grouping my edits into one permission? Here is a screenshot from my app.
I found another app on the app store that is able to mass delete photos with just one permission prompt. Here is a screenshot of that app. Does anyone know if this is possible for "Modify" and not just "Delete"?
Here is my code for modifying each asset.
func selectImageFromCameraRoll() {
let newImagePicker = BSImagePickerViewController()
newImagePicker.doneButton = UIBarButtonItem(title: "Stamp", style: UIBarButtonItemStyle.done, target: self, action: nil)
bs_presentImagePickerController(newImagePicker, animated: true,
select: { (asset: PHAsset) -> Void in
print("Selected")
}, deselect: { (asset: PHAsset) -> Void in
print("Deselected")
}, cancel: { (assets: [PHAsset]) -> Void in
print("Cancel")
}, finish: { (assets: [PHAsset]) -> Void in
for asset in assets {
self.saveUpdatedAsset(asset: asset)
}
print("Finished")
}, completion: nil)
}
func saveUpdatedAsset(asset: PHAsset) {
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { adjustmentData in
print("here")
return adjustmentData.formatIdentifier == self.myIdentifier
}
var id: PHContentEditingInputRequestID = 0
id = asset.requestContentEditingInput(with: options) {
input, info in
guard let input = input else {
asset.cancelContentEditingInputRequest(id)
return
}
let act = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
act.backgroundColor = .darkGray
act.layer.cornerRadius = 3
act.center = self.view.center
self.view.addSubview(act)
act.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
let inurl = input.fullSizeImageURL!
let inorient = input.fullSizeImageOrientation
let output = PHContentEditingOutput(contentEditingInput: input)
let outurl = output.renderedContentURL
guard var ci = CIImage(contentsOf: inurl)?.applyingOrientation(inorient) else {
act.removeFromSuperview()
let ac = UIAlertController(title: "Save error", message: "Failed to edit image 😭", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
return
}
let space = ci.colorSpace!
let uim = UIImage(ciImage: ci)
let width = uim.size.width
let height = uim.size.height
let location = Locations(rawValue: UserDefaults.standard.value(forKey: "location") as! String)!
var point = CGPoint(x: 0, y: 0)
switch location {
case .topLeft:
point = CGPoint(x: 30, y: 50)
case .topRight:
point = CGPoint(x: width - 30, y: 50)
case .bottomLeft:
point = CGPoint(x: 30, y: height - 50)
case .bottomRight:
point = CGPoint(x: width - 30, y: height - 50)
case .center:
point = CGPoint(x: width / 2, y: height / 2)
case .topCenter:
point = CGPoint(x: width / 2, y: 50)
case .bottomCenter:
point = CGPoint(x: width / 2, y: height - 50)
}
let savedFormat = UserDefaults.standard.value(forKey: "format") as! String
var date = Date()
if !self.currentDateBool {
date = UserDefaults.standard.value(forKey: "selectedDate") as! Date
}
let timestampText = getFormattedDateFromFormatType(formatType: savedFormat, date: date) as NSString
let timestampImage = self.textToImage(drawText: timestampText, inImage: uim, atPoint: point)
ci = CIImage(image: timestampImage)!
try! CIContext().writeJPEGRepresentation(of: ci, to: outurl, colorSpace: space)
let data = NSKeyedArchiver.archivedData(withRootObject: timestampText)
output.adjustmentData = PHAdjustmentData(
formatIdentifier: self.myIdentifier, formatVersion: "1.0", data: data)
PHPhotoLibrary.shared().performChanges({
print("finishing")
typealias Req = PHAssetChangeRequest
let req = Req(for: asset)
req.contentEditingOutput = output
}) { ok, err in
DispatchQueue.main.async {
act.removeFromSuperview()
print("in completion handler")
if ok {
print("changed!")
} else {
print("phasset change request error: \(err)")
}
}
}
}
}
}
回答1:
@dave-weston is right — you need to combine those requests in one single PHPhotoLibrary.performChanges()
block.
The question is how — and I've just figured this out myself. I'm a rookie so if there is a better way to do this please let me know.
// Create a dictionary with PHAsset as keys and PHContentEditingOutput as values.
var changeDict: [PHAsset: PHContentEditingOutput] = [:]
// Use two local variables to to the counting work
var _i = 0
let _n = <#YOUR_ARRAY_OF_ASSETS#>.count
// Get dict and perform changes
<#YOUR_ARRAY_OF_ASSETS#>.forEach { (asset) in
asset.requestContentEditingInput(with: nil) { (optionalInput, info) in
guard let input = optionalInput else {return}
guard let url = input.fullSizeImageURL else {return}
// perform the editing operation, which applies a noir filter in this case
let ciImage = CIImage(contentsOf: url, options: nil)!
.oriented(forExifOrientation: input.fullSizeImageOrientation)
// write to output location
let output = PHContentEditingOutput(contentEditingInput: input)
let adjustmentData = PHAdjustmentData(formatIdentifier: "s",
formatVersion: "1.2",
data: "CIPhotoEffectTransfer".data(using: .utf8)!)
output.adjustmentData = adjustmentData
if let data = <#YOUR_CIIMAGE_TO_DATA_METHOD#> {
do {
try data.write(to: output.renderedContentURL, options: .atomic)
} catch {
fatalError("Cannot write ciImage to disk.")
}
changeDict[asset] = output
}
// Count + 1
_i += 1
// Check if this is the last PHAsset to process. If so, we commit all the changes.
if _i >= _n {
// Commit the changes
PHPhotoLibrary.shared().performChanges({
for (asset, output) in changeDict {
let request = PHAssetChangeRequest(for: asset)
request.contentEditingOutput = output
}
}, completionHandler: { (success, error) in
if success {
} else {
print("Error Saving Edits:", error?.localizedDescription ?? "Unknown Error")
}
})
}
}
}
There are some issues with this approach, but currently it works OK for me. Again, please let me know if there is a better way of doing this, probably adding more control to processing flow with Grand Central Dispatch... Currently it only count the number of all photos processed, without any control the order in which the photos get processed.
回答2:
From the documentation for PHPhotoLibrary
:
Note
For each call to the
performChanges:completionHandler:
orperformChangesAndWait:error:
method, Photos shows an alert asking the user for permission to edit the contents of the photo library. If your app needs to submit several changes at once, combine them into a single change block....
To edit the content of multiple existing photos, create multiple
PHAssetChangeRequest
objects and set thecontentEditingOutput
property on each to an independentPHContentEditingOutput
object.
https://developer.apple.com/reference/photos/phphotolibrary
回答3:
It is much easier than I thought before, I was looking for the same problem:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.deleteAssets(your_array_of_assets as NSFastEnumeration)
}) { (success, error) in }
来源:https://stackoverflow.com/questions/42377872/only-one-ios-permission-dialog-when-modifying-multiple-photos