iOS Fluentd Loggerライブラリを公開しました

iOS用Fluentd Loggerライブラリを公開しました。

rizumita/CTFluentLogger

まだアルファ段階なので、開発時のみご利用ください。

Fluentd+elasticsearch+Kibana3が動作するVagrant+Chefも入っているので、簡単に使い始められるようになっています。

詳しい使い方はiOSアプリからFluentdにログを送る (Vagrantで簡単にサーバを利用可能) | CAPH TECH : iOSを御覧ください。

iOSのバージョンチェック用マクロ

iOSでバージョンのチェックをいろいろ行いたかったので、便利なマクロを探してみた。Check iPhone iOS Version – Stack Overflowから。これを.pchファイルに記述すると便利。

/*
 *  System Versioning Preprocessor Macros
 */ 
 
#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
 
/*
 *  Usage
 */ 
 
if (SYSTEM_VERSION_LESS_THAN(@'4.0')) {
    ...
}
 
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@'3.1.1')) {
    ...
}

Reactive-Evernote-SDK-iOSを作りました

Reactive-Evernote-SDK-iOSを作りました。Evernote SDKをReactiveCocoaで使えるライブラリーです。GitHubで公開しています。

下記のように使います。

[[[RACEvernoteNoteStore noteStore] listNotebooks] subscribeNext:^(NSArray *notebooks) {
    NSLog(@"%@", notebooks);
}                                                         error:^(NSError *error) {
    NSLog(@"error: %@", error);
}                                                     completed:^{
    NSLog(@"completed");
}];

さすがにThriftのコードジェネレータを作成するのは難しいので、[RACSignal createSignal:]で既存のEDAM系のクラスを呼んでいます。

まだlistNotebookとlistTagsしか試してないので、お暇な方は試してバグ報告よろしくです。podspecもあるのでCocoaPodsで簡単に導入できると思います。

TestFlight SDKを利用する

簡単にAdhocアプリを配布できるTestFlightからSDKがリリースされました。

  • セッション
  • クラッシュリポート
  • アプリ内での質問
  • チェックポイント
  • リモートロギング
  • アプリ内アップデート

といった機能があるようです。

早速組み込んでみたいと思います。

SDKのページからSDKをダウンロードして解凍します。

libTestFlight.aとTestFlight.hをXcodeプロジェクトに追加します。TestFlight.hを利用するファイルでimportするのですが、簡単に行うにはprojectname_Prefix.pchファイルの#ifdef __OBJC__セクションに#import “TestFlight.h”を記述します。プリプロセッサマクロで#ifdef ADHOCのように分岐するといいと思います。

http://testflightapp.com/dashboard/team/にアクセスしてeditをクリックしてチームトークンを取得します。取得したら
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[TestFlight takeOff:@”チームトークン”];
}
と記述します。チームトークンのところは書き換えてください。

結果はすべてビルドページで閲覧できます。

クラッシュリポートを有効にするにはビルドセッティングでDeployment Post Processing,Strip Debug Symbols During Copy,Strip Linked ProductをNoに設定します。アプリがクラッシュするとビルドページに表示され、スタックとレースなどを確認することができます。

チェックポイントを設定するには[TestFlight passCheckpoint:@”CHECKPOINT_NAME”];とします。チェックポイントを通過したデバイスの割合をビルドページで確認できます。

フィードバックを行えるビューを表示するには[TestFlight openFeedbackView];とします。フィードバックが送信されるとビルドページで確認して、返信することができます。

UINavigationControllerのアニメーションをカスタマイズする方法

UINavigationControllerでビューコントローラーをpush/popするときのアニメーションはスライドです。このアニメーションをカスタマイズしてみます。

ここではフェードを行ってみます。

まずはpushです。

- (IBAction)push {
    SubViewController *subViewController = [[[SubViewController alloc] initWithNibName:nil bundle:nil] autorelease];
    subViewController.view.frame = self.view.bounds;
    subViewController.view.alpha = 0.0;
    [self.view addSubview:subViewController.view];
 
    [UIView transitionWithView:subViewController.view duration:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void) {
        subViewController.view.alpha = 1.0;
    } completion:^(BOOL finished) {
        [self.navigationController pushViewController:subViewController animated:NO];
    }];
}

pushする対象はSubViewControllerです。現在のビューと同じサイズに設定して、透明にして、現在のビューにaddSubviewします。
アニメーションですが、alphaを1.0にして不透明にします。アニメーションが完了したらpushViewController:animated:でpushを行います。animatedにはNOを指定して、デフォルトのスライドアニメーションを行わないようにします。

次にpopです。

- (IBAction)pop {
    NSArray *viewControllers = [self.navigationController viewControllers];
    NSUInteger previousIndex = [viewControllers count] - 2;
    UIViewController *previousViewController = [viewControllers objectAtIndex:previousIndex];
    previousViewController.view.alpha = 0.0;
    [self.view addSubview:previousViewController.view];
 
    [UIView transitionWithView:previousViewController.view duration:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^(void) {
        previousViewController.view.alpha = 1.0;
    } completion:^(BOOL finished) {
        [self.navigationController popViewControllerAnimated:NO];
    }];
}

viewControllers から、pop後に表示される前のビューコントローラーを取得します。viewControllers配列の後ろから2番目に入っています。このコントローラーのビューのalphaを0.0に設定して透明にして、現在のコントローラーのビューに載せます。
アニメーションも同様にalphaを1.0に設定して、不透明にします。アニメーションが終了したらpopViewControllerAnimated:でpopします。これもNOを指定してデフォルトアニメーションを行わないようにします。

このように、UINavigationControllerでのフェードアニメーションは、push/popはどちらも、これから表示するビューを上に載せて、透明から不透明にアニメーションを行い、アニメーションが終了したタイミングでpush/popを行うことになります。

スレッド毎にNSManagedObjectContextを管理する

基本的にはNSManagedObjectContextはスレッド毎に作成して利用しなければいけません。以下は、その為にスレッド毎にContextを作成して管理するためのコードです。複雑なアプリでなければ、この管理法で十分でしょう。シングルトンなManagerクラスでコンテキストを管理する方法は簡単ですが、スレッドを多用する場合はスレッド毎に管理する方が良いでしょう。

managedObjectContextForCurrentThreadでコンテキストを取得し、保存は[NSManagedObjectContext save:&error]のように、クラスメソッドを呼びます。保存するとNSNotificationでコンテキスト自身のmanagedObjectContextDidSave:が実行され、メインスレッドのコンテキストに変更が反映されます。スレッド毎に自動的にthreadDictionaryにNSManagedObjectContextを登録し、スレッドが破棄されるとコンテキストも破棄されるようになっているので、明示的にコンテキストを破棄する必要はありません。

@interface NSManagedObjectContext (Extras)
+ (NSManagedObjectContext *)managedObjectContextForThread:(NSThread *)thread;
+ (NSManagedObjectContext *)managedObjectContextForCurrentThread;
+ (NSManagedObjectContext *)managedObjectContextForMainThread;
+ (BOOL)save:(NSError **)error;
@end
#import "NSManagedObjectContextExtras.h"
 
NSString * const NSManagedObjectContextThreadKey = @"NSManagedObjectContextThreadKey";
 
@interface NSManagedObjectContext ()
- (void)managedObjectContextDidSave:(NSNotification*)notification;
@end
 
@implementation NSManagedObjectContext (Extras)
 
+ (NSManagedObjectContext *)managedObjectContextForThread:(NSThread *)thread {
    NSMutableDictionary *threadDictionary = [thread threadDictionary];
    NSManagedObjectContext *context = [threadDictionary objectForKey:NSManagedObjectContextThreadKey];
 
    if (!context) {
#ifdef TARGET_OS_IPHONE
        id appDelegate = [[UIApplication sharedApplication] delegate];
#else
        id appDelegate = [NSApp delegate];
#endif
        NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
 
        if ([[NSThread currentThread] isMainThread]) {
            context = mainContext;
        } else {
            context = [[[NSManagedObjectContext alloc] init] autorelease];
            [context setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
        }
 
        [threadDictionary setObject:context forKey:NSManagedObjectContextThreadKey];
    }
 
    return context;
}
 
+ (NSManagedObjectContext *)managedObjectContextForCurrentThread {
    return [NSManagedObjectContext managedObjectContextForThread:[NSThread currentThread]];
}
 
+ (NSManagedObjectContext *)managedObjectContextForMainThread {
    return [NSManagedObjectContext managedObjectContextForThread:[NSThread mainThread]];
}
 
+ (BOOL)save:(NSError **)error {
    NSManagedObjectContext *context = [NSManagedObjectContext managedObjectContextForCurrentThread];
    BOOL isMainThread = [[NSThread currentThread] isMainThread];
 
    if (!isMainThread) {
        [[NSNotificationCenter defaultCenter] addObserver:context
                                                 selector:@selector(managedObjectContextDidSave:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:context];
    }
 
    BOOL result = [context save:error];
 
    if (!isMainThread) {
        [[NSNotificationCenter defaultCenter] removeObserver:context
                                                        name:NSManagedObjectContextDidSaveNotification 
                                                      object:context];
    }
 
    return result;
}
 
- (void)managedObjectContextDidSave:(NSNotification*)notification {
    NSManagedObjectContext *context = [NSManagedObjectContext managedObjectContextForMainThread];
 
    [context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
                              withObject:notification
                           waitUntilDone:YES];
}
@end

iOS5はサードパーティにとってどのような意味があるか

iOS5の標準アプリが充実して、サードパーティつぶしだ、いや開発者ががんばればいいんだ、というような記事が散見されますが、私はそのようなレベルの話ではないと考えています。

私の知人にiPhone4ユーザがいます。その人のPCはネットに繋がっていません。App Storeからアプリもほとんどダウンロードしていないようです。iTunesすら知らないのです。

今後はiPhoneとiPadだけしか使わないという人も現れるでしょう。(既に存在するかな)

そのような人がiPhoneを使うことを考えると、自然と標準アプリの充実やiCloudに行き着くと思うのです。

私は、AppleがiOS5はそのような「ライトユーザ」に向けで開発したものではないかと考えています。ある人の言ですが「新しい携帯としてスマホを選ぶ」という状況になりつつあり、iPhoneもAndroidも同じような状況ではないでしょうか。

AppleはiOS5でサードパーティに「ライトユーザ」という新しい顧客を連れてきてくれます。標準アプリの充実は決してApple vs サードパーティではないのです。そして、今後はこれまで以上に「ライトユーザ」を意識した開発やマーケティングが必要になるから、みんながんばってね♡ by Apple、というのがiOS5がサードパーティにとって意味するところではないでしょうか。

UIAlertViewとUIActionSheetをblocksで使いやすくする

UIAlertViewやUIActionSheetはdelegateで選択結果を取得して処理を分岐します。このdelegateでというのが結構面倒ですが、iOS4からはblocksが利用できるので、blocksを利用すればUIAlertViewをつくると同時にボタンに対応する動作も記述できて使いやすくなります。

ネット上にはblocksでの拡張情報がいくつかありますが、以下がネットの情報を元に私が作成したblocks版UIAlertViewとUIActionSheetです。

CTAlertView.h

#import <UIKit/UIKit.h>
 
@class CTAlertView;
 
typedef void (^CTAlertViewButtonCallback)(CTAlertView *alertView, NSInteger index);
 
@interface CTAlertView : UIAlertView <UIAlertViewDelegate> {
@private
    void (^willPresentCallback_)(CTAlertView *);
    void (^didPresentCallback_)(CTAlertView *);
    void (^willDismissCallback_)(CTAlertView *, NSInteger);
    void (^didDismissCallback_)(CTAlertView *, NSInteger);
 
    NSMutableArray *buttonCallbacks_;
}
 
- (id)initWithTitle:(NSString *)title
            message:(NSString *)message
  cancelButtonTitle:(NSString *)cancelButtonTitle;
 
-(void)addButtonWithTitle:(NSString*)title callback:(CTAlertViewButtonCallback)callback;
-(void)setCancelButtonCallback:(CTAlertViewButtonCallback)block;
-(void)setWillPresentCallback:(void (^)(CTAlertView *alertView))block;
-(void)setDidPresentCallback:(void (^)(CTAlertView *alertView))block;
-(void)setWillDismissCallback:(void (^)(CTAlertView *alertView, NSInteger index))block;
-(void)setDidDismissCallback:(void (^)(CTAlertView *alertView, NSInteger index))block;
 
@end

CTAlertView.h

#import "CTAlertView.h"
 
@interface CTAlertView ()
 
@property (nonatomic, retain) NSMutableArray *buttonCallbacks;
 
- (BOOL)hasCancelButton;
 
@end
 
@implementation CTAlertView
 
@synthesize buttonCallbacks=buttonCallbacks_;
 
- (id)initWithTitle:(NSString *)title
            message:(NSString *)message
  cancelButtonTitle:(NSString *)cancelButtonTitle
{
    self = [super initWithTitle:title message:message delegate:nil cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil];
    if (self) {
        self.delegate = self;
        self.buttonCallbacks = [NSMutableArray array];
        if ([self hasCancelButton]) {
            [self setCancelButtonCallback:^(CTAlertView *alertView, NSInteger index){}];
        }
    }
    return self;
}
 
- (void)dealloc
{
    [willPresentCallback_ release];
    [didPresentCallback_ release];
    [willDismissCallback_ release];
    [didDismissCallback_ release];
    self.buttonCallbacks = nil;
 
    [super dealloc];
}
 
-(void)addButtonWithTitle:(NSString*)title callback:(CTAlertViewButtonCallback)callback
{
    id obj = [NSNull null];
    if (callback) obj = [[callback copy] autorelease];
 
    [self addButtonWithTitle:title];
    [self.buttonCallbacks addObject:obj];
}
 
-(void)setCancelButtonCallback:(CTAlertViewButtonCallback)callback
{
    if (![self hasCancelButton]) return;
 
    if ([self.buttonCallbacks count] &gt; 0) {
        [self.buttonCallbacks removeObjectAtIndex:0];
    }
 
    [self.buttonCallbacks insertObject:[[callback copy] autorelease] atIndex:0];
}
 
-(void)setWillPresentCallback:(void (^)(CTAlertView *alertView))block
{
    [willPresentCallback_ release];
    willPresentCallback_ = [block retain];
}
 
-(void)setDidPresentCallback:(void (^)(CTAlertView *alertView))block
{
    [didPresentCallback_ release];
    didPresentCallback_ = [block retain];
}
 
-(void)setWillDismissCallback:(void (^)(CTAlertView *alertView, NSInteger index))block
{
    [willDismissCallback_ release];
    willDismissCallback_ = [block retain];
}
 
-(void)setDidDismissCallback:(void (^)(CTAlertView *alertView, NSInteger index))block
{
    [didDismissCallback_ release];
    didDismissCallback_ = [block retain];
}
 
- (BOOL)hasCancelButton
{
    return (self.cancelButtonIndex == 0);
}
 
#pragma mark -
#pragma mark UIAlertViewDelegate
 
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    id callback = [self.buttonCallbacks objectAtIndex:buttonIndex];
    if (![callback isMemberOfClass:[NSNull class]]) {
        ((CTAlertViewButtonCallback)callback)((CTAlertView *)alertView, buttonIndex);
    }
}
 
-(void)willPresentAlertView:(UIAlertView *)alertView
{
    if(willPresentCallback_) willPresentCallback_((CTAlertView *)alertView);
}
 
-(void)didPresentAlertView:(UIAlertView *)alertView
{
    if(didPresentCallback_) didPresentCallback_((CTAlertView *)alertView);
}
 
-(void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if(willDismissCallback_) willDismissCallback_((CTAlertView *)alertView, buttonIndex);
}
 
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if(didDismissCallback_) didDismissCallback_((CTAlertView *)alertView, buttonIndex);
}
 
@end

CTActionSheet.h

#import <UIKit/UIKit.h>
 
@class CTActionSheet;
 
typedef void (^CTActionSheetButtonCallback)(CTActionSheet *actionSheet, NSInteger index);
 
@interface CTActionSheet : UIActionSheet <UIActionSheetDelegate> {
@private
    NSMutableArray *buttonCallbacks_;
    void (^willPresentCallback_)(CTActionSheet *);
    void (^didPresentCallback_)(CTActionSheet *);
    void (^willDismissCallback_)(CTActionSheet *, NSInteger);
    void (^didDismissCallback_)(CTActionSheet *, NSInteger);
}
 
- (id)initWithTitle:(NSString *)title;
 
- (void)addCancelButtonWithTitle:(NSString *)title callback:(CTActionSheetButtonCallback)callback;
- (void)addDestructiveButtonWithTitle:(NSString *)title callback:(CTActionSheetButtonCallback)callback;
- (void)addButtonWithTitle:(NSString *)title callback:(CTActionSheetButtonCallback)callback;
- (void)setWillPresentCallback:(void (^)(CTActionSheet *actionSheet))callback;
- (void)setDidPresentCallback:(void (^)(CTActionSheet *actionSheet))callback;
- (void)setWillDismissCallback:(void (^)(CTActionSheet *actionSheet, NSInteger index))callback;
- (void)setDidDismissCallback:(void (^)(CTActionSheet *actionSheet, NSInteger index))callback;
 
@end

CTActionSheet.m

#import "CTActionSheet.h"
 
@interface CTActionSheet ()
 
@property (nonatomic, retain) NSMutableArray *buttonCallbacks;
 
@end
 
@implementation CTActionSheet
 
@synthesize buttonCallbacks=buttonCallbacks_;
 
- (id)initWithTitle:(NSString *)title
{
    self = [super initWithTitle:title delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
    if (self) {
        self.delegate = self;
        self.buttonCallbacks = [NSMutableArray array];
    }
    return self;
}
 
- (void)dealloc
{
    self.buttonCallbacks = nil;
    [willPresentCallback_ release];
    [didPresentCallback_ release];
    [willDismissCallback_ release];
    [didDismissCallback_ release];
 
    [super dealloc];
}
 
- (void)addCancelButtonWithTitle:(NSString *)title callback:(CTActionSheetButtonCallback)callback
{
    NSUInteger index = [self.buttonCallbacks count];
    [self addButtonWithTitle:title callback:callback];
    self.cancelButtonIndex = index;
}
 
- (void)addDestructiveButtonWithTitle:(NSString *)title callback:(CTActionSheetButtonCallback)callback
{
    NSUInteger index = [self.buttonCallbacks count];
    [self addButtonWithTitle:title callback:callback];
    self.destructiveButtonIndex = index;
}
 
- (void)addButtonWithTitle:(NSString*)title callback:(CTActionSheetButtonCallback)callback
{
    id obj = [NSNull null];
    if (callback) obj = [[callback copy] autorelease];
 
    [self addButtonWithTitle:title];
    [self.buttonCallbacks addObject:obj];
}
 
-(void)setWillPresentCallback:(void (^)(CTActionSheet *actionSheet))callback
{
    [willPresentCallback_ release];
    willPresentCallback_ = [callback retain];
}
 
-(void)setDidPresentCallback:(void (^)(CTActionSheet *actionSheet))callback
{
    [didPresentCallback_ release];
    didPresentCallback_ = [callback retain];
}
 
-(void)setWillDismissCallback:(void (^)(CTActionSheet *actionSheet, NSInteger index))callback
{
    [willDismissCallback_ release];
    willDismissCallback_ = [callback retain];
}
 
-(void)setDidDismissCallback:(void (^)(CTActionSheet *actionSheet, NSInteger index))callback
{
    [didDismissCallback_ release];
    didDismissCallback_ = [callback retain];
}
 
#pragma mark -
#pragma mark UIActionSheetDelegate
 
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    id callback = [self.buttonCallbacks objectAtIndex:buttonIndex];
    if (![callback isMemberOfClass:[NSNull class]]) {
        ((CTActionSheetButtonCallback)callback)((CTActionSheet *)actionSheet, buttonIndex);
    }
}
 
- (void)willPresentActionSheet:(UIActionSheet *)actionSheet
{
    if (willPresentCallback_) willPresentCallback_((CTActionSheet *)actionSheet);
}
 
- (void)didPresentActionSheet:(UIActionSheet *)actionSheet
{
    if (didPresentCallback_) didPresentCallback_((CTActionSheet *)actionSheet);
}
 
- (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (willDismissCallback_) willDismissCallback_((CTActionSheet *)actionSheet, buttonIndex);
}
 
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (didDismissCallback_) didDismissCallback_((CTActionSheet *)actionSheet, buttonIndex);
}
 
@end

使い方は

CTAlertView *alert = [[[CTAlertView alloc] initWithTitle:@"title" message:@"message" cancelButtonTitle:@"Cancel"] autorelease];
[alert setCancelButtonCallback:^(CTAlertView *alertView, NSInteger index) {
    // Cancel action
}];
[alert addButtonWithTitle:@"Foo" callback:^(CTAlertView *alertView, NSInteger index) {
    // Foo action
}];
[alert addButtonWithTitle:@"Bar" callback:^(CTAlertView *alertView, NSInteger index) {
    // Bar action
}];
[alert show];

のようにします。キャンセルタイトルは指定しなければキャンセルボタンは作成されません。また、キャンセルボタンコールバックを指定しなくても動作します。

CTActionSheetではキャンセルボタンの指定を- (void)addCancelButtonWithTitle:(NSString *)title callback:(CTActionSheetButtonCallback)callbackで行います。

また、テストは不十分なので、バグがあればコメントよろしくです。

UIButtonのハイライトを持続する方法

UIButtonは標準動作ではタップするとハイライト(青くなる、もしくは画像を設定している場合は黒っぽくなる)した後に元の状態に戻る。

これをハイライトしたままにするには以下のように一瞬時間を置いて、ハイライトを設定し、enabledをオフにする。

- (IBAction)buttonTapped:(id)sender
{
    // senderはUIButtonとする
    [self performSelector:@selector(highlightButton:) withObject:sender afterDelay:0.0];
}
 
- (void)highlightButton:(UIButton *)button
{
    button.highlighted = YES;
    button.enabled = NO;
}

もしくは

- (IBAction)buttonTapped:(id)sender
{
    UIButton *button = (UIButton *)sender;
    [[NSOperationQueue mainQueue] performSelector:@selector(addOperationWithBlock:)
                                       withObject:[[^{
                                           button.highlighted = YES;
                                           button.enabled = NO;
                                       } copy] autorelease]
                                       afterDelay:0.0];
}

この方法ではボタンのタッチには反応しなくなるのでトグル動作は出来ない。ハイライトを消してタッチに反応するようにするには、highlighted=NO/enabled=YESを設定する。

また、トグル動作が可能かUITapGestureRecognizerを設定してみたが、やはりenabledがNOの場合はジェスチャにも反応しないので、トグル動作にはならなかった。

Xcode4がダウンロードできなかったらLicense Agreementを更新するといい

Xcode4が出ました。新しもの好きの私は早速ダウンロード…しようとしたらAccess Denied。

ネットで調べたらキーチェーンがどうちゃらこうちゃら。その通りに調べてみても問題なし。

で、ADCサイトをブラウズしてたらiOS Developer Program License Agreementのアップデートをしてくれと表示される。ハッ!と思って、アップデートして、再度Xcode4をダウンロードしてみたら正常に開始。

Twitterで他にも同様の問題が発生しているとつぶやいてる人に、この現象を伝えたら、その人もLicense Agreementの更新でダウンロードができたと返事が。

ということで、Xcode4ダウンロードでAccess DeniedになるのはiOS Developer Program License Agreementの更新をしてなかったのが原因みたい。