How to block some keyboard layouts in NSTextField? - macos

Is there a way to block some keyboard layouts (input sources) in NSTextField.
I need to block all non-romans languages such as Russian, Belorussian, Ukraine and etc or disable all languages and enable only English/Deutsch language.
If it will be not so hard - make some example please.
UPD:
I think i need to use this
but how? =)

Checking just the keyboard attached is maybe a bit flakey. With the Option key you can input a lot non-Roman characters from any keyboard, for instance. Not to mention copy and paste.
A better approach would be to make a subclass of NSFormatter and implement isPartialStringValid:proposedSelectedRange:originalString:originalSelectedRange:errorDescription:
A simple implementation could be something like this:
- (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error
{
NSString *partialString = *partialStringPtr;
NSCharacterSet *acceptedCharacters = [NSCharacterSet characterSetWithCharactersInString: #"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"];
NSCharacterSet *notAcceptedCharacters = [acceptedCharacters invertedSet];
BOOL needsCheck = YES;
BOOL didChange = NO;
do {
NSRange rng = [partialString rangeOfCharacterFromSet:notAcceptedCharacters];
if ( !NSEqualRanges(rng, NSMakeRange(NSNotFound, 0)) ) {
partialString = [partialString stringByReplacingCharactersInRange:rng withString:#""];
didChange = YES;
}
else {
needsCheck = NO;
}
} while (needsCheck);
if ( didChange ) {
*partialStringPtr = partialString;
NSRange newRange = origSelRange;
newRange.length = 0;
*proposedSelRangePtr =newRange;
return NO;
}
return YES;
}
When subclassing NSFormatter you are also required to implement stringForObjectValue: and getObjectValue:forString:errorDescription:, but since you are inputting a string, they can just pass the input string straight through.

Related

Validating URL from drag and drop in a sandbox

With file access in a sandboxed osx app with swift in mind, does it work the same with URLs provided via Finder or other apps drops?
As there's no NSOpenPanel call to afford folder access as in this example, just urls - I think the folder access is implicit since the user dragged the file from the source / desktop "folder" much the same as implicit selection via the open dialog.
I have not begun the sandbox migration yet but wanted to verify my thinking was accurate, but here's a candidate routine that does not work in sandbox mode:
func performDragOperation(_ sender: NSDraggingInfo!) -> Bool {
let pboard = sender.draggingPasteboard()
let items = pboard.pasteboardItems
if (pboard.types?.contains(NSURLPboardType))! {
for item in items! {
if let urlString = item.string(forType: kUTTypeURL as String) {
self.webViewController.loadURL(text: urlString)
}
else
if let urlString = item.string(forType: kUTTypeFileURL as String/*"public.file-url"*/) {
let fileURL = NSURL.init(string: urlString)?.filePathURL
self.webViewController.loadURL(url: fileURL!)
}
else
{
Swift.print("items has \(item.types)")
}
}
}
else
if (pboard.types?.contains(NSPasteboardURLReadingFileURLsOnlyKey))! {
Swift.print("we have NSPasteboardURLReadingFileURLsOnlyKey")
}
return true
}
as no URL is acted upon or error thrown.
Yes, the file access is implicit. As the sandbox implementation is poorly documented and had/has many bugs, you want to work around URL and Filenames. The view should register itself for both types at initialisation. Code is in Objective-C, but API should be the same.
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]];
Then on performDragOperation:
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
BOOL dragPerformed = NO;
NSPasteboard *paste = [sender draggingPasteboard];
NSArray *typesWeRead = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil];
//a list of types that we can accept
NSString *typeInPasteboard = [paste availableTypeFromArray:typesWeRead];
if ([typeInPasteboard isEqualToString:NSFilenamesPboardType]) {
NSArray *fileArray = [paste propertyListForType:#"NSFilenamesPboardType"];
//be careful since this method returns id.
//We just happen to know that it will be an array. and it contains strings.
NSMutableArray *urlArray = [NSMutableArray arrayWithCapacity:[fileArray count]];
for (NSString *path in fileArray) {
[urlArray addObject:[NSURL fileURLWithPath:path]];
}
dragPerformed = //.... do your stuff with the files;
} else if ([typeInPasteboard isEqualToString:NSURLPboardType]) {
NSURL *droppedURL = [NSURL URLFromPasteboard:paste];
if ([droppedURL isFileURL]) {
dragPerformed = //.... do your stuff with the files;
}
}
return dragPerformed;
}

NSSharingService - Remove facebook and twitter and add Print

I've just implemented a share-button, that has a share menu:
[_shareButton sendActionOn:NSLeftMouseDownMask];
And has this action connected:
-(IBAction)share:(id)sender {
NSArray *shareArray = #[#"testShare"];
NSSharingServicePicker *sharingServicePicker = [[NSSharingServicePicker alloc] initWithItems:shareArray];
sharingServicePicker.delegate = self;
[sharingServicePicker showRelativeToRect:[sender bounds]
ofView:sender
preferredEdge:NSMinYEdge];
}
Now to my question, I don't want Facebook and Twitter to be an option in the menu. I only want E-Mail and Messages to be available. Also I would like to add "Print", but don't know if I can do that.
Is that possible?
Thanks
(Don't have enough rep points to add 'NSSharingService' as a tag)
Solved it by using proposedSharingServices.
- (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray *)proposedServices{
// Find and the services you want
NSMutableArray *newProposedServices = [[NSMutableArray alloc] initWithCapacity:5];
for (NSSharingService *sharingService in proposedServices) {
if ([[sharingService title] isEqualToString:#"Email"] || [[sharingService title] isEqualToString:#"Message"]) {
[newProposedServices addObject:sharingService];
}
}
NSArray *services = newProposedServices;
NSSharingService *customService = [[NSSharingService alloc] initWithTitle:#"Print" image:[NSImage imageNamed:#"PrintImage"] alternateImage:nil handler:^{
// Do whatever
}];
services = [services arrayByAddingObject:customService];
return services;
}
Comparing a proposed service to a new named instance works. Here's a trivial Swift code from my project:
let excludedNames = [
NSSharingServiceNamePostOnFacebook,
NSSharingServiceNamePostOnTwitter,
]
var excludedServices = [NSSharingService]()
for name in excludedNames {
if let service = NSSharingService(named: name) {
excludedServices += [service]
}
}
return proposedServices.filter {
!excludedServices.contains($0)
}
No need to use a private name property.
Rather then trying to say what you don't want simply return a list of what you do want.
- (NSArray<NSSharingService *> *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray<NSSharingService *> *)proposedServices
{
NSArray *result = #[[NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail], [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeMessage]];
return result;
}
A slightly different approach via proposedSharingServices:
  
- (NSArray*)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray *)proposedServices {
NSArray *excludedServices = #[NSSharingServiceNamePostOnFacebook,
NSSharingServiceNamePostOnTwitter];
NSArray *sharingServices = [proposedServices filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (name IN %#)", excludedServices]];
return sharingServices;
}
Here's a better way - no private API access required.
NSArray *excludedServices = #[NSSharingServiceNamePostOnFacebook,
NSSharingServiceNamePostOnTwitter];
NSMutableArray *includedServices = [[NSMutableArray alloc] init];
for (NSSharingService *service in proposedServices) {
if ([excludedServices indexOfObject:service] == NSNotFound) {
[includedServices addObject:service];
}
}
return includedServices;

UIActivityViewController customize text based on selected activity

I want to customize text for the same information but when I am sharing it on Facebook I don't want to use the twitter hash tags or #username scheme...
How can I diversify text for sharing based on which sharing service would be used?
Ofcourse I'm using UIActivityViewController:
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:#[shareText, shareURL] applicationActivities:nil];
I took this answer and made a simple class for it. The default message will be seen by sharing outlets other than Twitter, and for Twitter words within the hashWords array will appear with hashes if they are present in the default message. I thought I would share it for anyone else who needs it. Thanks Christopher!
Usage:
TwitterHashActivityItemProvider *twit = [[TwitterHashActivityItemProvider alloc] initWithDefaultText:#"I really like stackoverflow and code"
hashWords:#[#"stackoverflow", #"code"]];
NSArray *items = #[twit];
UIActivityViewController *act = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
Header:
#interface TwitterHashActivityItemProvider : UIActivityItemProvider
- (id)initWithDefaultText:(NSString*)text hashWords:(NSArray*)hashItems;
#property (nonatomic,strong) NSArray *hashItems;
#end
Implementation:
#import "TwitterHashActivityItemProvider.h"
#implementation TwitterHashActivityItemProvider
- (id)initWithDefaultText:(NSString*)text hashWords:(NSArray*)hashItems;
{
self = [super initWithPlaceholderItem:text];
if ( self )
{
self.hashItems = hashItems;
}
return self;
}
- (id)item
{
if ( [self.placeholderItem isKindOfClass:[NSString class]] )
{
NSString *outputString = [self.placeholderItem copy];
// twitter gets some hash tags!
if ( self.activityType == UIActivityTypePostToTwitter )
{
// go through each potential hash item and augment the main string
for ( NSString *hashItem in self.hashItems)
{
NSString *hashed = [#"#" stringByAppendingString:hashItem];
outputString = [outputString stringByReplacingOccurrencesOfString:hashItem withString:hashed];
}
}
return outputString;
}
// else we didn't actually provide a string...oops...just return the placeholder
return self.placeholderItem;
}
#end
Instead of passing the text strings into the initWithActivityItems call, pass in your own sub-class of the UIActivityItemProvider class and when you implement the itemForActivityType method it will provide the sharing service as the 'activityType' parameter.
You can then return the customized content from this method.
Swift implementation example of an UIActivityItemProvider subclass. Copy option will use only the password, other activity types will use the full share text. Should be easy to customize for different use cases. Credit to Cristopher & NickNack for their answers.
class PasswordShareItemsProvider: UIActivityItemProvider {
private let password: String
private var shareText: String {
return "This is my password: " + password
}
init(password: String) {
self.password = password
// the type of the placeholder item is used to
// display correct activity types by UIActivityControler
super.init(placeholderItem: password)
}
override var item: Any {
get {
guard let activityType = activityType else {
return shareText
}
// return desired item depending on activityType
switch activityType {
case .copyToPasteboard: return password
default: return shareText
}
}
}
}
Usage:
let itemProvider = PasswordShareItemsProvider(password: password)
let activityViewController = UIActivityViewController(activityItems: [itemProvider], applicationActivities: nil)

How can i get currency symbols from currency code in iphone?

I have get currency code (Eg: USD, EUR, INR) from webservice response. I need to show the currency symbols for the corresponding currency code. If the currency code is USD, i need to show $, if the currency code is EUR i need to show €. How can i do this? Please suggest any idea or sample code to do this. Please help me. Thanks in advance.
This code works charm in my project. I will share this to you all.
NSString *currencyCode = #"EUR";
NSLocale *locale = [[[NSLocale alloc] initWithLocaleIdentifier:currencyCode] autorelease];
NSString *currencySymbol = [NSString stringWithFormat:#"%#",[locale displayNameForKey:NSLocaleCurrencySymbol value:currencyCode]];
NSLog(#"Currency Symbol : %#", currencySymbol);
Thanks.
Swift 4 version
Finding locale by currency code:
let localeGBP = Locale
.availableIdentifiers
.lazy
.map { Locale(identifier: $0) }
.first { $0.currencyCode == "GBP" }
print(localeGBP?.currencySymbol) // £
Formatting currency
if let locale = localeGBP {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = locale
let result = formatter.string(from: 100000) // £100,000.00
}
Edit:
Why .lazy? Without it the loop would run over all the locale identifiers and return the first one which matches. That's about 700ish identifiers, and if the first one is the one you want then you have wasted creating 699 Locales :) With .lazy in there it automatically stops at the first matching one. In my case it reduces the number of times through the loop from 710 down to 22 when converting "GBP". This isn't important if you are only doing this once, but if you're doing this a number of times (i.e. over an array of symbols) then it's an easy way to get a bit more efficiency.
This code is what you are looking for though not very efficient because of loop for locales. Still it works correctly for all currency codes, not just for eur or usd. Hope this will help you.
- (NSLocale *) findLocaleByCurrencyCode:(NSString *)_currencyCode
{
NSArray *locales = [NSLocale availableLocaleIdentifiers];
NSLocale *locale = nil;
for (NSString *localeId in locales) {
locale = [[[NSLocale alloc] initWithLocaleIdentifier:localeId] autorelease];
NSString *code = [locale objectForKey:NSLocaleCurrencyCode];
if ([code isEqualToString:_currencyCode])
break;
else
locale = nil;
}
return locale;
}
- (NSString *)findCurrencySymbolByCode:(NSString *)_currencyCode
{
NSNumberFormatter *fmtr = [[NSNumberFormatter alloc] init];
NSLocale *locale = [self findLocaleByCurrencyCode:_currencyCode];
NSString *currencySymbol;
if (locale)
[fmtr setLocale:locale];
[fmtr setNumberStyle:NSNumberFormatterCurrencyStyle];
currencySymbol = [fmtr currencySymbol];
[fmtr release];
if (currencySymbol.length > 1)
currencySymbol = [currencySymbol substringToIndex:1];
return currencySymbol;
}
Use it this way:
NSString *currencySymbol = [self findCurrencySymbolByCode:currencyCode];
NSLocale will happily tell you the currency symbol used by a particular locale:
[locale objectForKey:NSLocaleCurrencySymbol];
It'll also tell you the currency code:
[locale objectForKey:NSLocaleCurrencyCode];
So all you have to do now is look up the locale that corresponds to a given code. There's no built-in method (that I'm aware of) to do this directly, so loop through all the known locales and pick the one that matches. #Umka's answer has a good example of this in the -findLocaleByCurrencyCode: method.
You could optimise the process by building your own lookup table, rather than iterating through all locales each time. You may need to handle the possibility of duplicate currency codes too, which would require some heuristic for deciding which is the most likely locale.
Building on code from #Mike Abdullah and #Umka, here are some functions in Swift.
func findCodeAndSymbolForAllLocales() {
let locales = NSLocale.availableLocaleIdentifiers()
for localeId in locales {
let locale = NSLocale(localeIdentifier: localeId)
if let code = locale.objectForKey(NSLocaleCurrencyCode) as? String,
let symbol = locale.objectForKey(NSLocaleCurrencySymbol) {
print("\(code) \(symbol)")
}
}
}
func findCurrencySymbolByCode(currencyCode:String) -> String? {
guard let locale = findLocaleByCurrencyCode(currencyCode) else {
print("locale for \(currencyCode) is nil")
return nil
}
return locale.objectForKey(NSLocaleCurrencySymbol) as? String
}
func findLocaleByCurrencyCode(currencyCode:String) -> NSLocale? {
let locales = NSLocale.availableLocaleIdentifiers()
var locale:NSLocale?
for localeId in locales {
locale = NSLocale(localeIdentifier: localeId)
if let code = locale!.objectForKey(NSLocaleCurrencyCode) as? String {
if code == currencyCode {
return locale
}
}
}
return locale
}
NSNumberFormatter * formatter = [NSNumberFormatter new];
formatter.numberStyle = NSNumberFormatterCurrencyStyle;
NSString * localeIde = [NSLocale localeIdentifierFromComponents:#{NSLocaleCurrencyCode: currencyCode}];
formatter.locale = [NSLocale localeWithLocaleIdentifier:localeIde];
NSString * symbol = formatter.currencySymbol;
One way is to start with a valid locale (e.g. the user current locale) and then override the currency code. Simple and efficient and allows you to use the newly constructed locale to configure a currency formatter in order to display a string according to the user's locale preferences:
NSLocale* locale = [NSLocale currentLocale];
if (_currencyCode) {
NSMutableDictionary* components = [[NSLocale componentsFromLocaleIdentifier:locale.localeIdentifier] mutableCopy];
components[NSLocaleCurrencyCode] = _currencyCode;
locale = [[NSLocale alloc] initWithLocaleIdentifier:[NSLocale localeIdentifierFromComponents:components]];
}
return locale.currencySymbol;
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
this is for US currency style
NSNumberFormatter *numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
//[numberFormatter setCurrencySymbol:#"Rs"];
[numberFormatter setNumberStyle:#"en_IN"];
This is for indian
Swift
let locale = NSLocale(localeIdentifier: currencyCode)
let currencySymbol = locale.displayName(forKey: .currencySymbol, value: currencyCode) ?? currencyCode
print("Currency symbol: \(currencySymbol)")

Get the Username(s) stored in Keychain, using only the ServiceName? OR: Where are you supposed to store the Username?

So the OS X Keychain has three pieces of information:
ServiceName (the name of my app)
Username
Password
I obviously always know the ServiceName. Is there a way to find any saved Username(s) for that ServiceName? (Finding the password is easy once you know the Username.)
I would much prefer to use a nice Cocoa wrapper such as EMKeychain to do this. But EMKeychain requires the UserName to get any keychain item!
+ (EMGenericKeychainItem *)genericKeychainItemForService:(NSString *)serviceNameString withUsername:(NSString *)usernameString;
How are you expected to fully utilize saving credentials in the Keychain, if you need the Username to find the credentials? Is the best practice to save the Username in the .plist file or something?
SecKeychainFindGenericPassword only returns a single keychain item. To find all generic passwords for a specific service, you need to run a query on the keychain. There are several ways to do this, based on what version of OS X you target.
If you need to run on 10.5 or below, you'll need to use SecKeychainSearchCreateFromAttributes. It's a rather horrible API. Here is a rough cut of a method that returns a dictionary mapping usernames to passwords.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
OSStatus status;
// Construct a query.
const char *utf8Service = [service UTF8String];
SecKeychainAttribute attr = { .tag = kSecServiceItemAttr,
.length = strlen(utf8Service),
.data = (void *)utf8Service };
SecKeychainAttribute attrList = { .count = 1, .attr = &attr };
SecKeychainSearchRef *search = NULL;
status = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &attrList, &search);
if (status) {
report(status);
return nil;
}
// Enumerate results.
NSMutableDictionary *result = [NSMutableDictionary dictionary];
while (1) {
SecKeychainItemRef item = NULL;
status = SecKeychainSearchCopyNext(search, &item);
if (status)
break;
// Find 'account' attribute and password value.
UInt32 tag = kSecAccountItemAttr;
UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
SecKeychainAttributeInfo info = { .count = 1, .tag = &tag, .format = &format };
SecKeychainAttributeList *attrList = NULL;
UInt32 length = 0;
void *data = NULL;
status = SecKeychainItemCopyAttributesAndData(item, &info, NULL, &attrList, &length, &data);
if (status) {
CFRelease(item);
continue;
}
NSAssert(attrList->count == 1 && attrList->attr[0].tag == kSecAccountItemAttr, #"SecKeychainItemCopyAttributesAndData is messing with us");
NSString *account = [[[NSString alloc] initWithBytes:attrList->attr[0].data length:attrList->attr[0].length encoding:NSUTF8StringEncoding] autorelease];
NSString *password = [[[NSString alloc] initWithBytes:data length:length encoding:NSUTF8StringEncoding] autorelease];
[result setObject:password forKey:account];
SecKeychainItemFreeAttributesAndData(attrList, data);
CFRelease(item);
}
CFRelease(search);
return result;
}
For 10.6 and later, you can use the somewhat less inconvenient SecItemCopyMatching API:
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
(id)kCFBooleanTrue, kSecReturnData,
(id)kCFBooleanTrue, kSecReturnAttributes,
kSecMatchLimitAll, kSecMatchLimit,
service, kSecAttrService,
nil];
NSArray *itemDicts = nil;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)q, (CFTypeRef *)&itemDicts);
if (status) {
report(status);
return nil;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSDictionary *itemDict in itemDicts) {
NSData *data = [itemDict objectForKey:kSecValueData];
NSString *password = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSString *account = [itemDict objectForKey:kSecAttrAccount];
[result setObject:password forKey:account];
}
[itemDicts release];
return result;
}
For 10.7 or later, you can use my wonderful LKKeychain framework (PLUG!). It doesn't support building attribute-based queries, but you can simply list all passwords and filter out the ones you don't need.
- (NSDictionary *)genericPasswordsWithService:(NSString *)service {
LKKCKeychain *keychain = [LKKCKeychain defaultKeychain];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (LKKCGenericPassword *item in [keychain genericPasswords]) {
if ([service isEqualToString:item.service]) {
[result setObject:item.password forKey:item.account];
}
}
return result;
}
(I didn't try running, or even compiling any of the above code samples; sorry for any typos.)
You don't need the username. You do with EMKeychain, but that's an artificial distinction that that class imposes; the underlying Keychain Services function does not require a username to find a keychain item.
When using SecKeychainFindGenericPassword directly, pass 0 and NULL for the username parameters. It will return a keychain item that exists on that service.
However, that will return only one item. If the user has multiple keychain items on the same service, you won't know that, or which one you got (the documentation says it returns the “first” matching item, with no specification of what it considers “first”). If you want any and all items for that service, you should create a search and use that.
Generic passwords have a unique key of the service name and the username. Thus, to fetch a single generic keychain entry, you will need to provide both. However, you can iterate over all generic keychain entries for your given service using the SecKeychainFindGenericPassword function.
(Disclaimer: I don't know anything about doing this in EMKeychain.)

Resources