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.
Related
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.
EDIT/UPDATE: So I've been messing around with this. When I copy a .mp3 to the UIPasteboard from iMessages on my Mac that I'm running the simulator on, the following code:
#IBAction func pasteboardButtonPressed(_ sender: Any) {
if UIPasteboard.general.hasStrings == true {
print("strings present")
}
if UIPasteboard.general.hasURLs == true {
print("URL present")
}
print("pasteboard items \(UIPasteboard.general.items)")
print("pasteboard count \(UIPasteboard.general.items.count)")
print("pasteboard type \(UIPasteboard.general.types)")
print("pasteboard URL \(UIPasteboard.general.url)")
}
}
yields the following output:
strings present
URL present
pasteboard items [["public.url": <OS_dispatch_data: data[0x600001ef9840] = { leaf, size = 116, buf = 0x1201b5000 }>, "public.file-url": <OS_dispatch_data: data[0x60000105c2c0] = { leaf, size = 116, buf = 0x1204f8000 }>]]
pasteboard count 1
pasteboard type ["public.url", "public.file-url"]
pasteboard URL nil
So, it seems that it's recognizing the .mp3 file on the pasteboard, but it's type is 'public.url' and won't be case as URL for me to store locally. Checking the Apple UIPasteboard Documentation it says "A Uniform Type Identifier (UTI) is frequently used as the key for a representation type. For example, you could use the kUTTypeJPEG UTI (a constant for public.jpeg) as a representation type key for JPEG data."
I'm assuming my 'constant' here is public.url, but I don't know what the equivalent of kUTTypeJPEG UTI representation key would be to cast the copied public.url as URL so that line
print("pasteboard URL \(UIPasteboard.general.url)")
doesn't result in nil?
ORIGINAL POST:
I've looked all over and can't find anything to even get me started, so I apologize for no code. But how would I allow a user to paste an .mp3 or .m4a audio file from their clipboard into a UITextField and then save that locally? I'm familiar with storing URL's and handling local AVAssets, just clueless on how I would pull a file type other than a string of text from the clipboard into a container to store locally. Any help is greatly appreciated!
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)
}
rowDescriptor is a AnyObject? type, I get convert self.rowDescriptor?.value to images. I want to add image to the self.rowDescriptor?.value. I failed to do it as below. images has one value. but self.rowDescriptor?.value still empty. I can't find any document about it. what's the reason about it.
var images :[UIImage] = self.rowDescriptor?.value as! [UIImage]
if let image = editedImage {
images.append(image)
} else {
images.append(originalImage!)
}
The array type in Swift is a struct. Structs in swift are value types not reference types. To clarify:
images contains a copy of self.rowDescriptor?.value
images.append( changes the copy images, not the original value in your rowDescriptor.value
You want to change self.rowDescriptor?.value so just set that to your newly changed images array.
self.rowDescriptor?.value = images
td;dr
To fix, add self.rowDescriptor?.value = images.