Problems with saving an image from UIDocumentPickerViewController / .stopAccessingSecurityScopedResource() - swift5

I have a viewcontroller that presents my custom ImagePicker class. One of the presented options is to select an image from 'Files', using UIDocumentPickerViewController. Picking an image works fine, but i want to close the security resources conform the recommendations.
ProjectimagePicker {
(...)
let documentsPicker = UIDocumentPickerViewController(documentTypes: ["public.image", "public.jpeg", "public.png"], in: .open)
documentsPicker.delegate = self
documentsPicker.allowsMultipleSelection = false
documentsPicker.modalPresentationStyle = .fullScreen
self.presentationController?.present(documentsPicker, animated: true, completion: nil)
}
//MARK: - Ext. Delegate DocumentPicker
extension ProjectImagePicker: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard controller.documentPickerMode == .open, let url = urls.first, url.startAccessingSecurityScopedResource() else { return }
defer {
DispatchQueue.main.async {
url.stopAccessingSecurityScopedResource()
}
}
guard let image = UIImage(contentsOfFile: url.path) else { return }
self.delegate?.didSelect(image: image)
controller.dismiss(animated: true)
}
public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
controller.dismiss(animated: true)
}
}
And in the viewController that called the picker class:
//MARK: - Delegate ProjectImagePicker
extension ProjectDetailsViewController: ProjectImagePickerDelegate {
func didSelect(image: UIImage?) {
if let image = image {
selectedImage = image
projectImageView.image = image
}
}
}
Part of the problem I circumvent by wrapping a dispatch call around stopAccessingSecurityScopedResource(). The image gets send back (delegate) and presented
in the viewController. But when I eventually save (write to documents directory) my project and that image (selectedImage), I get the security error
2020-05-08 13:54:04.429936+0200 ProjectS[3482:1339708] [ProjectS] createDataWithMappedFile:1524: 'open' failed '/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/10.JPEG' error = 1 (Operation not permitted)
So, apparently the image is still referencing the URL from the Documents picker. I could try and save the image temporarily in the didPickDocumentsAt method, but that seems ugly. Or I can omit calling stopAccessingSecurityScopedResource() all together, but that may cause problems?
Any ideas on how to handle this the best possible way ?

If I update my code for didPickDocumentsAt in the way that I make a copy of the image, all works as intended
//MARK: - Ext. Delegate DocumentPicker
extension ProjectImagePicker: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard controller.documentPickerMode == .open, let url = urls.first, url.startAccessingSecurityScopedResource() else { return }
defer {
DispatchQueue.main.async {
url.stopAccessingSecurityScopedResource()
}
}
//Need to make a new image with the jpeg data to be able to close the security resources!
guard let image = UIImage(contentsOfFile: url.path), let imageCopy = UIImage(data: image.jpegData(compressionQuality: 1.0)!) else { return }
self.delegate?.didSelect(image: imageCopy)
controller.dismiss(animated: true)
}
Good solution, or better ideas?

Related

Xcode, adding my image view to mail composer in app

I am hoping someone can help me with something I've not done before. which is attach an image from a image view and put it into my email in app. i have all other fields working correctly I just cannot add my image.
thanks in advance for help
class SecondViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet var imageView: UIImageView!
func showMailComposer() {
guard MFMailComposeViewController.canSendMail() else {
return
}
let composer = MFMailComposeViewController()
composer.mailComposeDelegate = self
composer.setToRecipients(["LJDNSGKJNDGJ"])
composer.setSubject("JSABFKASJ")
composer.setMessageBody("hbdfnjbqeashfbnwdlskm", isHTML: false)
//I would like to know how to connect the "#IBOutlet var imageView: UIImageView!" to the composer. here ? and this would then present the image in the email.
)
present(composer, animated: true)
func mailButtonPress() {
if let image = imageView!.image {
composeMail(with:image)
} else {
print("image is nil")
}
}
func composeMail(with image:UIImage) {
let mailComposeVC = MFMailComposeViewController()
mailComposeVC.addAttachmentData(image.jpegData(compressionQuality: CGFloat(1.0))!, mimeType: "image/jpeg", fileName: "test.jpeg")
mailComposeVC.setSubject("Email Subject")
mailComposeVC.setMessageBody("<html><body><p>This is your message</p></body></html>", isHTML: true)
self.present(mailComposeVC, animated: true, completion: nil)
}

Get selected image in imagePickerController and pass it to coreML

I'm trying to do the recognition by using coreML, the function working and showing the result correctly. But I want to call the method into a button, like when I pressed the catDog button and it runs the method. But since the finalResult() and identifyCatOrDog() is its own function, so that I can't call it into the button. I tried to copy and paste the method inside the button, but it doesn't show me anything. How can I edit the code so that findResult() only work when I pressed the button not running automatically?
import UIKit
import CoreML
import Vision
import Photos
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet var loadImage: UIImageView!
#IBOutlet var Result: UILabel!
#IBAction func photoBtn(_ sender: UIButton) {
getPhoto()
}
#IBAction func cameraBtn(_ sender: UIButton) {
}
#IBAction func catDog(_ sender: UIButton) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getPhoto() {
let picker = UIImagePickerController()
picker.delegate = self
picker.sourceType = .photoLibrary
present(picker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
guard let gotImage = info[.originalImage] as? UIImage else {
fatalError("No picture chosen")
}
loadImage.image = gotImage
identifyCatOrDog(image: gotImage)
}
func identifyCatOrDog(image: UIImage) {
let modelFile = ImageClassifier()
let model = try! VNCoreMLModel(for: modelFile.model)
let handler = VNImageRequestHandler(cgImage: image.cgImage!, options: [ : ])
let request = VNCoreMLRequest(model: model, completionHandler: findResults)
try! handler.perform([request])
}
func findResults(request: VNRequest, error: Error?) {
guard let results = request.results as? [VNClassificationObservation] else {
fatalError("Unable to get results")
}
var bestGuess = ""
var bestConfidence: VNConfidence = 0
for classification in results {
if (classification.confidence > bestConfidence) {
bestConfidence = classification.confidence
bestGuess = classification.identifier
}
}
Result.text = "Image is: \(bestGuess) with confidence \(bestConfidence) out of 1"
}
I take it that the problem is that sometimes when the image picker is dismissed, you want to call identifyCatOrDog, but other times you don’t.
One rather crude possibility is this: In the button action method, raise a bool instance property flag so that when didFinishPickingMedia is called you know whether or not to call identifyCatOrDog.
A more sophisticated way would be to divide things off into helper classes so that the operation of the image picker after pressing the catDog button takes place within a completely different code world.

How to set a Custom Image as ViewController Background

I have created a button that allows me to set an image from the camera roll as the ViewController background (imageView is the background view), but I need to save it and reload it when ViewController loads.
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var backgroundSelectionView: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBAction func imageButton(_ sender: UIButton) {
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.allowsEditing = false
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
imageView.image = image
}
self.dismiss(animated: true, completion: nil)
}
}
I think I should use paths (I read it somewhere but I don't know how to do that since I can't find anything). Could you provide advice on how to save and load a CUSTOM image (not a specific one that you manually select and import in your code, I can already do that?)
Welcome to StackOverflow!
The easiest way to do this would likely be to save the image to disk once you have picked it and restore it when ViewController loads.
To save the image, use FileManager and save to the app's document storage:
/// Saves a background image to disk
/// - Parameter image: The background image to save to disk
/// - Returns: true if successful, false otherwise
func saveBackgroundImage(image: UIImage) -> Bool {
// make sure that the image can be transformed into data (assuming PNG)
guard let data = image.pngData() ?? imageView.image?.pngData() else {
return false
}
// ensure that you can write to the document director
guard let directory = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first else {
return false
}
// write the file
do {
try data.write(to: directory.appendingPathComponent("backgroundImage.png")!)
return true
} catch {
print(error.localizedDescription)
return false
}
}
Now, you need a way to retrieve the file:
/// Retrieves the saved background image
/// - Returns: The background image from disk if it exists, or nil
func getSavedBackgroundImage() -> UIImage? {
if let dir = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first {
return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent("backgroundImage.png").path)
}
return nil
}
Inside your imagePickerController(picker: didFinishPickingMediaWithInfo:) method, when you set the image on imageView, save the background image by calling self.saveBackgroundImage(image: image). If you are not checking the boolean that is returned from saveBackgroundImage(image:), then call it as follows:
let _ = self.saveBackgroundImage(image: image)
to prevent a "Result unused" warning.
Now, implement your viewDidLoad, which is where you will attempt to load the image:
override func viewDidLoad() {
super.viewDidLoad()
if let backgroundImage = self.getSavedBackgroundImage() {
// set the background image on the `imageView`
self.imageView.image = backgroundImage
}
}
Please set backgroundImage as below,
override func viewDidLoad() {
super.viewDidLoad()
assignbackground()
}
func assignbackground(){
let background = UIImage(named: backgroundImage)
var imageView : UIImageView!
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = UIViewContentMode.ScaleAspectFill
imageView.clipsToBounds = true
imageView.image = background
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubviewToBack(imageView)
}
Please user remaining code as it is as #Leejay Schmidt's code

Changing View Controller After Google Place Picker

Does anyone know how to go to a different view controller after selecting a place in Google Place Picker other than the one that called the place picker?
according to google place picker documentation
#IBAction func pickPlace(_ sender: UIButton) {
let config = GMSPlacePickerConfig(viewport: nil)
let placePicker = GMSPlacePicker(config: config)
placePicker.pickPlace(callback: { (place, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
if let place = place {
DispatchQueue.main.async {
//code to push or present a viewController
}
} else {
print("No place selected")
return
}
print("Place name \(place.name)")
print("Place address \(place.formattedAddress)")
print("Place attributions \(place.attributions)")
})
}
Hope this helps

Swift displaying image in viewdidappear cannot be replaced by picking new image

I use firebase as my back end and I have a displaying facebook photo image function in viewdidappear so the profile image will display when the user is at the viewcontroller. My problem is whenever user want to change the profile Image by image picker it will display the "picked image" for 1 sec only and turn back to facebook image.
I think the problem is the displaying facebook photo image function is in the viewdidappear? How can I solve it?
override func viewdidappear(){
super.viewdidappear(animated)
self.displayProfilePic(user)
}
func displayProfilePic(user: FIRUser?){
let photoURL = user?.photoURL
struct last {
static var photoURL: NSURL? = nil
}
last.photoURL = photoURL; later one.
if let photoURL = photoURL {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
let data = NSData.init(contentsOfURL: photoURL)
if let data = data {
let image = UIImage.init(data: data)
dispatch_async(dispatch_get_main_queue(), {
if (photoURL == last.photoURL) {
self.profilePic.image = image
}
})
}
})
} else {
profilePic.image = UIImage.init(named: "DefaultPic")
}
image Picker method
func openGallary(){
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.SavedPhotosAlbum){
print("Pick Photo")
self.imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
self.imagePicker.allowsEditing = true
self.presentViewController(self.imagePicker, animated: true, completion: nil)
}
}
func openCamera(){
if(UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
self.imagePicker.sourceType = UIImagePickerControllerSourceType.Camera
self.imagePicker.allowsEditing = true
self.presentViewController(self.imagePicker, animated: true, completion: nil)
}else{
print("you got no camara")
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMeddiaWithInfo info: [String :AnyObject]){
if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
profilePic.contentMode = .ScaleAspectFit
profilePic.image = pickedImage
}
dismissViewControllerAnimated(true, completion: nil)
}
I am not sure why the image picker's picked image cannot be replaced.
I think the problem is the displaying facebook photo image function is in the viewdidappear
This is correct. viewDidAppear is called every time the view is shown again after a VC that was covering it is dismissed (e.g. the image picker).
You could do this in viewDidLoad instead -- which is called just once when the VC is loaded -- and is not called again when the imagePicker is done.
If you can't, then you need to keep track that the imagePicker replaced the image and that it should not be replaced again with the FB image when viewDidAppear is called.