@blog.justoneplanet.info

日々勉強

URL SchemeでiOSアプリを起動する

自分用のメモ。まず、プロジェクトのURL Typesを開きURL Schemesを追加する。2つ以上の場合はカンマで区切る。

AppDelegate.m

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    if ([[url scheme] isEqualToString:URL_SCHEME_HOGE] && [[url host] isEqualToString:URL_HOST_HOGE]) {
        // do something
    }
}

iOSでキーボードの表示と非表示に応じた処理をする

- (void)keyboardDidShow:(NSNotification *)note {
    // something
}
- (void)keyboardDidHide:(NSNotification *)note {
    // something
}
- (id)init {
    self = [super init];
    if (self) {
        // キーボードが表示された時
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
        // キーボードが隠れた時
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
    }
    return self;
}

iOSでUIViewのタップを検出する

■実装

以下のようにUITapGestureRecognizerを使う。

UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapped:)];
[view addGestureRecognizer:recognizer];// view.tag == VIEW_HOGE_TAG

viewがタップされると以下のメソッドが実行される。

- (void)onTapped:(UITapGestureRecognizer *)recognizer {
    switch (recognizer.view.tag) {
        case VIEW_HOGE_TAG:
            // hogehoge
            break;
            
        case VIEW_FUGA_TAG:
            // fugafuga
            break;
            
        default:
            break;
    }
}

iOSのAddressBookを操作する

■グループの検索

CFArrayRef groups = ABAddressBookCopyArrayOfAllGroups(addressBook);
for (int i = 0; i < CFArrayGetCount(groups); i++) {
    ABRecordRef group = CFArrayGetValueAtIndex(groups, i);
    CFStringRef registerdGroupName = ABRecordCopyValue(group, kABGroupNameProperty);
    if (CFStringFind(registerdGroupName, aCFgroupName, kCFCompareCaseInsensitive).location != kCFNotFound) {
        isRegistered = true;
        registerdId = ABRecordGetRecordID(group);
    }
    CFRelease(registerdGroupName);
}
CFRelease(groups);

■グループの作成

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
ABAddressBookRef addressBook = ABAddressBookCreate();
bool b1 = false;
bool b2 = false;
bool b3 = false;
ABRecordRef group = ABGroupCreate();
NSString *groupName = NSLocalizedString(@"APP_NAME", @"グループ名");
CFStringRef aCFgroupName = (__bridge CFStringRef)groupName;
b1 = ABRecordSetValue(group, kABGroupNameProperty, aCFgroupName, nil);
            
if (b1) b2 = ABAddressBookAddRecord(addressBook, group, nil);
if (b2) b3 = ABAddressBookSave(addressBook, nil);
if (b3) {
    [ud setInteger:ABRecordGetRecordID(group) forKey:@"key_ab_group_id"];
    [ud synchronize];
}
CFRelease(group);
CFRelease(addressBook);

■グループの削除

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
ABAddressBookRef addressBook = ABAddressBookCreate();
bool b1 = false;
bool b2 = false;
ABRecordRef group = ABAddressBookGetGroupWithRecordID(addressBook, [ud integerForKey:@"key_ab_group_id"]);
b1 = ABAddressBookRemoveRecord(addressBook, group, nil);
if (b1) b2 = ABAddressBookSave(addressBook, nil);
if (b2) {
    [ud setInteger:0 forKey:@"key_ab_group_id"];
    [ud synchronize];
}
CFRelease(addressBook);

■グループのメンバ取得と削除

CFArrayRef people = ABGroupCopyArrayOfAllMembers(group);
for (int i = 0; i < CFArrayGetCount(people); i++) {
    ABRecordRef person = CFArrayGetValueAtIndex(people, i);
    result1 = ABGroupRemoveMember(group, person, nil);// グループから削除されるだけで電話帳には残る
    result2 = ABAddressBookRemoveRecord(addressBook, person, nil);// 電話帳から削除される
}

■グループへのメンバ追加

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];

ABAddressBookRef addressBook = ABAddressBookCreate();
ABRecordRef group = ABAddressBookGetGroupWithRecordID(addressBook, [ud integerForKey:@"key_ab_group_id"]);
ABRecordRef person = ABPersonCreate();

CFStringRef aCFfirst       = (CFStringRef)first;
CFStringRef aCFsecond      = (CFStringRef)second;
CFStringRef aCFfirstYomi   = (CFStringRef)firstYomi;
CFStringRef aCFsecondYomi  = (CFStringRef)secondYomi;
ABRecordSetValue(person, kABPersonFirstNameProperty, aCFfirst, nil);
ABRecordSetValue(person, kABPersonFirstNamePhoneticProperty, aCFfirstYomi, nil);
ABRecordSetValue(person, kABPersonLastNameProperty, aCFsecond, nil);
ABRecordSetValue(person, kABPersonLastNamePhoneticProperty, aCFsecondYomi, nil);

ABAddressBookAddRecord(addressBook, person, &error);
ABAddressBookSave(addressBook, &error);

if ([ud integerForKey:@"key_ab_group_id"] != 0) {
    ABGroupAddMember(group, person, &error);
}

ABAddressBookSave(addressBook, nil);
CFRelease(person);
CFRelease(addressBook);

参考

iOSで本体の設定画面に設定項目を追加する

本体の設定画面にアプリの設定画面が出るアレであるが、Settings Bundleで検索すると色々出てくる。

■実装

NEW FILE … > Resource > Settings Bundleを追加する。この段階でシミュレータを立ち上げれば設定画面にアプリの設定項目が追加されている。

Root.plist

初期状態は以下のようになっている。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PreferenceSpecifiers</key>
    <array>
        <dict>
            <key>Title</key>
            <string>Group</string>
            <key>Type</key>
            <string>PSGroupSpecifier</string>
        </dict>
        <dict>
            <key>AutocapitalizationType</key>
            <string>None</string>
            <key>AutocorrectionType</key>
            <string>No</string>
            <key>DefaultValue</key>
            <string></string>
            <key>IsSecure</key>
            <false/>
            <key>Key</key>
            <string>name_preference</string>
            <key>KeyboardType</key>
            <string>Alphabet</string>
            <key>Title</key>
            <string>Name</string>
            <key>Type</key>
            <string>PSTextFieldSpecifier</string>
        </dict>
        <dict>
            <key>DefaultValue</key>
            <true/>
            <key>Key</key>
            <string>enabled_preference</string>
            <key>Title</key>
            <string>Enabled</string>
            <key>Type</key>
            <string>PSToggleSwitchSpecifier</string>
        </dict>
        <dict>
            <key>DefaultValue</key>
            <real>0.5</real>
            <key>Key</key>
            <string>slider_preference</string>
            <key>MaximumValue</key>
            <integer>1</integer>
            <key>MaximumValueImage</key>
            <string></string>
            <key>MinimumValue</key>
            <integer>0</integer>
            <key>MinimumValueImage</key>
            <string></string>
            <key>Type</key>
            <string>PSSliderSpecifier</string>
        </dict>
    </array>
    <key>StringsTable</key>
    <string>Root</string>
</dict>
</plist>

■データの読み出し

以下のコードで設定値を呼び出すことが出来る。

NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSLog(@"%@", [ud objectForKey:@"name_preference"]);
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSLog(@"%@", [ud objectForKey:@"enabled_preference"]);
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSLog(@"%@", [ud objectForKey:@"slider_preference"]);

一番難しいのは何で検索すればこの画面の作り方に辿り着けるのかというところである。

既存のプロジェクトをARC対応する

面倒ではあるがARC対応されたものしかライブラリとして存在しないものが少しづつ出てきたので時代の流れだししょうがない。

■Edit > Refactor > Convert to Objective-C ARC…

  1. Check
  2. Cannot Convert to Objective-C ARC…
  3. 左側でエラーが出たものを除外して、1に戻る

2が出なくなったら自動で変換される。

■Target > Build Phases > Compile Sources

除外したものに-fno-objc-arcが付いている。

  • ライブラリはARC版が出ていたら入れ替える
  • 自前のコードは頑張る

UIWebViewをカスタマイズする

WebViewっぽくならないようにバウンスをさせないようにし、HTTPリクエストにカスタムヘッダーを付加するようにした。たいしてカスタマイズしてない。

#import <UIKit/UIKit.h>
@interface CustomWebView : UIWebView
@end
#import "CustomWebView.h"
@implementation CustomWebView
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {// Initialization code
        // バウンスさせない
        for (id subview in self.subviews) {
            if ([[subview class] isSubclassOfClass: [UIScrollView class]]) {
                ((UIScrollView *)subview).bounces = NO;
            }
        }
    }
    return self;
}

- (void)loadRequest:(NSURLRequest *)request
{
    // カスタムヘッダーの付加
    NSMutableURLRequest *mutableRequest = (NSMutableURLRequest *)[request mutableCopy];
    [mutableRequest setValue:@"iOS.UIWebView" forHTTPHeaderField:@"App-Client"];
    [super loadRequest:mutableRequest];
}
@end

以下のようにオフライン時の画面を表示するメソッドを作ってあげてもいいと思う。

- (void)displayOfflineView
{
    if (self) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"offline" ofType:@"html"];
        [self loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    }
}

webviewを表示した時に圏外の場合、圏内になった時にreloadをしても正しく表示ができないので以下のようなメソッドも加えておく。

- (void)dealloc
{
    if (initialRequest) {
        [initialRequest release];
    }
    [super dealloc];
}
- (void)reload
{
    if (self) {
        if ([self canGoBack]) {
            [super reload];
        }
        else if (initialRequest) {
            [self loadRequest:initialRequest];
        }
    }
}
- (void)loadRequest:(NSURLRequest *)request
{
    [super loadRequest:request];
    if (!initialRequest) {
        initialRequest = [request retain];
    }
}

■カスタムヘッダの付加

ただし、上述の方法ではUIWebView内のリンクをクリックした時にはカスタムヘッダが付加されないので以下のようにする。

- (void) _internalInit {
    super.delegate = self;
    foreignDelegate = nil;
}
- (id) init {
    if((self = [super init])) {
        [self _internalInit];
    }
    return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder {
    if((self = [super initWithCoder:aDecoder])) {
        [self _internalInit];
    }
    return self;
}
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {// Initialization code
        [self _internalInit];
    }
    return self;
}
- (void)dealloc
{
    [super dealloc];
}
// offline画面を表示する
- (void)displayOfflineView
{
    if (self) {
        NSString *path = [[NSBundle mainBundle] pathForResource:@"offline" ofType:@"html"];
        [self loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    }
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/
- (void) setDelegate:(id<UIWebViewDelegate>)aDelegate {
    if(foreignDelegate == aDelegate) {
        return;
    }
	
    [foreignDelegate release];
    foreignDelegate = [aDelegate retain];
    /* Make sure this class is still the real delegate */
    super.delegate = self;
}
- (id<UIWebViewDelegate>) delegate {
    return foreignDelegate;
}
- (void) setCustomHeaders:(NSDictionary *)cHeaders {
    NSMutableDictionary *newHeaders = [NSMutableDictionary dictionary];
    for(NSString *key in [cHeaders allKeys]) {
        NSString *lowercaseKey = [key lowercaseString];
        [newHeaders setObject:[cHeaders objectForKey:key] forKey:lowercaseKey];
    }
    [customHeaders release];
    customHeaders = [newHeaders retain];
}
#pragma mark -
#pragma mark UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)aRequest navigationType:(UIWebViewNavigationType)navigationType {    
    
    BOOL missingHeaders = NO;
    
    /* Take all headers keys, lowercase them */
    NSArray *currentHeaders = [[aRequest allHTTPHeaderFields] allKeys];
    NSMutableArray *lowercasedHeaders = [NSMutableArray array];
    for(NSString *key in currentHeaders) {
        [lowercasedHeaders addObject:[key lowercaseString]];
    }
    
    /* See if there's at least one custom header missing */
    for(NSString *key in customHeaders) {
        if(![lowercasedHeaders containsObject:key]) {
            missingHeaders = YES;
            break;
        }
    }
    
    /* If one is missing, drop current request, and make a new one
     * with custom header */
    if(missingHeaders) {
        NSMutableURLRequest *newRequest = [aRequest mutableCopy];
        for(NSString *key in [customHeaders allKeys]) {
            [newRequest setValue:[customHeaders valueForKey:key] forHTTPHeaderField:key];
        }
        [self loadRequest:newRequest];
        [newRequest release];
        return YES;
    }
    
    //NSLog(@"Loading request with HTTP headers %@", [aRequest allHTTPHeaderFields]);
    
    if([foreignDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [foreignDelegate webView:self shouldStartLoadWithRequest:aRequest navigationType:navigationType];
    }
    
    return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
    if([foreignDelegate respondsToSelector:@selector(webViewDidStartLoad:)])
        [foreignDelegate webViewDidStartLoad:self];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if([foreignDelegate respondsToSelector:@selector(webViewDidFinishLoad:)])
        [foreignDelegate webViewDidFinishLoad:self];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    if([foreignDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)])
        [foreignDelegate webView:self didFailLoadWithError:error];
}

NSURLのURLの各部分に対するアクセス方法を調べてみる

分かりやすいものから分かりにくいものまであるのでとりあえずメモしておく。

// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
NSURL *url = [request URL];
[url scheme];// http
[url host];// hogehoge.com
[url absoluteString];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url absoluteURL];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url baseURL];
[url fragment];// hoge
[url host];// hogehoge.com
[url lastPathComponent];// test.html
[url parameterString];
[url password];// pass
[url path];// /dir/use/test.html
[url pathComponents];// ("/",dir,d,"test.html")
[url pathExtension];// html
[url port];// 1234
[url query];// q=sample
[url relativePath];// /dir/use/test.html
[url relativeString];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url resourceSpecifier];// //user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url standardizedURL];// http://user:pass@hogehoge.com:1234/dir/d/test.html?q=sample#hoge
[url user];// user

面倒になってきた。