Only one iOS permission dialog when modifying multiple photos

这一生的挚爱 提交于 2021-02-08 04:45:55

问题


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: or performChangesAndWait: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 the contentEditingOutput property on each to an independent PHContentEditingOutput 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!