How to copy textField to OSX clipboard? - objective-c

I'm stuck here. I know how to copy and paste on the iPhone side of things but how can I copy contents from a textField to the global clipboard in OSX. I've been searching the web but there are really no examples. So let me explain in detail what I'm trying to accomplish. I have a NSTextField named helloField and I want to be able to copy the contents of this helloField to the global pasteboard by pressing a button. How can this be done and is there certain libraries I need? Thanks.

On iOS
[UIPasteboard generalPasteboard].string = helloField.text;
On OSX
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:helloField.stringValue forType:NSStringPboardType];
On macOS and Swift 3.x
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.writeObjects([text as NSString])

For Swift 5
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString("string to copy", forType: .string)

Code to copy a string to the clipboard:
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:copiedString forType:NSPasteboardTypeString];
NSStringPboardType is deprecated. There's a note in NSPasteboard.h about pboard types:
Use of pboard types should be replaced with use of UTIs. Pboard types will be deprecated in a future release.
Also in the header file:
APPKIT_EXTERN NSString *const NSPasteboardTypeString NS_AVAILABLE_MAC(10_6); // Replaces NSStringPboardType
...
APPKIT_EXTERN NSString *NSStringPboardType; //Use NSPasteboardTypeString

For Cocoa macOS in Swift 3:
let pasteBoard = NSPasteboard.general()
pasteBoard.clearContents()
pasteBoard.setString("something", forType: NSPasteboardTypeString)

You can create an extension for your String which supports iOS and macOS:
extension String {
func copy() {
#if os(macOS)
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(self, forType: .string)
#else
UIPasteboard.general.string = self
#endif
}
}

Clipboard.set("some text")
class:
import AppKit
public class Clipboard {
public static func set(text: String?) {
if let text = text {
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setString(text, forType: .string)
}
}
#available(macOS 10.13, *)
public static func set(url: URL?) {
guard let url = url else { return }
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.setData(url.dataRepresentation, forType: .URL)
}
#available(macOS 10.13, *)
public static func set(urlContent: URL?) {
guard let url = urlContent,
let nsImage = NSImage(contentsOf: url)
else { return }
let pasteBoard = NSPasteboard.general
pasteBoard.clearContents()
pasteBoard.writeObjects([nsImage])
}
public static func clear() {
NSPasteboard.general.clearContents()
}
}

Related

again convert textKit from Objective-C to Swift

The following code was used in my app to change the state for text in a textview with strikeThrough. Now i wrote a small sample-app, in Objective-C and Swift. Again the result is frustrating as u can see in the screenshots. Any help is welcome so much.
I just use a TextView and try to show some text with StrikeThrough-Layout (other styles like Bold, Italic, Underline... have the same result)
First objc, that is ok, although the font-size of the striked part is very small
and now with Swith. The font is small as with objc, but there is no strikethrough :-)
And now again (dont know another way) the test-code:
objc Part 1: set the Font for a Range and call makeStrikeThrough()
- (void) setFont
{
NSRange range = NSMakeRange(11, 24);
[self makeStrikeThrough:range];
}
same in swift:
func setFont() {
let range = NSMakeRange(11, 24)
self.makeStrikeThrough(range)
}
objc Part 2: the strikeThrough
- (void) makeStrikeThrough:(NSRange)selectedRange
{
NSMutableDictionary *dict = [self getDict:selectedRange];
[_textView.textStorage beginEditing];
[_textView.textStorage setAttributes:dict range:selectedRange];
[_textView.textStorage endEditing];
}
and in Swift:
func makeStrikeThrough(selectedRange: NSRange) {
let dict = self.getDict(selectedRange)
self.textView.textStorage.beginEditing()
textView.textStorage.setAttributes([String() : dict], range: selectedRange)
self.textView.textStorage.endEditing()
}
objc Part 3: the help-method getDict() to buid a dictionary with the StrikeThrough
- (NSMutableDictionary*) getDict:(NSRange)range
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:[NSNumber numberWithInt:2] forKey:NSStrikethroughStyleAttributeName];
return dict;
}
and again in Swift
func getDict(range: NSRange) -> NSMutableDictionary {
let dict = NSMutableDictionary()
dict[NSStrikethroughStyleAttributeName] = NSNumber(integer: NSUnderlineStyle.StyleDouble.rawValue)
return dict
}
I´ve tried to reduce the problem to the root. Perhaps u vote me down ;-)
But i need a solution...
Or should i use AttributedStrings?
The procts to download
objc
swift
It boils down to one line of code in your Swift function:
func makeStrikeThrough(selectedRange: NSRange) {
let dict = self.getDict(selectedRange)
self.textView.textStorage.beginEditing()
textView.textStorage.setAttributes([String() : dict], range: selectedRange) // error
self.textView.textStorage.endEditing()
}
it should have been just dict:
func makeStrikeThrough(selectedRange: NSRange) {
let dict = self.getDict(selectedRange)
self.textView.textStorage.beginEditing()
textView.textStorage.setAttributes(dict, range: selectedRange)
self.textView.textStorage.endEditing()
}
And you need to change your getDict function too:
func getDict() -> [String: AnyObject] {
return [NSStrikethroughStyleAttributeName: 2]
}

Append to NSTextView and scroll

OK, what I need should have been very simple. However, I've looked everywhere and I'm not sure I've found something that works 100% (and it's something that has troubled me in the past too).
So, here we are :
I want to be able to append to an NSTextView
After appending, the NSTextView should scroll down (so that that latest appended contents are visible)
Rather straightforward, huh?
So... any ideas? (A code example that performs exactly this simple "trick" would be more than ideal...)
After cross-referencing several answers and sources (with some tweaks), here's the answer that does work (given _myTextView is an NSTextView outlet) :
- (void)appendToMyTextView:(NSString*)text
{
dispatch_async(dispatch_get_main_queue(), ^{
NSAttributedString* attr = [[NSAttributedString alloc] initWithString:text];
[[_myTextView textStorage] appendAttributedString:attr];
[_myTextView scrollRangeToVisible:NSMakeRange([[_myTextView string] length], 0)];
});
}
The appendAttributedString and scrollToEndOfDocument are available starting in OS X 10.0 and 10.6 respectively
extension NSTextView {
func append(string: String) {
self.textStorage?.appendAttributedString(NSAttributedString(string: string))
self.scrollToEndOfDocument(nil)
}
}
Simply use this way :
for (NSInteger i=1; i<=100; i++) {
[self.textView setString:[NSString stringWithFormat:#"%#\n%#",[self.textView string],#(i)]];
}
[self.textView scrollRangeToVisible:NSMakeRange([[self.textView string] length], 0)];
Here's a Swift version of Anoop Vaidya's answer
extension NSTextView {
func append(string: String) {
let oldString = self.string == nil ? "" : self.string!
let newString = NSString(format: "%#%#", oldString, string)
self.string = newString
}
}
Here's a Swiftier solution:
extension NSTextView {
func appendString(string:String) {
self.string! += string
self.scrollRangeToVisible(NSRange(location:countElements(self.string!), length: 0))
}
}

Check NSURL for UTI / file type

I'm building an app that allows users to drop videos onto it. Given a list of dropped NSURL*s how do I make sure each one conforms to the public.movie UTI type?
If I had an NSOpenPanel, I would just use openPanel.allowedFileTypes = #[#"public.movie"]; and Cocoa would take care of it for me.
Thanks in advance!
This should work:
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
for (NSURL *url in urls) {
NSString *type;
NSError *error;
if ([url getResourceValue:&type forKey:NSURLTypeIdentifierKey error:&error]) {
if ([workspace type:type conformsToType:#"public.movie"]) {
// the URL points to a movie; do stuff here
}
} else {
// handle error
}
}
(You can also use UTTypeConformsTo() instead of the NSWorkspace method.)
Swift version:
do {
var value: AnyObject?
try url.getResourceValue(&value, forKey:NSURLTypeIdentifierKey)
if let type = value as? String {
if UTTypeConformsTo(type, kUTTypeMovie) {
...
}
}
}
catch {
}
Swift 5 version:
if let resourceValues = try? localUrl.resourceValues(forKeys: [URLResourceKey.typeIdentifierKey]) {
if let typeId = resourceValues.typeIdentifier {
if UTTypeConformsTo(type, kUTTypeMovie) {
...
}
}
}

How to retrieve values from settings.bundle in Objective-c/Swift?

I have created a project that set and retrieve values from settings.bundle. I have also set some defaults values in settings.bundle file. Now the problem is when I retrieve values as
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
loginName.text = [defaults objectForKey:#"login_name"];
for the first time it shows null, but the values get set in iPhone application settings.
If I change the values or set it manually, then values are retrieved properly.
Help me out
Although you define the defaults settings, they are not really stored as a value. They are stored as default. If you try to read it, the value is null. Default setting is another property as value is. But it doesnt mean that will write the default value as a default.
What I do is, first, check if some setting,(that I'm sure that should have a value) has anything stored on it. If it doesn't have anything then I write all the defaults.
Here is an example.
on AppDelegate.m I check if email_notificaciones_preference has a value, if not, I write ALL default settings to each setting.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSUserDefaults * standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString * email_notificaciones_preference = [standardUserDefaults objectForKey:#"email_notificaciones_preference"];
if (!email_notificaciones_preference) {
[self registerDefaultsFromSettingsBundle];
}
}
This function is what I use to write defaults to each element.
#pragma NSUserDefaults
- (void)registerDefaultsFromSettingsBundle {
// this function writes default settings as settings
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
if(!settingsBundle) {
NSLog(#"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:#"Root.plist"]];
NSArray *preferences = [settings objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:#"Key"];
if(key) {
[defaultsToRegister setObject:[prefSpecification objectForKey:#"DefaultValue"] forKey:key];
NSLog(#"writing as default %# to the key %#",[prefSpecification objectForKey:#"DefaultValue"],key);
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}
Hope that helps.
If anyone needs it - I translated the answer from MIQUEL to Swift (as good as I could as I'm still learning) :
var standardUserDefaults = NSUserDefaults.standardUserDefaults()
var us: AnyObject? = standardUserDefaults.objectForKey("your_preference")
if us==nil {
self.registerDefaultsFromSettingsBundle();
}
And the func registerDefaultsFromSettingsBundle:
func registerDefaultsFromSettingsBundle() {
// this function writes default settings as settings
var settingsBundle = NSBundle.mainBundle().pathForResource("Settings", ofType: "bundle")
if settingsBundle == nil {
NSLog("Could not find Settings.bundle");
return
}
var settings = NSDictionary(contentsOfFile:settingsBundle!.stringByAppendingPathComponent("Root.plist"))!
var preferences: [NSDictionary] = settings.objectForKey("PreferenceSpecifiers") as [NSDictionary];
var defaultsToRegister = NSMutableDictionary(capacity:(preferences.count));
for prefSpecification:NSDictionary in preferences {
var key: NSCopying? = prefSpecification.objectForKey("Key") as NSCopying?
if key != nil {
defaultsToRegister.setObject(prefSpecification.objectForKey("DefaultValue")!, forKey: key!)
}
}
NSUserDefaults.standardUserDefaults().registerDefaults(defaultsToRegister);
}
Updated for Swift 3:
func registerDefaultsFromSettingsBundle() {
let userDefaults = UserDefaults.standard
if let settingsURL = Bundle.main.url(forResource: "Root", withExtension: "plist", subdirectory: "Settings.bundle"),
let settings = NSDictionary(contentsOf: settingsURL),
let preferences = settings["PreferenceSpecifiers"] as? [NSDictionary] {
var defaultsToRegister = [String: AnyObject]()
for prefSpecification in preferences {
if let key = prefSpecification["Key"] as? String,
let value = prefSpecification["DefaultValue"] {
defaultsToRegister[key] = value as AnyObject
debugPrint("registerDefaultsFromSettingsBundle: (\(key), \(value)) \(type(of: value))")
}
}
userDefaults.register(defaults: defaultsToRegister)
} else {
debugPrint("registerDefaultsFromSettingsBundle: Could not find Settings.bundle")
}
}
Updated version for swift 2.1:
func registerDefaultsFromSettingsBundle() {
let userDefaults = NSUserDefaults.standardUserDefaults()
if let settingsURL = NSBundle.mainBundle().URLForResource("Root", withExtension: "plist", subdirectory: "Settings.bundle"),
settings = NSDictionary(contentsOfURL: settingsURL),
preferences = settings["PreferenceSpecifiers"] as? [NSDictionary] {
var defaultsToRegister = [String: AnyObject]()
for prefSpecification in preferences {
if let key = prefSpecification["Key"] as? String,
value = prefSpecification["DefaultValue"] {
defaultsToRegister[key] = value
NSLog("registerDefaultsFromSettingsBundle: (\(key), \(value)) \(value.dynamicType)")
}
}
userDefaults.registerDefaults(defaultsToRegister);
} else {
NSLog("registerDefaultsFromSettingsBundle: Could not find Settings.bundle");
}
}
You can use a simple property wrapper like this:
Usage
#SettingsBundleStorage(key: "storageUsage_preference")
var storageUsage: Double
Note that this is 100% objective-c compatible by just adding #objc before the variable.
Implementation of the code behind this:
Settings bundle values are live in the UserDefaults so you can use a custom PropertyWrapper for it. The following wrapper will work for any UserDefault value, including all values of the SettingsBundle.
Property wrapper
#propertyWrapper
public struct SettingsBundleStorage<T> {
private let key: String
public init(key: String) {
self.key = key
setBundleDefaults(plist: .root) // This is the main plist
setBundleDefaults(plist: .child(name: "DeveloperOptions")) // This is an example child.
}
public var wrappedValue: T {
get { UserDefaults.standard.value(forKey: key) as! T }
set { UserDefaults.standard.set(newValue, forKey: key) }
}
}
The root and the children
You should pass the following enum for the root and the child plists:
extension SettingsBundleStorage {
enum PList {
case root
case child(name: String)
var name: String {
var file: String
switch self {
case .root: file = "Root"
case .child(let name): file = name.replacingOccurrences(of: ".plist", with: "")
}
file.append(".plist")
return file
}
}
}
Find and set defaults if needed.
This wrapper finds the default value of the bundle keys with this function:
extension SettingsBundleStorage {
func setBundleDefaults(plist: PList = .root) {
let settingsName = "Settings"
let settingsExtension = "bundle"
let settingsPreferencesItems = "PreferenceSpecifiers"
let settingsPreferenceKey = "Key"
let settingsPreferenceDefaultValue = "DefaultValue"
guard let settingsBundleURL = Bundle.main.url(forResource: settingsName, withExtension: settingsExtension),
let settingsData = try? Data(contentsOf: settingsBundleURL.appendingPathComponent(plist.name)),
let settingsPlist = try? PropertyListSerialization.propertyList(
from: settingsData,
options: [],
format: nil) as? [String: Any],
let settingsPreferences = settingsPlist?[settingsPreferencesItems] as? [[String: Any]] else {
return assertionFailure("Can not get the \(plist.name) from the bundle: \(settingsName)")
}
var defaultsToRegister = [String: Any]()
settingsPreferences.forEach { preference in
if let key = preference[settingsPreferenceKey] as? String {
defaultsToRegister[key] = preference[settingsPreferenceDefaultValue]
}
}
UserDefaults.standard.register(defaults: defaultsToRegister)
}
}
This wrapper can store/restore any kind of codable into/from the user defaults including all Swift standard data types that are already conformed to the codable.
Also, you can find a similar but with way less code version for accessing any key-value from any user default, you can take a look at this answer here
try this
To register Default Values of Setting bundles
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:defaultValue forKey:#"key"];
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
Before retrieving setting bundles values synchronize data
[[NSUserDefaults standardUserDefaults] synchronize]

How to programmatically open an NSComboBox's list?

I've been around this for a while.. I thought this should be an easy task, but it isn't =D
What I am trying to do, is to display the combobox's list when the user clicks the combobox but not specifically in the button.
Any Idea?
Thanks in advance!
This answer fits the title of the question, but not question itself. Omer wanted to touch a text field and have the box popup.
This solution shows the popup when the user enters text.
I found this answer on cocoabuilder from Jens Alfke. I reposted his code here. Thanks Jens.
original cocoabuilder post: (http://www.cocoabuilder.com/archive/cocoa)
#interface NSComboBox (MYExpansionAPI)
#property (getter=isExpanded) BOOL expanded;
#end
#implementation NSComboBox (MYExpansionAPI)
- (BOOL) isExpanded
{
id ax = NSAccessibilityUnignoredDescendant(self);
return [[ax accessibilityAttributeValue:
NSAccessibilityExpandedAttribute] boolValue];
}
- (void) setExpanded: (BOOL)expanded
{
id ax = NSAccessibilityUnignoredDescendant(self);
[ax accessibilitySetValue: [NSNumber numberWithBool: expanded]
forAttribute: NSAccessibilityExpandedAttribute];
}
I used this code in my controlTextDidChange: method.
- (void) controlTextDidChange:(NSNotification *) aNotification {
NSTextField *textField = [aNotification object];
NSString *value = [textField stringValue];
NSComboBox *box = [self comboBox];
if (value == nil || [value length] == 0) {
if ([box isExpanded]) { [box setExpanded:NO]; }
} else {
if (![box isExpanded]) { [box setExpanded:YES]; }
}
}
Returns true if the NSComboBox's list is expanded
comboBox.cell?.isAccessibilityExpanded() ?? false
Open the NSComboBox's list
comboBox.cell?.setAccessibilityExpanded(true)
Close the NSComboBox's list
comboBox.cell?.setAccessibilityExpanded(false)
Ref. jmoody’s answer.
You can use the following code line:
[(NSComboBoxCell*)self.acomboBox.cell performSelector:#selector(popUp:)];
Put
comboBoxCell.performSelector(Selector("popUp:"))
Into
override func controlTextDidChange(obj: NSNotification) {}
is what I ended up with. Thanks #Ahmed Lotfy
Here's the full code, it works for me on OSX 10.11
override func controlTextDidChange(obj: NSNotification) {
if let comboBoxCell = self.comboBox.cell as? NSComboBoxCell {
comboBoxCell.performSelector(Selector("popUp:"))
}
}
Thanks to jmoody and Jens Alfke mentioned above. Here is a SWIFT translation of the above solution.
import Cocoa
class CComboBoxEx: NSComboBox {
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
}
func isExpanded() -> Bool{
if let ax:AnyObject? = NSAccessibilityUnignoredDescendant(self) {
if ax!.accessibilityAttributeValue(NSAccessibilityExpandedAttribute) != nil {
return true
}
}
return false
}
func setExpanded (bExpanded:Bool) {
if let ax:AnyObject? = NSAccessibilityUnignoredDescendant(self) {
ax!.accessibilitySetValue(NSNumber(bool: bExpanded), forAttribute: NSAccessibilityExpandedAttribute)
}
}
}
NSComboBox was not designed to work this way. Because the user may want to edit the text in the control, they'll need to be able to click it without unexpectedly popping up the choices.
You would need to subclass NSComboBoxCell and change this behavior ... but then you'd have a standard-looking control that does not behave in a standard way. If you're determined to do this, take a look at the open source version of NSComboBoxCell. The interesting methods appear to be -popUpForComboBoxCell: and friends.
Based on the other answers I wrote this solution (tested with Xcode 10.2.1, Swift 5). It uses the same ideas but it's a little shorter.
// Put this extension for NSComboBox somewhere in your project
import Cocoa
public extension NSComboBox {
var isExpanded: Bool{
set {
cell?.setAccessibilityExpanded(newValue)
}
get {
return cell?.isAccessibilityExpanded() ?? false
}
}
}
// Set your corresponding NSViewController as NSComboBoxDelegate
// in the storyboard and add this piece of code
// to expand the combobox when the user types
class MyViewController: NSViewController, NSComboBoxDelegate {
func controlTextDidChange(_ notification: Notification) {
guard let comboBox = notification.object as? NSComboBox else { return }
if comboBox.isExpanded == false {
comboBox.isExpanded = true
}
}
}