iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
外部サービスと通信する際、アプリケーションがインストールされているiOS機器を区別できるユニークな識別子が必要になる事があると思うが、iOS 5より以前では、以下のコードでiOS機器毎にユニークなIDを取得できる。
NSString *uniqueID = [[UIDevice currentDevice] uniqueIdentifier];
NSLog(@"UDID: %@", uniqueID);
iOS5からは、この方法は推奨されなくなった。そこで、多くの場合、以下の様なコードでユニークなIDを取得している。
- (NSString *)createUUID
{
CFUUIDRef uuid = CFUUIDCreate(NULL);
NSString *uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
return uuidString;
}
ただし、このメソッドは呼ばれる度にユニークなIDを返す。その為、そのiOS機器にインストールされたアプリケーションという単位でユニークな識別子が欲しい場合は、この値をキーチェインに保存して、キーチェインに存在していたらそれを理由するという方法が、よく行われていると思う。
前回、UUIDをユニークIDとして利用する方法を紹介したが、UUIDは取得する度に異なる値となる為、そのアプリケーションにとて、インストールされたiOS機器でユニークで固定なIDとして利用できない。
そこで、UUIDをキーチェインに登録して、そのアプリケーションにとってユニークで固定なIDにするコードを紹介する。まずは、Secuirty.frameworkをプロジェクトに追加する。
#import <Security/Security.h>
@interface ViewController ()
- (NSString *)createUUID;
- (NSString *)loadKeychainServices;
- (void)removeKeychainService;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *uuidString = [self loadKeychainServices];
NSLog(@"UUID:%@", uuidString);
uuidString = [self loadKeychainServices];
NSLog(@"UUID:%@", uuidString);
[self removeKeychainService];
uuidString = [self loadKeychainServices];
NSLog(@"UUID:%@", uuidString);
}
- (NSString *)createUUID
{
CFUUIDRef uuid = CFUUIDCreate(NULL);
NSString *uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
return uuidString;
}
- (NSString *)loadKeychainServices
{
NSString *savedUUID = nil;
NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrGeneric];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrAccount];
[query setObject:[[NSBundle mainBundle] bundleIdentifier] forKey:(__bridge id)kSecAttrService];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
CFDictionaryRef attributesDictRef = nil;
OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&attributesDictRef);
NSDictionary *attributes = (__bridge_transfer NSDictionary *)attributesDictRef;
if (result == noErr) {
query = [NSMutableDictionary dictionaryWithDictionary:attributes];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFDataRef dataRef;
result = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&dataRef);
NSData *data = (__bridge_transfer NSData *)dataRef;
if (result == noErr) {
savedUUID = [[NSString alloc] initWithBytes:[data bytes]
length:[data length]
encoding:NSUTF8StringEncoding];
}
}
if (! savedUUID) {
savedUUID = [self createUUID];
query = [[NSMutableDictionary alloc] init];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrGeneric];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrAccount];
[query setObject:[[NSBundle mainBundle] bundleIdentifier] forKey:(__bridge id)kSecAttrService];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrLabel];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrDescription];
[query setObject:(id)[savedUUID dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (result != noErr) {
savedUUID = nil;
}
}
return savedUUID;
}
- (void)removeKeychainService
{
NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrGeneric];
[query setObject:(id)@"UUID" forKey:(__bridge id)kSecAttrAccount];
[query setObject:[[NSBundle mainBundle] bundleIdentifier] forKey:(__bridge id)kSecAttrService];
[query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
OSStatus result = SecItemDelete((__bridge CFDictionaryRef)query);
if (result != noErr) {
}
}
@end
キーチェインに登録できる情報の種類は決められているので、パスワードとしてUUIDを登録した。
たとえば、テキスト・フィールドの場合。
OSXでは、InterfaceBuilderでテキストフィールドのフォーマッタを設定して、ユーザが入力できる値の形式を指定できる。
iOSでもNSFormatterは存在しているが、ビューの仕組みが違う為か、InterfaceBuilderで入力できる値の形式を指定できない。
そこで、NSFormatterを利用するが、別の方法で入力値の制限方法を試してみることにする。
どので入力値の制限を行うかは、テキストフィールドの場合、UITextFieldのデリゲートUITextFieldDelegateで用意されているメソッドによる事になる。
入力制限は、入力中に行われるのが親切だと思うので、– textField:shouldChangeCharactersInRange:replacementString:で対応することにする。
NSFormatterの使い方を試してみる。
小終点以下1〜2桁までを表示する。
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMinimumFractionDigits:1];
[numberFormatter setMaximumFractionDigits:2];
NSNumber *n = [NSNumber numberWithDouble:123456.123456];
NSString *s = [numberFormatter stringFromNumber:n];
NSLog(@"%@", s);
お金として扱い、頭に¥をつける。
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setCurrencyCode:@"JPY"];
NSNumber *n = [NSNumber numberWithInt:123456.123456];
NSString *s = [numberFormatter stringFromNumber:n];
NSLog(@"%@", s);
今度は、末尾に円をつける。
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setPositiveFormat:@"#,##0円"];
NSNumber *n = [NSNumber numberWithInt:123456.123456];
NSString *s = [numberFormatter stringFromNumber:n];
NSLog(@"%@", s);
三桁毎にカンマ。
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setGroupingSeparator:@","];
[numberFormatter setGroupingSize:3];
NSNumber *n = [NSNumber numberWithInt:123456.123456];
NSString *s = [numberFormatter stringFromNumber:n];
NSLog(@"%@", s);
今の時刻を24時間表記で分まで表示。
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[dateFormatter setDateStyle:NSDateFormatterNoStyle];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]];
NSString *s = [dateFormatter stringFromDate:[NSDate date]];
NSLog(@"%@", s);
頭に年月日を加える。
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm"];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]];
NSString *s = [dateFormatter stringFromDate:[NSDate date]];
NSLog(@"%@", s);
10桁の数字のみを受け付ける例を作ってみる。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSInteger i = [string integerValue];
NSNumber *n = [[NSNumber alloc] initWithInteger:i];
NSString *s = [numberFormatter stringFromNumber:n];
if ([string compare:s] == NSOrderedSame) {
return YES;
}
return NO;
}
もっと、いい方法がありそうだが。。。
前回紹介した方法だと上手くいかない事が分かったので、紹介する。
フォーマッタを通した後だと、装飾的な文字が追加されるし、元の文字列と、置換される文字列を個別に評価するのは面倒なので、くっつけた後、評価するようにした。以下は、n桁の数値のみの場合。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSMutableString *text = [textField.text mutableCopy];
[text replaceCharactersInRange:range withString:string];
/* クリア(空文字) */
if (0 == text.length) {
return YES;
}
/* n桁 */
if (n < text.length) {
return NO;
}
/* 数字 */
NSCharacterSet *digitCharSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
NSScanner *scanner = [NSScanner localizedScannerWithString:text];
[scanner setCharactersToBeSkipped:nil];
[scanner scanCharactersFromSet:digitCharSet intoString:NULL];
if (![scanner isAtEnd]) {
return NO;
}
return YES;
}
これを金額に変換するメソッドは、以下のとおり。
- (NSString *)stringFromDecimalNumber:(NSDecimalNumber *)decimalNumber
{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setCurrencyCode:@"JPY"];
NSString *s = [numberFormatter stringFromNumber:decimalNumber];
return s;
}