2012年3月31日土曜日

数値定数の定義


#defineを使った定義

objc.hの事例

YESを(BOOL)1、NOを(BOOL)0に定義

#define YES    (BOOL)1
#define NO     (BOOL)0

プラットフォームに応じてARITH_SHIFTを32、ないしは16に定義
#if defined(__LP64__)
typedef long arith_t;
typedef unsigned long uarith_t;
# define ARITH_SHIFT 32
#elsetypedef int arith_t;
typedef unsigned uarith_t;
# define ARITH_SHIFT 16
#endif





外部変数による定義(参照: CString定数の定義

NSStringの例
NSMaximumStringLengthをINT_MAX-1に定義

NSString.h
FOUNDATION_EXPORT const NSUInteger NSMaximumStringLength;

NSString.m
const NSUInteger NSMaximumStringLength=INT_MAX-1;

自分のクラスに実装する場合の例

MyClass.h
    extern const NSInteger MyIntergerConstant;

MyClass.m
    const NSInteger  MyIntergerConstant = 123;

enumによる定義

値を指定する例

NSString.h

enum {
   NSCaseInsensitiveSearch = 0x01,
   NSLiteralSearch = 0x02,
   NSBackwardsSearch = 0x04,
   NSAnchoredSearch = 0x08,
   NSNumericSearch = 0x40
};

関連Blog
NSString定数の定義
数値定数の定義

C言語の手法


Objective-CはCのスーパーセットであるため、C言語をそのまま使用できます。
AppleのドキュメントではObjective-Cについては詳しく説明されていますが、Cについては前提事項のため、あまり言及がありません。私もあまり詳しくありませんが、なにかと便利ということをいくつか挙げてみます。

objc_runtime.h、TargetConditionals.hなどを見ると様々な事例を見ることができます。 開発時の応用できそうな個所をいくつかピックアップしてみます。

#define 識別子 置換文字列

ソース中の識別子を置換文字列に置き換える。
事例はtypedefを参照

#if ~ #elseif ~ #else ~ #endif

#if 定型式1
[定型式1がtrueのときにコンパイル対象となる記述]
#elseif 定型式2
  [定型式2がtrueのときにコンパイル対象となる記述]
#else
  [その他の場合にコンパイル対象となる記述]
#endif

事例はtypedefを参照

typedef

プラットフォームに応じてlongないしはint型のNSInteger、NSUIntegerを定義している個所です。

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif


NSStringをtypedefで別の型として宣言することもできます。
typedef NSString* SomeStringType;

typedefにより宣言した変数は、元の型の値を使用してもコンパイルエラーとはならず警告も出ないため厳密な型チェックは期待できませんが、これを引数や戻り値の型に使用するとコードが読みやすくなり、補完機能で候補を探すときに便利になります。


--------------------------------------------------------------

コンスタント文字列連結

NSString *str = @"ABC"
                 "DEF"
                 "GHI";

上の記述は次のように連結されます。

NSString *str = "ABCDEFGHI";

長い文字列を書くときなど適度に改行を加えると見やすくなります。

.stringsファイルの場合は上記の方法ではエラーになりますが、行末の改行をエスケープすることで同様のことが行えます。

"SomeWords" = "ABC\
CDE\
EFG";

この場合はエスケープされた改行も結合した文字列に含まるため、次の書き方と同等になります。
"SomeWords" = "ABC\nCDE\n EFG";

NSLogでのC Stringの表示

NSLog(@"%s", [NSData bytes]);

NSDataの中身が文字列(Cのchar*)と分かっている場合はNSStringにしなくてもNSLog表示が行えます。可読かどうかはエンコード次第です。

2012年3月30日金曜日

NSString定数の定義


Cocoaから事例をひとつ取り上げてみます。
ヘッダファイルNSBundle.hに次の行があります。

FOUNDATION_EXPORT NSString * const NSLoadedClasses;

FOUNDATION_EXPORTはobjc_runtime.hでマクロ定義されており、Objective-Cの場合はexternとなります。(実際は#ifで場合分けされています。)

#define FOUNDATION_EXTERN extern
#define FOUNDATION_EXPORT  FOUNDATION_EXTERN

上記の例の場合はNSBundle.mで変数NSLoadedClassesが宣言、定数設定されます。

NSString * const NSLoadedClasses=@"NSLoadedClasses";

これにより、NSBundle.hをimportすると外部定義変数としてNSLoadedClassesが参照解決できるようになり、コンパイルするとNSBundle.mで宣言されている@"NSLoadedClasses"が値となります。

これに倣って自分で使う場合は、.hファイルで外部変数の宣言をし、.mで変数宣言、定数設定をします。

SomeClass.h
    extern NSString * const SomeConstant;
    @interface SomeClass
    @end

SomeClass.m
    NSString * const  SomeConstant = @"SomeConstValue";
    @implements SomeClass
    @end

利用するクラスでSomeClass.hをimportします。

#import "SomeClass.h"

NSStringの文字列を比較する場合は通常はisEqualsToStringを使いますが、定義済みの定数どうしであれば==で比較することができます。

参考: Constants in Objective C


#defineによる定数定義

もうひとつの文字列定数の定義に#defineマクロを使う方法があります。
#define SOME_WORD @"SomeWordString"

サンプルアプリのReachability.hでは次のようなマクロによる定義が使われています。
#define kReachabilityChangedNotification
@"kNetworkReachabilityChangedNotification"

実はconstを使った定義と#defineでは結果は同じです。
次のようなコードを書きます。

MyClass.h
#define SOME_WORD @"SomeWordString"
extern NSString * const someWord;

MyClass.m
NSString * const someWord = @"SomeWordString";

そして次のように各々のアドレスを調べてみると、どちらも同一のオブジェクトであることがわかります。
NSLog(@"SOME_WORD = %p", SOME_WORD);
NSLog(@"someWord = %p", someWord);
NSLog(@"(SOME_WORD == someWord) = %d", (SOME_WORD == someWord));

これはソース中に@""で直書きされたNSStringはconstとして扱われ、constについては同一の値のものはひとつだけデータ領域に確保されるためです。

重要な違いは、同一名のマクロを別の値で再定義するとエラーにはならず、コンパイラがマクロを解釈する時点で最後に定義されたものが有効になり、一方constを使った場合はグローバル変数で同一名の変数が二度宣言されるとエラーになるということです。そのため、#defineで同一名のマクロを異なる値で再定義すると、ソース中は同じSOME_WORDでも別オブジェクトを指すことになります。

また、マクロの場合はプリプロセッサで文字列置き換えを行うものなので、大量にあると若干コンパイル時のオーバーヘッドになるかもしれません(気付くほどではないかもしれませんが)。

Cocoaでは概ね次のような場合に#defineによる定義が使用されているようです。
・同一ファイルまたはクラス、ないしは限定的な範囲で使用されるもの
・数値の場合(NSIntegerなどオブジェクトでないもの、BOOL含む)

ちなみに、Cocoaでは基本的に#defineを使う場合はSOME_WORDのように全て大文字で文字区切りを"_"とし、constを使う場合はPrefix+単語の先頭大文字+小文字(例:MYSomeWord)となっています。ただし、#defineで数値定数を定義する場合は後者と同じ命名規則になっているものも多く見かけます。

typedefと併用するとより定数らしくなります。

SomeClass.h
typedef NSString* SomeStringType;
extern SomeStringType const strType1;

SomeClass.m
SomeStringType const strType1 = @"strType1";

定数として定義されたNSStringであれば、isEqualToSting:ではなく、==で一致判断が行えます。


関連Blog
NSString定数の定義
数値定数の定義

UIWebViewの画面タップトラップ


UIWebViewの画面がタップされたことを検知しようとしたところ、UIWebViewは内部にUIScrollViewを持ち、さらに実際の描画はそのsubviewが行うため、UIWebViewのサブクラスにイベントを受け取るメソッド(-touchesBegan:withEvent:等)をオーバライドしても呼ばれません。

この場合、hitTestを利用すると画面タップを検知することができます。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    //必要であればevantをチェック
    //superのメソッド呼び出し前後で処理を行う。

    UIView  *v = [super hitTest:point withEvent:event];

    //vは実際にイベントに応答するオブジェクト。イベントを横取りするのでなければvを返す。

    return v;
}

UIViewController(のサブクラス)でも同様の方法でタップを検知できます。

UIViewControllerのviewがUIWebViewの場合は、次のようにhitTestメッセージが送られ、なんらかのscrollViewのサブビューが応答し、そのオブジェクトにイベント関連メッセージが送られます。

UIViewController
⇒UIWebView
    ⇒UIScrollVeiw
        ⇒content view(s)

そのため、UIViewControllerやUIWebViewのイベント関連メソッドをオーバライドしても呼ばれません。

[補足]
hitTestも利用可能ですが、UIGestureRecognizerを使うのがよさそうです。

symbol(s) not found for architectureエラー


必要なframeworkが追加されていない場合に次のようなエラーが発生します。

Undefined symbols for architecture armv7:
  "...", referenced from ....
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

デフォルトで追加されるframework以外のクラスを使用する場合、ヘッダファイルをimportすればコーディング中はクラス名、メソッド名が解決されますが、ビルドするとリンクできないため発生します。

UIViewのanimation実行とキャンセル


someViewを一秒で透明化する例です。

[UIView animateWithDuration:1.0
                         animations: ^{ someView.alpha = 0.0; } ];

someViewはUIView(またはそのサブクラス)であれば何でもかまいません。

アニメーション実行中はイベントを受け付けなくなるため、このビューがイベントに応答するものだと操作性に問題がでる場合があります。

たとえば画面がタップされたらアニメーションをキャンセルし、イベントに応答できるようにするといった場合は、次の要領でアニメーションをキャンセルすることができます。

[someView.layer removeAllAnimations];

//この例の場合はalpha値を元に戻す。

someView.alpha = 1.0;

layerにアクセスするためにはQuatsCore.frameworkを追加する必要があります。

2012年3月28日水曜日

連絡先(Address Book)を利用する方法


連絡先(Address Book)を利用するAPIが用意されています。

ABPeoplePickerNavigationControllerを利用することで連絡先の画面を自分のアプリ内で表示し、選択された人の情報を取得できます。

・プロジェクトにAddressBook.framework, AddressBookUI.frameworkを追加する。

・Address Bookを利用するクラス宣言にMFMailComposeViewControllerDelegateプロトコルを追加する。

#import <MessageUI/MFMailComposeViewController.h>
#import <AddressBookUI/AddressBookUI.h>

@interface MyViewController : UIViewController <MFMailComposeViewControllerDelegate, ABPeoplePickerNavigationControllerDelegate>
@end

#import <AddressBookUI/ABPeoplePickerNavigationController.h>
#import <AddressBook/ABPerson.h>

@implementation
{
    NSString *selectedMailAddr;

}

//連絡先表示
- (IBAction)showAddressBook:(id)sender
{
    selectedMailAddr = nil;
    ABPeoplePickerNavigationController *picker =
    [[ABPeoplePickerNavigationController alloc] init];
    picker.displayedProperties = @[@(kABPersonEmailProperty)];
    if ([picker respondsToSelector:@selector(setPredicateForEnablingPerson:)])
    {
        picker.predicateForEnablingPerson
            = [NSPredicate predicateWithFormat:@"emailAddresses.@count > 0"];
    }
    //picker.delegate = self; //この例では不要
    picker.peoplePickerDelegate = self;
    [self presentViewController:picker animated:YES completion:nil];
}

- (void)mailComposeController:(MFMailComposeViewController*)controller
          didFinishWithResult:(MFMailComposeResult)result
                        error:(NSError*)error
{
    //必要であればエラー処理
    //モーダルビュー解除
    [self dismissViewControllerAnimated:YES completion:nil];

}

//連絡先UIで行がタップされたときに呼ばれる。連絡先画面に留まらないときはNOを返す。
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
      shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
    [self setMailAddrWithPerson:person];
    return NO;
}

//これは呼ばれなかったが、念のため追加。
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
      shouldContinueAfterSelectingPerson:(ABRecordRef)person
                                property:(ABPropertyID)property
                              identifier:(ABMultiValueIdentifier)identifier
{
    [self setMailAddrWithPerson:person];
    return NO;
}

//iOS8 連絡先UIで行がタップされたときに呼ばれる。
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
                         didSelectPerson:(ABRecordRef)person
{
    [self setMailAddrWithPerson:person];
}

//iOS8 これは呼ばれなかったが、念のため追加。
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
                         didSelectPerson:(ABRecordRef)person
                                property:(ABPropertyID)property
                              identifier:(ABMultiValueIdentifier)identifier
{
    [self setMailAddrWithPerson:person];

}
- (void)setMailAddrWithPerson:(ABRecordRef)person
{
    ABMultiValueRef addrs = (ABMultiValueRef)ABRecordCopyValue(person, kABPersonEmailProperty);
    NSString *addr = nil;
    long addrCount = ABMultiValueGetCount(addrs);
    if (addrCount == 0) {
        // [NLUtil showAlert:NSLocalizedString(@"NoAddrsFound", nil)];
        //この場合にNOを返して元の画面に戻る。連絡先UIに留まる場合はYESを返す。
    } else if (addrCount > 1) {
        //メールアドレスが複数ある場合はUIActionSheetを使用してそのうちのひとつを選択する。
        UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"メールアドレス選択"
                                                                 delegate:self
                                                        cancelButtonTitle:@"キャンセル"
                                                   destructiveButtonTitle:nil
                                                        otherButtonTitles:nil];
        actionSheet.tag = 9;
        //複数のアドレスをUIActionSheetのボタンにセット。
        for(int i=0; i<addrCount; i++) {
            [actionSheet addButtonWithTitle:(__bridge_transfer NSString *) ABMultiValueCopyValueAtIndex(addrs, i)];
        }
        //モーダルビューを解除する。
        //iOS7ではAnimated:NOpeoplePickerを閉じないと
        //'Sheet can not be presented because the view is not in a window: "エラーが発生する。
        [self dismissViewControllerAnimated:NO completion:nil];
        //UIActionSheetを表示。
        [actionSheet showInView:self.view];
    } else {
        //アドレスがひとつだけのときはそれをメールアドレスのテキストフィールドにセットする。
        addr = (__bridge_transfer NSString *) ABMultiValueCopyValueAtIndex(addrs, 0);
        selectedMailAddr = [addr copy];
        //モーダルビューを解除する。
        //if ([NLUtil isOS6] || [NLUtil isOS7])
            [self dismissViewControllerAnimated:YES completion:nil];
        [self sendMail];
    }
}

//actionSheetタップ
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == actionSheet.cancelButtonIndex) return;
    selectedMailAddr = [actionSheet buttonTitleAtIndex:buttonIndex];
    [self sendMail];
}

#pragma mark - Mail送信

- (void)sendMail
{
    if (![MFMailComposeViewController canSendMail])
    {
        //showAlert: メール送信不可
        return;
    }
    if (!selectedMailAddr) return;
    
    MFMailComposeViewController *mailView = [[MFMailComposeViewController alloc] init];
    //メール送信後のメッセージを受け取るため、mailViewdelegateselfをセット。
    mailView.mailComposeDelegate = self;
    //アドレス、サブジェクト等の設定。
    [mailView setToRecipients:@[selectedMailAddr]];
    [mailView setSubject:[NSString stringWithFormat:@"テストメール"]];
    [mailView setMessageBody:@"テストメール" isHTML:NO];
    //MFMailComposeViewControllerをモーダルビューで表示。
    [self presentViewController:mailView animated:YES completion:NULL];
}

- (void)dealloc
{
    selectedMailAddr = nil;
}

@end

参照: iOS Address Bookプログラミングガイド
        Address Book Programming Guide for iOS

添付つきメール送信


iOSアプリからMFMailComposeViewControllerを使用してメール送信することができます。

プロジェクトにMessageUI.frameworkを追加する。
MFMailComposeViewControllerを呼ぶオブジェクト(おそらくはUIViewControllerまたはそそのサブクラスに)MFMailComposeViewControllerDelegateプロトコルをクラス宣言に追加する。

#import <MessageUI/MFMailComposeViewController.h>

@interface MyViewController : UIViewController <MFMailComposeViewControllerDelegate>
@end

//MyViewControllerのimplementation
//[メール]ボタンがタップされた
- (IBAction)mySendMailMethod:(id)sender
{
    //メール送信可能な設定になっているかチェック
    if (![MFMailComposeViewController canSendMail])
    {
        //エラー処理
        return;
    }
    MFMailComposeViewController *mailView = [[MFMailComposeViewController alloc] init];
//メール送信後のメッセージを受け取るため、mailViewのdelegateにselfをセット。
    mailView.mailComposeDelegate = self;
//アドレス、サブジェクト等の設定。
    [mailView setToRecipients:[NSArray arrayWithObjects:@"someone@somewhere.com", nil]];
    [mailView setSubject:@"someSubject"];
    [mailView setMessageBody:@"someMessage" isHTML:NO];
    //添付データをセット。
    NSData *binData = someBinData;
    if (binData != nil) {
        [mailView addAttachmentData:binData mimeType:@"application/octet-stream" fileName:@"someFileName"];
    }
//MFMailComposeViewControllerをモーダルビューで表示。
    [self presentModalViewController:mailView animated:YES];
}

#pragma mark - <UINavigationControllerDelegate>

- (void)mailComposeController:(MFMailComposeViewController*)controller
          didFinishWithResult:(MFMailComposeResult)result
                        error:(NSError*)error
{
    //必要であればエラー処理
    //モーダルビュー解除
    [self dismissModalViewControllerAnimated:YES];
}

参考: How to Send Email with Attachments

mailto:スキーマでメールアプリを起動することもできます。

2012年3月25日日曜日

core data modelの不要なバージョンを削除する方法


core data modelのバージョンを追加したが不要なものを削除したいと思ってもメニューからはできません。
次のページに書き込まれていた方法でうまきいきました。投稿者が"hack"と書いていますが、今のところこれ以外の方法は見つかりません。
  1. core data model の Current version を残したいものに設定する。
  2. .xcdatamodeld を"Remove Reference Only"でプロジェクトから削除する。
  3. .Finderで.xcdatamodeldを開き、不要な  .xcdatamodel ファイルを削除する。
  4. Xcodeで .xcdatamodeldをプロジェクトに追加する。

Core Data 自動マイグレーション


おおまかな手順は次のとおりです。
現在のxcdatamodelファイルをModel.xcdatamodelとします。

1.Xcode左ペインで Model.xcdatamodel を選択する。

2.メニューのEditorから"Add Model Version..."を選択する。

3.保存名(デフォルト:Model 2.xcdatamodel)、保存先を設定/選択する。

4.保存すると、次のようなModel.xcdatamodeldが作られる。
    Model.xcdatamodeld
      Model.xcdatamodel
      Model 2.xcdatamodel
   
5.Model 2.xcdatamodelをカレントモデルに設定する。

6.自動マイグレーションのためのコードを実装する。

自動マイグレーションの実装例
(Core Dataの追加でメソッドを実装した場合)

MYAppDelegate.m

#import "MYAppDelegate.h"
#import "Person.h"

@implementation MYAppDelegate
{
    NSManagedObjectContext *_managedObjectContext;
    NSPersistentStoreCoordinator *_persistentStoreCoordinator;
    NSManagedObjectModel *_managedObjectModel;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext == nil) {
        NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
        if (coordinator != nil) {
            _managedObjectContext = [[NSManagedObjectContext alloc] init];
            [_managedObjectContext setPersistentStoreCoordinator:coordinator];
        }
    }
    return _managedObjectContext;
}

-(NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel == nil) {
        _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    }
    return _managedObjectModel;
}

//プロパティpersistentStoreCoordinatorのgetterメソッド。
//persistentStoreCoordinatorWithOptionをoption=nilで呼び、その戻り値を返す。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    return [self persistentStoreCoordinatorWithOption:nil];
}

//オプション指定可のpersistentStoreCoordinator getterメソッド。migrarion時にオプションを追加する。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinatorWithOption:(NSDictionary *)options
{
    if (_persistentStoreCoordinator == nil) {
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                       initWithManagedObjectModel:[self managedObjectModel]];
     
        NSError *error = nil;
        if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                      configuration:nil
                                                                URL:[self coreDataStoreURL]
                                                            options:options
                                                              error:&error])
        {
           //エラー処理
            NSLog(@"persistentStoreCoordinator: Error %@, %@", error, [error userInfo]);
        }
    }
    return _persistentStoreCoordinator;
}

//アプリケーションのDocumentsフォルダ。CoreDataをここに保存する。
- (NSString *)applicationDocumentsDirectory {
    return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

//Core Data保存先。保存先がひとつだけの場合。
- (NSURL *)coreDataStoreURL
{
    return [NSURL fileURLWithPath:[[self applicationDocumentsDirectory]
                                   stringByAppendingPathComponent: @"CoreData.sqlite"]];
}

//---------- Core Data Migration 関連 ----------

//CurrentのModelと保存されているCoreDateと整合がとれているかチェック。
//不整合の場合はマイグレーションが必要なのでYESを返す。
- (BOOL)shouldPerformCoreDataMigration
{
    NSError *error = nil;  
    NSDictionary *storeMetadata = [NSPersistentStoreCoordinator
                                    metadataForPersistentStoreOfType:NSSQLiteStoreType
                                    URL:[self coreDataStoreURL]
                                    error:&error];
 
    if (storeMetadata == nil)
    {
        return NO;
    }
    //NSManagedObjectModel *destinationModel = [self managedObjectModel];
    BOOL isCompatibile = [[self managedObjectModel]
                          isConfiguration:nil
                          compatibleWithStoreMetadata:storeMetadata];
    return !isCompatibile;
}

//Core Dataの自動マイブレーションを行う。成功した場合はYES、失敗の場合はNOを返す。
- (BOOL)performMigration
{
    //自動マイグレーションのためのオプション
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    //_persistentStoreCoordinatorを作り直すためnilにし、persistentStoreCoordinatorWithOptionを呼ぶ。
    _persistentStoreCoordinator = nil;
    [self persistentStoreCoordinatorWithOption:options];      
    NSError *error;
    if (error)
    {
        //エラー処理
        NSLog(@"error: %@", error);
        return NO;
    }
    return YES;
}

//--------- UIApplication delegate / 起動直後にマイグレーションを実行する ---------

- (BOOL)application:(UIApplication *)application
             didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSPersistentStoreCoordinator *persistentStoreCoordinator =  [self persistentStoreCoordinator];
    // update
    if ([self shouldPerformCoreDataMigration])
    {
        if ([self performMigration] == NO)
        {
            _persistentStoreCoordinator = nil;
        }
        [self persistentStoreCoordinator];
    }
    return YES;
}

@end


文字表示だけのUITabBarItem


UITabBarItemはアイコン表示が主体で文字は補助的なため、フォントサイズが小さく、またアイコンがない場合はボタンの下側に表示されるため情けない状態になります。
文字表示だけのUITabBarItemを使う場合は、少々コーディングが必要になります。
3つボタンの場合の例です。

//UIViewController(またはそのサブクラス)

@property (weak, nonatomic) IBOutlet UITabBar *tabBar;

- (void)loadView
{
    [super loadView];
    ...
    UITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@"タブ1" image:nil tag:1];
    UITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@"タブ2" image:nil tag:2];
    UITabBarItem *item3 = [[UITabBarItem alloc] initWithTitle:@"タブ3" image:nil tag:3];
 
    NSMutableDictionary *attrDict
        = [NSMutableDictionary dictionaryWithObject:[UIFont boldSystemFontOfSize:18.0]
                                             forKey:(id)aKey:UITextAttributeFont];

    [item1 setTitleTextAttributes:attrDict forState:UIControlStateNormal];
    [item2 setTitleTextAttributes:attrDict forState:UIControlStateNormal];
    [item3 setTitleTextAttributes:attrDict forState:UIControlStateNormal];

    UIOffset offset1 = UIOffsetMake(item1.titlePositionAdjustment.horizontal, -11.0);
    UIOffset offset2 = UIOffsetMake(item2.titlePositionAdjustment.horizontal, -11.0);
    UIOffset offset3 = UIOffsetMake(item3.titlePositionAdjustment.horizontal, -11.0);

    [item1 setTitlePositionAdjustment:offset1];
    [item2 setTitlePositionAdjustment:offset2];
    [item3 setTitlePositionAdjustment:offset3];

    tabBar.items = [NSArray arrayWithObjects:item1, item2, item3, nil];
    tabBar.delegate = self;
}

#pragma mark - UITabBarDelegate method

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
    [self saveUserDefaults];
    switch(item.tag) {
        case 1: ...; break;
        case 2: ...; break;
        case 3: ...; break;
    }
}

--------------------------------------------------------------

予めstoryboardでタイトルを設定してある場合は、次の要領で属性だけ変更することができます。

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.delegate = self;
    [self.navigationItem setRightBarButtonItem:nil animated:NO];

    UIOffset os1 = UIOffsetMake(0, -11.0);
    float fontSize = 16.0;
    UIFont *font = [UIFont boldSystemFontOfSize:fontSize];
    NSDictionary *attrDict = [NSDictionary dictionaryWithObject:font
                                                                  forKey:UITextAttributeFont];
    
    for(UITabBarItem *tabBarItem in self.tabBar.items) {
        [tabBarItem setTitleTextAttributes:attrDict forState:UIControlStateNormal];
        [tabBarItem setTitlePositionAdjustment:os1];
    }

}

2012年3月23日金曜日

UITableViewのヘッダー、フッターの上下マージン調整


UITableViewのデリゲートメソッドを実装し、sectionごとにヘッダー、フッターの高さを指定し、マージンを調整します。


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    switch(section) {
        case 0: return @"書類選択";
        case 1: return @"検索条件";
        case 2: return @"ファイル表示順序";
    }
    return @"";
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    switch(section) {
        case 0: return 32.0;
        case 1: return 32.0;
        case 2: return 32.0;
    }
}

SOAPサーバーでString配列を返した場合


SOAPレスポンスでバイナリデータとファイル名などのメタ情報を同時に受信したい場合に、BASE64エンコードされたバイナリデータは文字列ですから、Stringの配列を使うことができます。

DOT.NETのWebServiceで戻り値をList<String>とした場合のResponseの例です。

Resutltタグ内に次のようにstringタグが複数作られます。

<?xml version="1.0" encoding="utf-8"?>
  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
      <SomeMethodResponse xmlns="http://www.mycompany.com/">
        <SomeMethodResult>
          <string>AAA</string>
          <string>BBB</string>
        </SomeMethodResult>
      </SomeMethodResponse>
    </soap:Body>
  </soap:Envelope>

例えば、上記の"AAA"の部分はファイル表示名、"BBB"の部分はPDFデータという組み合わせが可能です。
タグ名はいずれもstringとなるため、各々の要素の意味は順序や内容からプログラムで判断する必要があります。

このstringタグはSOAPレスポンスのタグなのでエスケープされません。またNSXmlParserはstringタグの開始、終了を通知してきますが、"string"自体は文字列としては抽出されません。

2012年3月21日水曜日

formatterの例


//数値のフォーマット
NSNumber *number = [NSNumber numberWithFloat:4321.1234];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setPositiveFormat:@"#,##0.0"];
NSString *numberStr = [formatter stringFromNumber:number];

//uintを先頭ゼロ付きHex文字列にする場合
NSString *hexStr = [NSString stringWithFormat:@"%8X", hexNum];

//多言語対応
//NSNumberFormatterはLocaleに合わせた変換を行う
NSNumberFormatter *formatter = [[NSNumberFormatter allocinit];
[formatter setPositiveFormat:@"0.0"];
 //currentLocale以外の場合に設定
 //[formatter setLocale:anNSLocale ];
NSString *str = [formatter stringFromNumber:anNSNumber]; 

//NSStringを数値に変換する場合に注意
//小数点が","のLocalの場合

[@"1,0" floatValue] => 1.100000
[@"1,1" floatValue] => 1.000000
//NSDecimalNumberを使用すると正しい変換ができる
[NSDecimalNumber decimalNumberWithString:@"1.1"  locale:NSLocale.currentLocale]; => 1

[NSDecimalNumber decimalNumberWithString:@"1,1"  locale:NSLocale.currentLocale]; => 1.1

//日付のフォーマット
NSDate *date = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy年MM月dd日"];
NSString *dateStr = [dateFormatter stringFromDate:_date];

//多言語対応
NSDateFormatter *dateFormatter =
    [NSDateFormatter localizedStringFromDate:date
                                                          dateStyle:NSDateFormatterShortStyle
                                                           timeStyle:NSDateFormatterShortStyle]; 
//3ヶ月前の日付に設定する。
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *componentss =
  [calendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:date];
[componentss setMonth:[comps month] - 3];

2012年3月17日土曜日

文字列⇔数値変換


Cocoaでの数値、文字列相互変換に使うメソッドです。

NSStringのメソッドで文字列を数値に変換する。
– doubleValue
– floatValue
– intValue
– integerValue
– longLongValue
– boolValue

NSNumberのメソッドで数値を文字列に変換する。
数値からNSNumberオブジェクトを作るファクトリメソッド
+ numberWithBool:
+ numberWithChar:
+ numberWithDouble:
+ numberWithFloat:
+ numberWithInt:
+ numberWithInteger:
+ numberWithLong:
+ numberWithLongLong:
+ numberWithShort:
+ numberWithUnsignedChar:
+ numberWithUnsignedInt:
+ numberWithUnsignedInteger:
+ numberWithUnsignedLong:
+ numberWithUnsignedLongLong:
+ numberWithUnsignedShort:

文字列へ変換
– stringValue
– descriptionWithLocale:

数値の型変換
– boolValue
– charValue
– decimalValue
– doubleValue
– floatValue
– intValue
– integerValue
– longLongValue
– longValue
– shortValue
– unsignedCharValue
– unsignedIntegerValue
– unsignedIntValue
– unsignedLongLongValue
– unsignedLongValue
– unsignedShortValue

元データの型
- objCType
(ただしオブジェクトを生成するときに使用したメソッドと一致しない場合がある、となってます。どのような場合に一致しないかはわかりません。)

UITableCellのswipe時のボタン表示


UITableViewのdelegate(通常はUITableViewController)に次のデリゲートメソッドを実装します。

//swipeでボタンを表示させるCellの場合にYESを返す。
- (BOOL)tableView:(UITableView *)tableView
       canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (someCondition == YES) {
        return YES;
    } else {
        return NO;
    }
}

//swipeされたときのボタンの名前
- (NSString *)tableView:(UITableView *)tableVie
       titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return @"ボタン名";
}

//swipeで表示されたボタンタップ時に呼ばれる。
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
     //someAction
}

2012年3月16日金曜日

UITableViewのセクションのフッターを左寄せにする方法


UITableViewのSectionのフッターのアライメントはセンタリングのみで、左寄せにする場合はひと手間必要になります。

フッターにUIView(のサブクラス)をセットすることができるので、UILabelのインスタンスをセットします。

左寄せだけであればUILabelそのものでも実現できますが、左マージンやフォントサイズを変更する場合はサブクラスを作り、drawTextInRect(またはtextRectForBounds?)をオーバライドします。

@interface MyTableFotterLabel : UILabel
- (id)initWithFrame:(CGRect)frame;
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines;
@end

@implementation MyTableFotterLabel

//インスタンスの初期化。全セクション共通の属性を設定する。
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {   
        //左寄せ
        self.textAlignment = UITextAlignmentLeft;
        //背景を透明に設定。
        self.backgroundColor = [UIColor clearColor];
//好みでinsetを設定。
        self.shadowColor = [UIColor whiteColor];
        self.shadowOffset = CGSizeMake(1.0f, 1.0f);
        //フォントサイズ設定。
        self.font = [UIFont fontWithName:self.font.fontName size:14.0f];
    }
    return self;
}

//drawTextInRectをオーバライドし表示位置を設定。左右マージンを設定している。
- (void)drawTextInRect:(CGRect)rect
{
    rect.origin.x += 24.0f;
    rect.size.width -= 48.0f;
    [super drawTextInRect:rect];
}

//Referenceでは呼ばれることになっているが、5.0では呼ばれなかった。
//バージョン違いで呼ばれるかもしれないので実装しておいたほうが良いでしょう。
- (CGRect)textRectForBounds:(CGRect)bounds
        limitedToNumberOfLines:(NSInteger)numberOfLines
{
    bounds.origin.x += 24.0f;
    bounds.size.width -= 48.0f;
    return bounds;
}
@end

UITableViewオブジェクトのdelegate(デフォルトのままであればUITabelViewControllerオブジェクト)のtableView:viewForFooterInSection:をオーバライドし

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
    //サイズをセットしてもUITableViewがセットし直すのでCGRectZeroでよい。
    UILabel * footer = [[IDTableFotterLabel alloc] initWithFrame:CGRectZero];
footer.text = @"セクション別のfooterテキスト";
    return footer;
}

//フッターの高さを返す。
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    //セクションのフォントサイズ、行数に応じて必要な適切な高さを返す。
    return 20.0f;
}

2012年3月14日水曜日

ディレクトリ作成/削除


//NSFileManagerオブジェクトを取得。
NSFileManager *fm = [NSFileManager defaultManager];

//ディレクトリ存在チェック
BOOL isDirectory;
BOOL found = [fm fileExistsAtPath:fileURL.path isDirectory:&isDirectory];
if (found) {
    if (isDirectory) {
        NSLog(@"getDirectory: %@: %@", fileURL, @"同名のディレクトリがあります。");
    } else {
        NSLog(@"getDirectory: %@: %@", fileURL, @"同名のファイルがあります。");
    }
} else {
    //ディレクトリ作成
    NSError *error = nil;
    if (![fm createDirectoryAtURL:fileURL withIntermediateDirectories:<                                attributes:nil error:&e       r]) {             NSLog(@"ディレクトリ作成でエラー発生: %@: %@", fileURL, error.localizedDescription);         }     } } //ディレクトリ/ファイルの削除 NSError *error = nil; if (![fm removeItemAtURL:fileURL error:&error]) {
    NSLog(@"削除でエラー発生: %@: %@", fileURL, error.localizedDescription);

関連Blog
データのディスクへの保存
ディレクトリ作成/削除

画面遷移・表示にかかわるDelegateたち


デリゲートの厄介なのは元クラスのリファレンスにはメソッドの一覧がなく、デリゲートのリファレンスを開かないとわからないことです。加えて画面遷移・表示にかかわるオブジェクトの多くにデリゲートがあるため、行いたいことによって元オブジェクトが変わってきます。そこで、画面遷移・表示にかかわるオブジェクトをまとめてみました。
UIWebView、UITextViewを使う場合はそのデリゲートが加わります。
UIActionSheetを使う場合はそのデリゲートが加わります。

UITextFieldを使う場合は編集、キーボード操作のためにそのデリゲートが加わります。
デリゲートではありませんが、プログラムでインスタンスを作る、あるいはサブクラスを作りカスタマイズすることがあるクラスです。

全部を網羅していませんが、主だったメソッドが呼ばれる順序です。

UIViewController +initialize (クラスイニシャライザー)
UIWebView        -initWithCoder (Stroryboardからインスタンスを作成した場合)
UIViewContoller  -loadView
UIViewContoller  -viewDidLoad
UIWebView        -loadData
UIViewContoller  -viewWillAppear
UIViewContoller  -shouldAutorotateToInterfaceOrientation
UIViewContoller  -viewWillLayoutSubviews
UIViewContoller  -viewDidLayoutSubviews
UIViewContoller  -viewDidAppear
UIWebView        -webViewDidFinishLoad
UIViewContoller  -viewWillLayoutSubviews
UIViewContoller  -viewDidLayoutSubviews

・ DOT.NETのデリゲートとの比較を追加しました。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

API一覧(depricatedされているものは省いてあります)

UINavigationControllerDelegate

    Customizing Behavior
        – navigationController:willShowViewController:animated:
        – navigationController:didShowViewController:animated:

UINavigationBarDelegate
    Pushing Items
        – navigationBar:shouldPushItem:
       – navigationBar:didPushItem:
    Popping Items
       – navigationBar:shouldPopItem:
       – navigationBar:didPopItem:

UIScrollViewDelegate
    Responding to Scrolling and Dragging
       – scrollViewDidScroll:
       – scrollViewWillBeginDragging:
       – scrollViewWillEndDragging:withVelocity:targetContentOffset:
       – scrollViewDidEndDragging:willDecelerate:
       – scrollViewShouldScrollToTop:
       – scrollViewDidScrollToTop:
       – scrollViewWillBeginDecelerating:
       – scrollViewDidEndDecelerating:
    Managing Zooming
       – viewForZoomingInScrollView:
       – scrollViewWillBeginZooming:withView:
       – scrollViewDidEndZooming:withView:atScale:
       – scrollViewDidZoom:
    Responding to Scrolling Animations
       – scrollViewDidEndScrollingAnimation:

UITableViewDelegate
    Configuring Rows for the Table View
       – tableView:heightForRowAtIndexPath:
       – tableView:indentationLevelForRowAtIndexPath:
       – tableView:willDisplayCell:forRowAtIndexPath:
    Managing Accessory Views
       – tableView:accessoryButtonTappedForRowWithIndexPath:
    Managing Selections
        – tableView:willSelectRowAtIndexPath:
       – tableView:didSelectRowAtIndexPath:
       – tableView:willDeselectRowAtIndexPath:
       – tableView:didDeselectRowAtIndexPath:
    Modifying the Header and Footer of Sections
       – tableView:viewForHeaderInSection:
       – tableView:viewForFooterInSection:
       – tableView:heightForHeaderInSection:
       – tableView:heightForFooterInSection:
    Editing Table Rows
       – tableView:willBeginEditingRowAtIndexPath:
       – tableView:didEndEditingRowAtIndexPath:
       – tableView:editingStyleForRowAtIndexPath:
       – tableView:titleForDeleteConfirmationButtonForRowAtIndexPath:
       – tableView:shouldIndentWhileEditingRowAtIndexPath:
    Reordering Table Rows
       – tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:
    Copying and Pasting Row Content
       – tableView:shouldShowMenuForRowAtIndexPath:
       – tableView:canPerformAction:forRowAtIndexPath:withSender:
       – tableView:performAction:forRowAtIndexPath:withSender:

UITabBarDelegate
    Customizing Tab Bars 
        – tabBar:willBeginCustomizingItems:
        – tabBar:didBeginCustomizingItems:
        – tabBar:willEndCustomizingItems:changed:
        – tabBar:didEndCustomizingItems:changed:
        – tabBar:didSelectItem: required method;

UIWebViewDelegate
    Loading Content
        – webView:shouldStartLoadWithRequest:navigationType:
        – webViewDidStartLoad:
        – webViewDidFinishLoad:
        – webView:didFailLoadWithError:

UIActionSheetDelegate
UIActionSheetDelegate
    Responding to Actions

        – actionSheet:clickedButtonAtIndex:
    Customizing Behavior
        – willPresentActionSheet:
        – didPresentActionSheet:
        – actionSheet:willDismissWithButtonIndex:
        – actionSheet:didDismissWithButtonIndex:
    Canceling
        – actionSheetCancel:


UITextFieldDelegate
    Managing Editing
        – textFieldShouldBeginEditing:
        – textFieldDidBeginEditing:
        – textFieldShouldEndEditing:
        – textFieldDidEndEditing:
    Editing the Text Field’s Text
        – textField:shouldChangeCharactersInRange:replacementString:
        – textFieldShouldClear:
        – textFieldShouldReturn:

UINavigationItem
    Initializing an Item
       – initWithTitle:
    Getting and Setting Properties
        title  property
         prompt  property
        backBarButtonItem  property
         hidesBackButton  property
       – setHidesBackButton:animated:
         leftItemsSupplementBackButton  property
    Customizing Views
         titleView  property
        leftBarButtonItems  property
        leftBarButtonItem  property
        rightBarButtonItems  property
        rightBarButtonItem  property
        – setLeftBarButtonItems:animated:
        – setLeftBarButtonItem:animated:
        – setRightBarButtonItems:animated:
        – setRightBarButtonItem:animated:

UIBarButtonItem
    Initializing an Item
       – initWithBarButtonSystemItem:target:action:
       – initWithCustomView:
       – initWithImage:style:target:action:
        – initWithTitle:style:target:action:
       – initWithImage:landscapeImagePhone:style:target:action:
    Getting and Setting Properties
        target  property
        action  property
        style  property
        possibleTitles  property
        width  property
        customView  property
    Customizing Appearance
        tintColor  property
       – backButtonBackgroundImageForState:barMetrics:
       – setBackButtonBackgroundImage:forState:barMetrics:
       – backButtonTitlePositionAdjustmentForBarMetrics:
       – setBackButtonTitlePositionAdjustment:forBarMetrics:
       – backButtonBackgroundVerticalPositionAdjustmentForBarMetrics:
       – setBackButtonBackgroundVerticalPositionAdjustment:forBarMetrics:
       – backgroundVerticalPositionAdjustmentForBarMetrics:
       – setBackgroundVerticalPositionAdjustment:forBarMetrics:
       – backgroundImageForState:barMetrics:
       – setBackgroundImage:forState:barMetrics:
       – titlePositionAdjustmentForBarMetrics:
       – setTitlePositionAdjustment:forBarMetrics:

    Initializing a UITableViewCell Object
       – initWithStyle:reuseIdentifier:
    Reusing Cells
         reuseIdentifier  property
       – prepareForReuse
    Managing Text as Cell Content
        textLabel  property
        detailTextLabel  property
    Managing Images as Cell Content
        imageView  property
    Accessing Views of the Cell Object
         contentView  property
        backgroundView  property
        selectedBackgroundView  property
        multipleSelectionBackgroundView  property
    Managing Accessory Views
        accessoryType  property
        accessoryView  property
        editingAccessoryType  property
        editingAccessoryView  property
  Managing Cell Selection and Highlighting
        selected  property
        selectionStyle  property
       – setSelected:animated:
        highlighted  property
       – setHighlighted:animated:
    Editing the Cell
          editing  property
        – setEditing:animated:
        editingStyle  property
        showingDeleteConfirmation  property
        showsReorderControl  property
    Adjusting to State Transitions
        – willTransitionToState:
        – didTransitionToState:
    Managing Content Indentation
        indentationLevel  property
         indentationWidth  property
        shouldIndentWhileEditing  property


UITextViewDelegate 
    Responding to Editing Notifications
        – textViewShouldBeginEditing:
        – textViewDidBeginEditing:
        – textViewShouldEndEditing:
        – textViewDidEndEditing:
    Responding to Text Change
        – textView:shouldChangeTextInRange:replacementText:
        – textViewDidChange:
    Responding to Selection Change
        – textViewDidChangeSelection:

UIWebViewでPDF指定ページへスクロール


  //NSURLからPDFDocumentを作成
  NSURL *pdfURL;

  //ARC環境ではキャストに"__bridge"を付加する。
  CGPDFDocument pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfURL);

  //1ページ目のPageオブジェクトの取得
  CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);

  //ページサイズ取得
  CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
  float pageHeight = pageRect.size.height;
  float pageWidth = pageRect.size.width;

  //PDFの横幅をディスプレイの横幅に納める倍率
  float fitScale = self.frame.size.width / pageRect.size.width;

  //ページ数の取得
  int numberOfPages= CGPDFDocumentGetNumberOfPages(pdf);

  //UIWebViewをPDFの指定ページへスクロール
  UIWebView *webView = someUIWebView;
  UIScrollView *scrollView = webView.scrollView;

  float x = scrollView.contentOffset.x;
  float y = scrollView.contentSize.height/numberOfPages*pageNumber;
  CGPoint point = CGPointMake(x, y);
  [scrollView setContentOffset:point animated:YES];

参照:ZoomingPDFViewer

基本的には上記の方法でスクロールできるのですが、UIWebViewのPDFロードが非同期で行われるため、ロード直後(UIWebViewのデリゲートメソッドのwebViewDidFinishLoad:)ではスクロールビューのcontentSizeがまだ初期値のままの場合があります。

もし表示直後に特定ページにスクロールしたいような場合は注意が必要です。
参照:UIWebViewの初期ズーム倍率設定