I am trying to get file name from PHAsset reference provided by UIImagePickerController,How do I retrieve the image file name when choose from library and take photo with camera?
I am using Xcode 11.4 and Swift 5.
Given, you already have the PHAsset object, PHAssetResource is what you need to work retrieve the filename.
let assetResources = PHAssetResource.assetResources(for: asset)
This will return you an array of PHAssetResource instances, among which one of them is the current image.
Inspecting the PHAssetResource instances on the debugger, you can see it contains multiple files. This may include adjustment data, represented by .plist, and one or more photos.
To get the file that is currently accessible, what you need to look for is a key isCurrent. Apple hasn't exposed this key as a variable for PHAssetResource, however, they don't take any effort hiding the value. And I assume, querying this value doesn't result in an app rejection.
You can query this value directly, or can do it more cleanly by extending PHAssetResource.
extension PHAssetResource {
var isCurrent: Bool? {
self.value(forKey: "isCurrent") as? Bool
}
}
Now, you can ask the [PHAssetResource], for the first item that isCurrent as well as a photo or video.
let resourceTypes: [PHAssetResourceType] = [.photo, .video, .audio, .fullSizePhoto]
let currentAssetResource = assetResources.first(
where: { $0.isCurrent == true && resourceTypes.contains($0.type)
})
And, finally for the filename.
let fileName = currentAssetResource?.originalFilename
Note
PhotoKit expects the developer to work with the abstract asset resource, and not to deal with the underlying files or data. The usage of isCurrent seems to be allowed. However, the value can be altered or removed with an iOS update. Do it only if you have a strong use-case for extracting the filename.
There are two approaches:
You can retrieve the URL of the temporary file that UIImagePickerController creates for you using info[.imageURL]. If you only need, for example, the extension of the asset, this is sufficient.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
// url of temporary file provided by image picker
if let url = info[.imageURL] as? URL {
print("temp URL", url) // file:///private/var/mobile/Containers/Data/Application/.../tmp/FAC0D82B-85A9-4C7A-B69C-58BB53F74CDC.jpeg
}
dismiss(animated: true)
}
But the filename, itself, bears no relationship to the original asset.
You can retrieve the original asset URL from the Photos framework. We used to be able to use PHImageManager, call requestImageDataForAsset, and then use "PHImageFileURLKey" in the resulting info dictionary, but that doesn’t work anymore.
But as suggested elsewhere, you can asset.requestContentEditingInput, and that includes the URL of the asset:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
// url of the original asset
if let asset = info[.phAsset] as? PHAsset {
asset.requestContentEditingInput(with: nil) { input, info in
if let fileURL = input?.fullSizeImageURL {
print("original URL", fileURL) // file:///var/mobile/Media/DCIM/100APPLE/IMG_0135.HEIC
}
}
}
dismiss(animated: true)
}
If you do this, you obviously need to request permissions for the photos library, notably:
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization { granted in
print(granted)
}
}
And you need to supply a NSPhotoLibraryUsageDescription usage string in your Info.plist.
Personally, it feels wrong to request content editing input when you don’t really need to edit, but AFAIK, that’s the only way to get the original asset name nowadays. (There’s got to be a better way: If you browse the PHAsset in the debugger, the asset name is actually buried in there, but not exposed ... maybe they did that to avoid confusion with the HEIC extension.)
Bottom line, it just depends upon what you need the name for. If you just need the path for the temporary file, then use .imageURL. If you need information about the original asset, use .phAsset and the Photos/PhotoKit framework.
Related
I am having difficulty grabbing a fileURL in a DropDelegate in SwiftUI, when dragging a file from my desktop into my app.
I am using the async loadItem call on NSItemProvider which returns an NSSecureCoding. I assumed that I could cast the result to an NSURL but it always fails.
let url = try await (urlProvider
.loadItem(forTypeIdentifier: UTType.url.identifier) as? NSURL
If I try to force the cast I get the following error:
Could not cast value of type 'Foundation.__NSSwiftData' to 'NSURL'.
I am successfully using the same call and casting to an NSDictionary elsewhere in my code.
This is in Xcode 13.4, Swift 5.
I have read access to user selected files in my sandbox capabilities.
Can anyone help point out what I am doing wrong?
Thank you.
I had to cast to Data and then construct the URL from that:
if let data = try await urlProvider
.loadItem(forTypeIdentifier: UTType.fileURL.identifier) as? Data {
url = URL(dataRepresentation: data, relativeTo: nil) }
I'm a total beginner to OSX GUI programming, so please be gentle with me.
I'm trying some experiments with adding light GUI elements from appkit to a CLI, so I'm working on a very small program to take the contents of a PDF and save it to a text file.
Here's the code I have
import AppKit
import Foundation
import Quartz
func helperReadPDF(_ filename: String) -> String {
let pdata = try! NSData(contentsOfFile: filename) as Data
let pdf = PDFDocument(data: pdata)
return pdf!.string!
}
func selectFile() -> URL? {
let dialog = NSOpenPanel()
dialog.allowedFileTypes = ["pdf"]
guard dialog.runModal() == .OK else { return nil }
return dialog.url
}
func getSaveLocation() -> URL? {
let sa = NSSavePanel()
sa.nameFieldStringValue = "Untitled.txt"
sa.canCreateDirectories = true
sa.allowedFileTypes = ["txt"]
guard sa.runModal() == .OK else { return nil }
return sa.url
}
let file = selectFile()?.path ?? ""
print("where to save?")
let dest = getSaveLocation()!
try! helperReadPDF(file).write(to: dest, atomically: true, encoding: .utf8)
(I know, there are lots of unidiomatic things in here, like all the forced unwrapping and pointlessly converting URLs to paths. I have obscure reasons...)
So this code mostly works: when I run it from a terminal window with swift guitest.swift it'll pop up a file picker window, let me select a pdf file, and then pop up a save dialogue and let me choose the directory, and then save the extracted text from the pdf into that directory.
But it won't let me change the filename. I can highlight the "Untitled.txt" provided by default, I can even get a cursor into the field... but it doesn't respond to keyboard input.
In this previous SO, someone suggested adding a nameFieldStringValue to make it editable, but, as you can see from the above code, I did that, and it doesn't work.
I see from this very old SO that at least in Objective-C-land, you have to initiate some kind of application object in order to accept keyboard input. Is that true today in Swift-land as well?
(Even though for some weird reason you can accept mouse input without doing any of that?!) If so, how do I do that here?
Edit: I get from the comments to that last prior SO I linked that this is probably a terrible idea, and that if I want to learn Mac GUI programming I should do it the heavy way with XCode and storyboards and all the rest. But could you indulge my doing it the stupid way in an effort to try to learn one thing at a time? (I.e., learn the GUI APIs on offer without also trying to learn XCode and Apple's preferred style of architecture at the same time.)
Thanks!
(Swift 4.2 on latest version of OSX. Not using XCode at all.)
Setting the application's ActivationPolicy will make it work.
// Import statements... (import Quartz)
NSApplication.shared.setActivationPolicy(.accessory)
// Functions and so on... (func helper..)
I'm trying to copy multiple NSImages to the Pasteboard using Swift 4 - like this:
private func putItemsOnPasteboard (_ items: [Int]) {
if let images = mainImageController?.getNSImages(for: items) {
NSPasteboard.general.clearContents()
NSPasteboard.general.writeObjects(images)
if let c = NSPasteboard.general.pasteboardItems?.count {
print (c)
}
}
}
NSImage implements NSPasteboardWriting, and it works fine. The snag is, if I then paste into, say, Mail, it only pastes the first image, and I'm struggling to figure out why.
For instance, if I call the function with three images, it all appears to work, and my diagnostic 'print (c)' correctly shows '3'. But if I paste into Mail (or OneNote etc.) it only paste the first image.
I know Mail supports pasting multiple images, because I can select three thumbnails in Apple Photos, copy them to the pasteboard, and it correctly pastes all three into Mail.
Any clues would be gratefully appreciated!
If I copy a group of photos in Photos, and then inspect the pasteboard types, I get:
import Cocoa
print(NSPasteboard.general.types?.map { $0.rawValue } ?? [])
outputs:
["dyn.ah62d4rv4gu8ywyc2nbu1g7dfqm10c6xekr1067dwr70g23pw", "IPXPasteboardController", "com.apple.PhotoPrintProduct.photoUUID", "public.file-url", "CorePasteboardFlavorType 0x6675726C", "dyn.ah62d4rv4gu8y6y4grf0gn5xbrzw1gydcr7u1e3cytf2gn", "NSFilenamesPboardType", "dyn.ah62d4rv4gu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu", "Apple URL pasteboard type"]
Of those types, the one that looks interesting to me is public.file-url, which suggests that Photos is copying a group of URLs onto the pasteboard. Let's test that hypothesis:
import Cocoa
print(NSPasteboard.general.readObjects(forClasses: [NSURL.self], options: nil) ?? [])
outputs:
[file:///Users/*deleted*/Pictures/Photos%20Library.photoslibrary/resources/proxies/derivatives/1e/00/1e03/UNADJUSTEDNONRAW_thumb_1e03.jpg, file:///Users/*deleted*/Pictures/Photos%20Library.photoslibrary/resources/proxies/derivatives/1e/00/1e04/UNADJUSTEDNONRAW_thumb_1e04.jpg, file:///Users/*deleted*/Pictures/Photos%20Library.photoslibrary/resources/proxies/derivatives/1e/00/1e05/kOBCUhzGRcyeVfBCC8VfvQ_thumb_1e05.jpg]
The output is a list of URLs to JPEG files, suggesting that Photos is indeed providing a list of URLs and putting those on the pasteboard. So perhaps you could save the TIFF representations of your NSImages to a temporary location, and then write the URLs to those temporary files onto the pasteboard, and it would work.
Charles Srstka's answer was very helpful here. It appears that though you can put multiple NSImages on the Pasteboard, you can only paste one.
Swift's URL object doesn't comply with NSPasteboardWriting, but NSURL does, and it doesn't have the restriction of being only able to paste one.
So this worked
private func putImagesOnPasteboard (_ images: [Image], folderURL: URL) {
let imageURLs = images.map() {
image in
return NSURL (fileURLWithPath: folderURL.appendingPathComponent(image.fileName).path)
}
NSPasteboard.general.clearContents()
NSPasteboard.general.writeObjects(imageURLs)
}
I have a set of images inside an .xcassets folder.
Using iOS 11 and Xcode 9 I access them with
func searchCodes() -> NSArray {
let file = Bundle.main.url(forResource: "PickerStations", withExtension: "plist")
return NSArray(contentsOf: file!)!
}
lineCode = searchCodes().object(at: 1) as? NSDictionary
//lineFirstInitial is either C,H,J,N,P,D,M,V,B or W
let lineNameFromInitial = lineCode?.allKeys(for: lineFirstInitial).first as! String
//lineNameFromInitial is 100% correct value and loaded from an array of Strings.
//Looped through and each value added to the code below.
if let lineLabelImage = UIImage(named: lineNameFromInitial) {
lineLabelImageView.image = lineLabelImage
}
Some Images enter the block and yet others do not as they return nil. However the images are 100% in the .xcassets and the file name is 100% correct. I have tried hardcoding the images name instead of passing the variable lineNameFromInitial but the images are still nil for some despite actually existing.
The images are found using the keys in a plist
Any suggestions?
Two reasons that might be the reason for the images being nil:
1. The images have not been added to your app's target.
2. The images have a name with special characters:
Xcode does not like assets that have special characters like ÄÖÜ etc. in their name. Have a look at the problematic images' names and change those characters to plain old english characters.
i currently developpe an app which request data from a webservice, and one the datas are retrieve, i call a method to create some "swype" card like tinder.
The problem is, i do not success to call my method in the viewDidload, this is my code :
if(success == 1) {
NSLog("Recherche OK");
//Call the swype method
}
This is my method :
func fetchData(int: Int, completion: (()->())?) {
let newCard = Model()
//ttp://thecatapi.com/api/images/get?format=src&type=png
if let url = NSURL(string: "http://test.com/api/images/get?format=src&type=png") {
if let data = NSData(contentsOfURL: url) {
newCard.image = UIImage(data: data)
newCard.content = "test"
newCard.desc = "test"
self.data.append(newCard)
NSLog("fetch new data")
}
Have you got an idea ?
Please read more about getting NSData from a network call. Following is from a Apple Documentation
Do not use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
Instead, for non-file URLs, consider using the
dataTaskWithURL:completionHandler: method of the NSSession class. See
URL Loading System Programming Guide for details.
Also I noticed that the url you are showing redirects, so maybe that is the problem. Instead of trying to load it in a UIImage, you can create a UIWebview and show the image there.
You can find Swift related help regarding that in answer here