App Crashes When saving image to camera roll - swift

When I try saving a scan to the camera roll the app crashes
Here's the Code:
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
// Make sure the user scanned at least one page
guard scan.pageCount >= 1 else {
// You are responsible for dismissing the VNDocumentCameraViewController.
controller.dismiss(animated: true)
return
}
// This is a workaround for the VisionKit bug which breaks the `UIImage` returned from `VisionKit`
// See the `Image Loading Hack` section below for more information.
var arrImages = [UIImage]()
for i in 0...scan.pageCount-1 {
let originalImage = scan.imageOfPage(at: i)
let fixedImage = reloadedImage(originalImage)
arrImages.append(fixedImage)
}
controller.dismiss(animated: true)
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let docURL = documentDirectory.appendingPathComponent("Delete This")
if Filetype == 1 {
let data = createNewPDF(arrImage: arrImages)
do {
try data?.write(to: docURL, options: .completeFileProtection)
print("Success")
} catch(let error) {
print("error is \(error.localizedDescription)")
}
} else {
if Filetype == 2 {
if customjg == 68 {
for i in 0...scan.pageCount-1 {
let originalImage = scan.imageOfPage(at: i)
let fixedImage = originalImage.jpegData(compressionQuality: 0.7)
let reloadedImage = UIImage(data: fixedImage!)
UIImageWriteToSavedPhotosAlbum(reloadedImage!, nil, nil, nil);
//arrImages.append(fixedImage)
}
if customjg == 69 {
let originalImage = scan.imageOfPage(at: 1)
let rere = self.resizeImagezz(image: originalImage, targetSize: CGSize(width: Widthv, height: Heightv))
let fixedImage = rere.jpegData(compressionQuality: 0.7)
let reloadedImage = UIImage(data: fixedImage!)
UIImageWriteToSavedPhotosAlbum(reloadedImage!, nil, nil, nil);
//arrImages.append(fixedImage)
}
}
}else{
if Filetype == 3 {
for i in 0...scan.pageCount-1 {
let originalImage = scan.imageOfPage(at: i)
let fixedImage = originalImage.pngData()
let reloadedImage = UIImage(data: fixedImage!)
UIImageWriteToSavedPhotosAlbum(reloadedImage!, nil, nil, nil);
//arrImages.append(fixedImage)
}
}
}
}
}
The File Type is a segment controlled switch case. The first option by default is JPEG.
It does not even ask for the camera roll access permission before crashing (Yes I've put it in the info.plist file).
Only PDF works as of now.
But the twist is that everything works when installed on iOS 14 Beta.
Please help me rectify this issue As soon as you can.
Thanks for the help in Advance.

As per documentation - https://developer.apple.com/documentation/uikit/1619125-uiimagewritetosavedphotosalbum,
we should implement the completionSelector. and for the same set completionTarget as self.
implement the api as below:
UIImageWriteToSavedPhotosAlbum(reloadedImage!, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
then in this completionSelector:
#objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
guard let error = error else {//success return}
// found error
print(error)
}

Related

collectionView cell Image change when scrolling - swift - programmatically

I need to load an ImageView inside UIcollectionViewcell using a URL that I pass during initialisation:
func configureCellWith(messageModel : MessageModel){
guard let url = URL(string: messageModel.contentUrl!) else { return }
if url.isURLPhoto(){
likedImageView.sd_setImage(with: url, placeholderImage: nil)
}
else if url.isURLVideo(){
getThumbnailImageFromVideoUrl(url: url) { (image) in
self.likedImageView.image = image
}
}
If url is video I need to load the image in this way using this method:
func getThumbnailImageFromVideoUrl(url: URL, completion: #escaping ((_ image: UIImage?)->Void)) {
DispatchQueue.global().async {
let asset = AVAsset(url: url)
let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
avAssetImageGenerator.appliesPreferredTrackTransform = true
let thumnailTime = CMTimeMake(value: 2, timescale: 1)
do {
let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumnailTime, actualTime: nil)
let thumbNailImage = UIImage(cgImage: cgThumbImage)
DispatchQueue.main.async {
completion(thumbNailImage)
}
} catch {
print(error.localizedDescription)
DispatchQueue.main.async {
completion(nil)
}
}
}
}
As visible I retrieve the initial frame of the video and I load it inside the cell, obviously since it's an asynchronous function it will take some time for loading the image, there's no problem In that.
The problem occurs when I scroll through the collection and I see that some cells display images which don't correspond to the correct ones.
Searching online I found out that I need to clear the image in prepareForReuse of the cell and so I did (both in case the image is loaded through sd_setImage and though getThumbnailImageFromVideoUrl function):
override func prepareForReuse() {
super.prepareForReuse()
self.likedImageView.image = UIImage()
self.likedImageView.image = nil
self.likedImageView.sd_cancelCurrentImageLoad()
}
but I still get images mismatched when scrolling thought the collection view, what could be the problem?
I think the issue is not with images, i guess its with video thumbnail. You generate a thumbnail on background thread synchronously but while setting it back to imageView you never bothered to find if the cell is reused and the image u just created is outdated or not.
So in your cell
var currentModel: MessageModel! = nil //declare a instance variable to hold model
... other code
func configureCellWith(messageModel : MessageModel){
self.currentModel = messageModel //keep a copy of model passed to u as argument
guard let url = URL(string: messageModel.contentUrl!) else { return }
if url.isURLPhoto(){
likedImageView.sd_setImage(with: url, placeholderImage: nil)
}
else if url.isURLVideo(){
getThumbnailImageFromVideoUrl(url: url) { (image) in
self.likedImageView.image = image
}
}
Finally in getThumbnailImageFromVideoUrl
func getThumbnailImageFromVideoUrl(url: URL, completion: #escaping ((_ image: UIImage?)->Void)) {
DispatchQueue.global().async {
let asset = AVAsset(url: url)
let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
avAssetImageGenerator.appliesPreferredTrackTransform = true
let thumnailTime = CMTimeMake(value: 2, timescale: 1)
do {
let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumnailTime, actualTime: nil)
let thumbNailImage = UIImage(cgImage: cgThumbImage)
if url.absoluteString == currentModel.contentUrl { //check if image you generated is still valid or its no longer needed
DispatchQueue.main.async {
completion(thumbNailImage)
}
}
} catch {
print(error.localizedDescription)
DispatchQueue.main.async {
completion(nil)
}
}
}

Swift. How to extract the individual sub images from an HEIC image

I need to be able to load an heic image and extract and output all of the sub images as pngs similar to how preview does it. For example, if you open a dynamic heic wallpaper in preview, it shows all the images in the sidebar with their names:
How do you do this? I've tried to use NSImage like below. But that only outputs a single image:
let image = NSImage(byReferencing: url)
image.writePNG(toURL: newUrl)
You need to load the HEIC data, get its CGImageSource and its count. Then create a loop from 0 to count-1 and get each image at the corresponding index. You can create an array with those CGImages in memory or write them to disk (preferred). Note that this will take a while to be executed because of the size of the HEIC file 186MB. Each image extracted will be from 19MB to 28MB.
func extractHeicImages(from url: URL) throws {
let data = try Data(contentsOf: url)
let location = url.deletingLastPathComponent()
let pathExtension = url.pathExtension
let fileName = url.deletingPathExtension().lastPathComponent
let destinationFolder = location.appendingPathComponent(fileName)
guard pathExtension == "heic", let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
let count = CGImageSourceGetCount(imageSource)
try FileManager.default.createDirectory(at: destinationFolder, withIntermediateDirectories: false, attributes: nil)
for index in 0..<count {
try autoreleasepool {
if let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, nil) {
let number = String(format: "#%05d", index)
let destinationURL = destinationFolder
.appendingPathComponent(fileName+number)
.appendingPathExtension(pathExtension)
try NSImage(cgImage: cgImage, size: .init(width: cgImage.width, height: cgImage.height))
.heic?
.write(to: destinationURL)
print("saved image " + number)
}
}
}
}
You will need these helpers as well to extract the cgimate from your image and also to get a HEIC data representation from them:
extension NSImage {
var heic: Data? { heic() }
var cgImage: CGImage? {
var rect = NSRect(origin: .zero, size: size)
return cgImage(forProposedRect: &rect, context: .current, hints: nil)
}
func heic(compressionQuality: CGFloat = 1) -> Data? {
guard
let mutableData = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(mutableData, "public.heic" as CFString, 1, nil),
let cgImage = cgImage
else { return nil }
CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: compressionQuality] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}
Playground testing. This assumes the "Catalina.heic" is located at your desktop.
let catalinaURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("Catalina.heic")
do {
try extractHeicImages(from: catalinaURL)
} catch {
print(error)
}
Each subimage is represented by a NSBitmapImageRep. Loop the image reps, convert to png and save:
let imageReps = image.representations
for imageIndex in 0..<imageReps.count {
if let imageRep = imageReps[imageIndex] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(imageIndex).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}
The conversion to png takes some time. Running the conversions in parallel is faster but I'm not sure if it's save:
DispatchQueue.concurrentPerform(iterations: imageReps.count) { iteration in
if let imageRep = imageReps[iteration] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(iteration).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}

Save Image to Photo Library and retrieve the URL

I can save an image to my photo library using the following code:
PHPhotoLibrary.shared().performChanges({
PHAssetCreationRequest
.creationRequestForAssetFromImage(atFileURL: outfileURL)
}) { (saved, err) in
print("Saved?", saved)
if (saved) {
DispatchQueue.main.async {
onComplete(outfileURL.absoluteString)
}
}
}
But I am trying to load the image I just saved in an Image (SwiftUI) and I want to get the new images fileURL. The outfileURL is a temp file and is not retained. Please note this is a gif - if that has any bearing.
I am trying to use the PHObjectPlaceholder thing but I still don't know how to get the location out:
var placeHolder: PHObjectPlaceholder? = nil
PHPhotoLibrary.shared().performChanges({
let changeRequest = PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: outfileURL)
placeHolder = changeRequest?.placeholderForCreatedAsset
}) { (saved, err) in
print("Saved? \(saved) to location \(placeHolder?)") //<--- AAAARGH!!!!
if (saved) {
DispatchQueue.main.async {
onComplete(/*????*/)
}
}
}
From PHObjectPlaceholder you can use localIdentifier
let fetchOptions = PHFetchOptions()
let fetchResult: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: fetchOptions)
if let asset = fetchResult.firstObject {
// Here you can get UIImage from PHAsset
}
So this is possible solution:
var placeHolder: PHObjectPlaceholder? = nil
PHPhotoLibrary.shared().performChanges({
let changeRequest = PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: outfileURL)
placeHolder = changeRequest?.placeholderForCreatedAsset
}) { (saved, err) in
if let localIdentifier = placeHolder?.localIdentifier, saved {
let fetchOptions = PHFetchOptions()
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: fetchOptions)
if let phAsset = fetchResult.firstObject {
let targetSize = CGSize(width: CGFloat(phAsset.pixelWidth), height: CGFloat(phAsset.pixelHeight))
let options = PHImageRequestOptions()
PHCachingImageManager.default().requestImage(for: phAsset, targetSize: targetSize, contentMode: .aspectFill, options: options) { (uiImage, info) in
DispatchQueue.main.async {
onComplete(uiImage)
}
}
}
}
}

UIImage sometimes flipped [duplicate]

If I use the image before it is saved it is normal. But if I save it and use it later is is 90 degrees turned. How can I make sure it doesn't save sideways?
func saveEvent(_ center1: CLLocation, title2: String, imagePicked1: UIImage)
{
let data = UIImagePNGRepresentation(imagePicked1);///
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(NSUUID().uuidString+".dat")
do {
try data!.write(to: url!, options: [])
} catch let e as NSError {
print("Error! \(e)");
return
}
let image11 = CKAsset(fileURL: url!)
self.eventRecord.setObject(image11 as CKAsset, forKey: "Picture")
let publicData = CKContainer.default().publicCloudDatabase
publicData.save(self.eventRecord, completionHandler: { record, error in
if error == nil
{
print("Image saved")
}else{
print(error!)
}
})
}
If you need to save your PNG with correct rotation you will need to redraw your image if its orientation it is not .up. You can redraw it as follow:
extension UIImage {
func png(isOpaque: Bool = true) -> Data? { flattened(isOpaque: isOpaque)?.pngData() }
func flattened(isOpaque: Bool = true) -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
edit/update:
For iOS10+ tvOS10+ you can use UIGraphicsImageRenderer:
extension UIImage {
func png(isOpaque: Bool = true) -> Data? { flattened(isOpaque: isOpaque).pngData() }
func flattened(isOpaque: Bool = true) -> UIImage {
if imageOrientation == .up { return self }
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: size, format: format).image { _ in draw(at: .zero) }
}
}
Playground testing:
Usage for images without transparency:
let image = UIImage(data: try! Data(contentsOf: URL(string: "https://i.stack.imgur.com/varL9.jpg")!))!
if let data = image.png() {
let imageFromPNGData = UIImage(data: data)
}
With transparency :
if let data = image.png(isOpaque: false) {
let imageFromPNGData = UIImage(data: data)
}
Just convert the image to JPEG data instead. No need to redraw your image:
let imageData = image.jpegData(compressionQuality: 1.0)
You can use this as well to prevent it from changing of orientation.
func rotateImage(image: UIImage) -> UIImage? {
if (image.imageOrientation == UIImage.Orientation.up ) {
return image
}
UIGraphicsBeginImageContext(image.size)
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
let copy = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return copy
}

How do I fix a nil error when trying to save an image to library?

I'm building an app that takes a picture and then you will have the option to save it. However before you have taken a picture the UIImageView is empty (nil) and when you press the save button it crashes because you can't save photos that are nil. How can I make an error handling so it doesn't crash when the UIImageView is nil.
I've tried throw and catch but can't get it to work.
func tryDataConversion() throws -> Data {
let imageData = myImage.image!.jpegData(compressionQuality: 1.0)
return imageData!
}
Full code for action func that I want to work:
#IBAction func saveImageButton(_ sender: Any) {
let imageData = myImage.image!.jpegData(compressionQuality: 1.0)
let compressedImage = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(compressedImage!, nil, nil, nil)
alert(title: "Recent Photo Saved", msg: "Your most recent photo has been saved to your photo library")
}
as soon as the following line it crashes because when unwrapping it founds nil and it can unwrap nil.
let imageData = myImage.image!.jpegData(compressionQuality: 1.0)
and gives the following error.
Fatal error: Unexpectedly found nil while unwrapping an Optional value:
instead of
let imageData = myImage.image!.jpegData(compressionQuality: 1.0)
let compressedImage = UIImage(data: imageData!)
UIImageWriteToSavedPhotosAlbum(compressedImage!, nil, nil, nil)
try this:
if let imageData = myImage.image?.jpegData(compressionQuality: 1.0),
let compressedImage = UIImage(data: imageData) {
UIImageWriteToSavedPhotosAlbum(compressedImage!, nil, nil, nil)
}
If you want to throw an error to handle you need to create an error:
enum DataConversionError: Error {
case emptyImage
}
func tryDataConversion() throws -> Data {
guard let imageData = someData else {
throw DataConversionError.emptyImage
}
return imageData
}
func handleSaveButtonTap() {
do {
let imageData = try tryDataConversion()
// Do something with your imageData
print(imageData)
}
catch {
// Handle your error here
print("UIImageView is empty")
print("Error: \(error)")
return
}
}