Save Image to Photo Library and retrieve the URL - swift

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)
}
}
}
}
}

Related

Select Multiple Videos and Convert PHAsset to Video File using OpalImagePickerController in swift

I am working with OpalImagePickerController in Swift dealing with uploading multiple images and videos. I can easily deal with images and converting to PHAsset to UIImage but I am trying to upload multiple videos with OpalImagePickerController but unable to convert PHAsset to Video format to upload. I am providing my code .
let imagePicker = OpalImagePickerController()
imagePicker.imagePickerDelegate = self
imagePicker.allowedMediaTypes = Set([PHAssetMediaType.image])
imagePicker.maximumSelectionsAllowed = 20
self.present(imagePicker, animated: true, completion: nil)
internal func imagePicker(_ picker: OpalImagePickerController, didFinishPickingAssets assets: [PHAsset])
{
var image = UIImage()
for asset in assets
{
switch asset.mediaType {
case .image:
print("Image")
image = asset.getAssetThumbnail()
let data = image.pngData() as NSData?
self.sendImage(data:data! as Data, previewImage: image)
case .video:
print("Video")
let options: PHVideoRequestOptions = PHVideoRequestOptions ()
options.deliveryMode = .highQualityFormat
options.version = .original
PHImageManager.default().requestAVAsset (forVideo: asset, options: options, resultHandler: {(asset, audioMix, info) in
if let urlAsset = asset as? AVURLAsset {
let playerItem = AVPlayerItem(asset: urlAsset)
self.sendVideo(video: playerItem, isFromCamera: false)
} else {
}
})
case .audio:
print("Audio")
default:
print("Unknown")
}
}
picker.dismiss(animated: true, completion: nil)
}
extension PHAsset {
func getAssetThumbnail() -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: self,
targetSize: CGSize(width: self.pixelWidth, height: self.pixelHeight),
contentMode: .aspectFit,
options: option,
resultHandler: {(result, info) -> Void in
thumbnail = result!
})
return thumbnail
}
Please help me to convert PHAsset to Video format

For loop not accounting for all items?

I have a multi-selection imagepicker with the intention of allowing the user to select multiple assets, then upload each asset to the database. In the completion handler, I take all the selected assets and pass them to a custom function: uploadImageAssets(assets: [PHAsset], projectRef: DocumentReference), where the upload begins.
In the function, I'm using a for-loop to upload each asset individually. While the assets are being uploaded correctly, not all assets are being uploaded. Lets say I've selected 5 assets... Only 4 will show up in the database, and they'll all be the same image, repeated. Any idea as to why this is happening? Here is my code below:
Image Picker Selection:
#IBAction func uploadProjectTapped(_ sender: Any) {
let imagePicker = ImagePickerController()
imagePicker.settings.selection.max = 10
imagePicker.settings.theme.selectionStyle = .numbered
imagePicker.settings.fetch.assets.supportedMediaTypes = [.image, .video]
imagePicker.settings.selection.unselectOnReachingMax = false
let start = Date()
self.presentImagePicker(imagePicker, select: { (asset) in
print("Selected: \(asset)")
}, deselect: { (asset) in
print("Deselected: \(asset)")
}, cancel: { (assets) in
print("Canceled with selections: \(assets)")
}, finish: { (assets) in
print("Finished with selections: \(assets)")
self.getAssetThumbnail(assets: assets)
}, completion: {
let finish = Date()
print(finish.timeIntervalSince(start))
})
}
And, the function to add them to Firestore:
func uploadImageAsset(assets: [PHAsset], projectRef: DocumentReference) {
let userID = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = false
option.isNetworkAccessAllowed = true
option.resizeMode = .exact
option.version = .original
option.deliveryMode = .highQualityFormat
let uniqueImageID = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("project-images").child("\(uniqueImageID).jpeg")
for asset in assets {
let imageSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
manager.requestImage(for: asset, targetSize: imageSize, contentMode: .aspectFill, options: option) { (image, info) in
let uploadData = image?.jpegData(compressionQuality: 0.6)
storageRef.putData(uploadData!, metadata: nil, completion: {
(metadata, error) in
if error != nil {
return
} else {
storageRef.getMetadata(completion: { (metadata, err) in
if let error = err {
print(error)
} else {
storageRef.downloadURL(completion: { (url, err) in
if let error = err {
print(error)
} else {
self.imageAssetURLs.append((url?.absoluteString)!)
guard let url = url?.absoluteString else { return }
projectRef.updateData(["images": FieldValue.arrayUnion([url])], completion: { (err) in
if err != nil {
print(err)
} else {
self.dismiss(animated: true, completion: nil)
}
})
}
})
}
})
}
})
}
}
}
I have a strong feeling that the error lies within this line:
self.imageAssetURLs.append((url?.absoluteString)!)
guard let url = url?.absoluteString else { return }
in func uploadImageAsset(...) the
let uniqueImageID = NSUUID().uuidString
and
let storageRef = Storage.storage().reference().child("project-images").child("\(uniqueImageID).jpeg")
should be inside the loop just before
storageRef.putData(..)

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).")
}
}
}
}

how to set custom UIImage to tabbar item

what im trying to achieve is to have custom profile IMG on a tabbar item
First: I have tried to add the IMG to the tabbar directly
let ProfileImg: UIImageView = {
let Img = UIImageView(image: UIImage(named: "PlaseHolder"))
Img.layer.cornerRadius = 20
Img.contentMode = .scaleAspectFit
Img.clipsToBounds = true
Img.layer.masksToBounds = true
Img.translatesAutoresizingMaskIntoConstraints = false
return Img
}()
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
}
func fetchUser() {
guard let uid = Auth.auth().currentUser?.uid else {return}
let userRef = Database.database().reference(withPath: "users").child(uid)
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.value ?? "")
let value = snapshot.value as? NSDictionary
guard let profileImageUrl = value?["profileImageUrl"] as? String else {return}
guard let url = URL(string: profileImageUrl) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
if let err = err {
print("unable to get profile image", err)
}
guard let data = data else {return}
let image = UIImage(data: data)
DispatchQueue.main.async {
self.tabBarItem.image = image?.withRenderingMode(.alwaysOriginal)
self.tabBarItem.selectedImage = image?.withRenderingMode(.alwaysOriginal)
}
}.resume()
}) { (err) in
print("fail to fetch user", err)
}
}
and this was the result :
iphone simulator image
the image is too big for a tabbar item image
but then I found another method I couldn't dead it or get it, it did add the image but I couldn't make it fit inside the tabbar + the corner radius is not working
extension UITabBarController {
func addSubviewToLastTabItem(_ imageName: UIImage) {
if let lastTabBarButton = self.tabBar.subviews.last, let tabItemImageView = lastTabBarButton.subviews.last {
if let accountTabBarItem = self.tabBar.items?.first {
accountTabBarItem.selectedImage = nil
accountTabBarItem.image = nil
}
let imgView = UIImageView()
imgView.frame = tabItemImageView.frame
imgView.layer.cornerRadius = tabItemImageView.frame.height/2
imgView.layer.masksToBounds = true
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
imgView.image = imageName
self.tabBar.subviews.last?.addSubview(imgView)
}
}
}
and this is how to call it
self.tabBarController?.addSubviewToLastTabItem(image!)
Result : iphone simulator image
please any idea to accomplish this
use this extintion to resize your image
extension UIImage {
func resize(targetSize: CGSize) -> UIImage {
return UIGraphicsImageRenderer(size:targetSize).image { _ in
self.draw(in: CGRect(origin: .zero, size: targetSize))
}
}
}
here is how to use it in dispatch method
DispatchQueue.main.async { [weak self] in
guard let data = data else {return}
self?.ProfileImg.image = UIImage(data: data)?.resize(targetSize: CGSize(width: 33, height: 33)).roundMyImage.withRenderingMode(.alwaysOriginal)
self?.tabBar.items?[0].selectedImage = self?.ProfileImg.image
self?.tabBar.items?[0].image = self?.ProfileImg.image
}
}.resume()

OSX - Loading an image and saving it as a smaller png file using Swift

I am looking for a way to to upload an image file, resize it (in order to reduce the total file size), and then save it as a png. I think there must be a fairly straightforward way of doing this, but hours of searching around have yielded no effective results. I have been able to achieve the desired file size by exporting the image as a compressed JPEG, but I need to preserve transparency. Here is the code I used to get the JPEG:
func chooseImage () {
var image = NSImage()
//choose image from hard disk
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.runModal()
panel.allowedFileTypes = ["png", "jpeg", "jpg"]
let chosenFile = panel.URL
//convert to NSData and send to Jpeg function
if chosenFile != nil {
image = NSImage(contentsOfURL: chosenFile!)!
let imageData = image.TIFFRepresentation
self.saveAsJpeg(imageData!, compression: 0.5)
}
}
func saveAsJpeg (image:NSData, compression:NSNumber) {
// make imagerep and define properties
let imgRep = NSBitmapImageRep(data: image)
let props = NSDictionary.init(object: compression, forKey: NSImageCompressionFactor)
let pingy = imgRep?.representationUsingType(NSBitmapImageFileType.NSJPEGFileType, properties: props as! [String : AnyObject])
//save to disk
let documentURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let folderURL = documentURL.URLByAppendingPathComponent("KKNightlife Data")
let g = GetUniqueID()
let fileName = g.getUniqueID() + ".jpeg"
do {
try NSFileManager.defaultManager().createDirectoryAtURL(folderURL, withIntermediateDirectories: false, attributes: nil)
} catch {
print("cannot create directory - folder Exists?")
}
let url = folderURL.URLByAppendingPathComponent(fileName)
if let pid = pingy {
pid.writeToURL(url, atomically: false)
} else {
print("error saving image")
}
}
I attempted to use the following code to scale the image in order to create a smaller .png file, but no matter what values I enter for the size, the resulting file is the same size (both in terms of height/width and overall file size):
func chooseImage (size:String) {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.runModal()
panel.allowedFileTypes = ["png", "jpeg", "jpg"]
let chosenFile = panel.URL
if chosenFile != nil {
let image = NSImage(contentsOfURL: chosenFile!)
self.scaleImage(image!)
}
}
func scaleImage (image:NSImage) {
//create resized image
let newSize = NSSize(width: 10, height: 10)
var imageRect:CGRect = CGRectMake(0, 0, image.size.width, image.size.height)
let imageRef = image.CGImageForProposedRect(&imageRect, context: nil, hints: nil)
let resizedImage = NSImage(CGImage: imageRef!, size: newSize)
let imageData = resizedImage.TIFFRepresentation
//make imagerep
let imgRep = NSBitmapImageRep(data: imageData!)
let pingy = imgRep?.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [:])
//save to disk
let documentURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let g = GetUniqueID()
let fileName = g.getUniqueID() + ".png"
let folderURL = documentURL.URLByAppendingPathComponent("KKNightlife Data")
do {
try NSFileManager.defaultManager().createDirectoryAtURL(folderURL, withIntermediateDirectories: false, attributes: nil)
} catch {
print("cannot create directory - folder Exists?")
}
let url = folderURL.URLByAppendingPathComponent(fileName)
if let pid = pingy {
pid.writeToURL(url, atomically: false)
print("image is at \(documentURL)")
} else {
print("error saving image")
}
}
Any suggestions would be greatly appreciated.
I was finally able to do this using the extensions found here:
https://gist.github.com/raphaelhanneken/cb924aa280f4b9dbb480
This is how I ended up calling them in case anyone encounters similar problems:
func chooseImage (size:String) {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.runModal()
panel.allowedFileTypes = ["png", "jpeg", "jpg"]
let chosenFile = panel.URL
if chosenFile != nil {
let image = NSImage(contentsOfURL: chosenFile!)
self.scaleImageUsingExtensions(image!)
}
}
func scaleImageUsingExtensions (image:NSImage){
let size: NSSize = NSMakeSize(10, 10)
let resizedImage = image.resizeWhileMaintainingAspectRatioToSize(size)
let documentURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let g = GetUniqueID()
let fileName = g.getUniqueID() + ".png"
let folderURL = documentURL.URLByAppendingPathComponent("KKNightlife Data")
do {
try NSFileManager.defaultManager().createDirectoryAtURL(folderURL, withIntermediateDirectories: false, attributes: nil)
} catch {
print("cannot create directory - folder Exists?")
}
let url = folderURL.URLByAppendingPathComponent(fileName)
do {
try resizedImage?.savePNGRepresentationToURL(url)
}
catch {
print("error saving file")
}
}