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!
Related
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 had this all working in Swift 3 and earlier but with Swift 4 no matter what variation I use this code will instead output text as a URL. If I put in "This is my sample text" the output after pasting the clipboard will be "This%20is%20my%20sample%20text". I have tried KuTTypeFileURL but that doesn't appear to make any difference either. What am I missing here? I have seen posts and discussions about how Apple is changing Pboards and other issues with sandboxing but I can't seem to figure this out at all.
original code what was working in swift 3 and earlier
private func copyToClipBoard(textToCopy: String) {
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.setString(textToCopy, forType: NSStringPboardType)
}
This gives an error of
'NSStringPboardType' is unavailable in Swift: use 'PasteboardType.string'
After searching online I came across these posts that describe the same issue and the workaround was to use the kuTTypeUrl as String
Found here stackoverflow.com/questions/44537356/… and here forums.developer.apple.com/thread/79144
When I try it this way it simply outputs as a URL when I just need a String.
#IBOutlet weak var nameTextField: NSTextField!
#IBAction func nameCopy(_ sender: Any) {
copyToClipBoard(textToCopy: nameTextField.stringValue)
}
let NSStringPboardType = NSPasteboard.PasteboardType(kUTTypeURL as String)
private func copyToClipBoard(textToCopy: String) {
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setString(textToCopy, forType: NSStringPboardType)
}
You are pasting an URL because you created a PasteboardType kUTTypeURL.
The solution is much simpler, there is a predefined string type
private func copyToClipBoard(textToCopy: String) {
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setString(textToCopy, forType: .string)
}
The note in the documentation
Apps that adopt App Sandbox cannot access files identified using the string pasteboard type. Instead, use an NSURL object, a bookmark, or a filename pasteboard type.
is related to files (aka string paths), not to regular strings
I just ran into a similar issue. My code looked like this:
NSPasteboard.general.setString("Hello World", forType: .string)
Unfortunately, this didn't work. But I figured there is a bug that if you don't store the NSPasteboard.general into a variable, the object created as part of the general computed property gets deinitialized before the setString change is propagated to the system.
So if you tried doing this in one line like me, just split it up to two instead, which worked for me:
let pasteboard = NSPasteboard.general
pasteboard.setString("Hello World", forType: .string)
I reported this bug via Feedback Assistant to Apple (FB9988062).
UPDATE:
Apple answered my bug report, stating that you need to call declareTypes before setting a value, like so:
NSPasteboard.general.declareTypes([.string], owner: nil)
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.
Lets assume that 3 strings have been copied on the UIPasteBoard:
UIPasteboard.generalPasteboard().string="hello"
UIPasteboard.generalPasteboard().string="world"
UIPasteboard.generalPasteboard().string="!"
I use
UIPasteboard.generalPasteboard().string=""
Will it clear the pasteboard? Is there any similar func for UIPasteBoard like there is clearContents() for NSPasteBoard?
If you know that your program is the only one manipulating the specific pasteboard, then yes, setting the string property to "" will effectively clear the pasteboard.
You can easily test this in Playground
var pb = UIPasteboard.generalPasteboard()
pb.string = "hello"
pb.string
pb.items
pb.string = ""
pb.string
pb.items
which outputs
<UIPasteboard: 0x7fed6bd0a750>
<UIPasteboard: 0x7fed6bd0a750>
"hello"
[["public.utf8-plain-text": "hello"]]
<UIPasteboard: 0x7fed6bd0a750>
nil
[[:]]
However, note that string property of UIPasteboard is a shorthand for the first pasteboard item that is of type string. All items of type string can be accessed through strings property.
All underlying pasteboard items are modelled in items property, which is an array of dictionaries of type [String: AnyObject]. Each dictionary contains the type information of an object in the key and pasteboard value in the value.
Because you are using a system-wide generalPasteboard, it can also be manipulated by other programs, thus, to clear all items from the pasteboard, you should use
UIPasteboard.generalPasteboard().items = []
If you are using the pasteboard for your internal application purposes, it is better to create an internal pasteboard than to use a system-wide generalPasteboard. See pasteboardWithUniqueName()