2011年12月27日火曜日

NSArray/NSDictionaryをファイルに書き出す方法

NSArray(やNSDictionary)をファイルに書き出す場合、通常は
[array writeTofile:@"パス名"];
一発でいける。

ところが、arrayの要素がNSクラス(から継承されたクラス)でない場合、
これでは書き出せない。
正確には、NSCodingプロトコルに準拠しているクラスでないと書き出せない。
(書き出し時にエラーが発生する。)

たとえば、Cの構造体を要素にしている場合とかがこれに相当する。
NSValueが含まれる場合も、実体が任意のアドレス内容であるため同じ。

そういう場合は、要素毎NSDataに変換して出力する。
こんな感じ。
[[NSFileManager defaultManager]createFileAtPath:path contents:nil attributes:nil];
id fp=[NSFileHandle fileHandleForWritingAtPath:path];
typedef struct {
   // 構造体定義
} ST;
ST st1;
NSValue *val;
NSEnumerator *emu=[lineArray objectEnumerator];
while (val=[emu nextObject]) {
    // 1行ごと構造体に読みなおして書きだす
    [val getValue:&st1];
    NSData *data = [NSData dataWithBytes:&st1length:sizeof(ST)];
    [fp writeData:data];
}
[fp closeFile];
NSValueの場合はNSString stringWithFormat:で文字列化して書き出すという手もある。

ついでにいえば、NSArrayをNSDataに変換する方法、
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
[fp writeData:data];
を使ってもでもだめ。これもNSCodingがないから。

ちょっとしたテクニックかも。








2011年12月25日日曜日

UIScrollView座標について

UIScrollViewとその配下に置くViewの座標の件。

画面回転が有効で、Autosizingの指定が双方で異なると座標がずれてしまう。
特にiPadでは要注意。

Autosizingの設定を同じにしていると座標も同じになる。

2011年12月24日土曜日

iOSで使えるフォント

iOSは5になってフォント数が増えた。
日本語で言えば、「ヒラギノ明朝体」が使えるようになったのは大きい。

iOS5でのみ使えるフォントをUILabel等表示で使っているとき、これをiOS4にもって行っても代替フォントで表示されるので、若干意図する表示とは異なるだろうが、大きな問題にはならない。

しかし、プログラム的にフォントを選択するような場合には、その追加されたフォントは選択出来ないようしなければならない。これは、フォントは番号ではなく名称で管理する必要があることを意味する。

フォントにはfamilyNameとfontNameの2つがある。大分類と詳細名と考えれば良い。

使える全フォントを全部表示するプログラムは以下の通り。

NSArray *namesArray=[NSArray arrayWithArray:[UIFont familyNames]];
NSEnumerator *emu=[namesArray objectEnumerator];
NSString *name;
while (name=[emu nextObject]) {
    NSLog(@"Font familyNames=%@",name);
    NSArray *namesArray2=[NSArray arrayWithArray:[UIFont fontNamesForFamilyName:name]];
    NSEnumerator *emu2=[namesArray2 objectEnumerator];
    NSString *name2;
    while (name2=[emu2 nextObject]) {
        NSLog(@"+-Font fontNames=%@",name2);
    }
}
ついでにそのフォントで例文でも表示させれば良いかも。

・・・追記

逆に、iOS5使えなくなったフォントもある。
気がついた限りでは「Heiti_K」とか。代替フォントはあるけど。

・・・2012/11/30追記
iOS6でもフォントが幾つか追加されている。
でもiOS5から1つフォントが削除された。いわゆる7セグLEDフォント。
電卓や時計に使っている人いるんじゃないかなぁ。
どのフォントが追加・削除され、結局どういうフォント体系になったかは拙作「X−BASIC for iOS」の説明書の中に書いたので、よろしければご購入をm(_ _)m。





2011年12月23日金曜日

iOSシミュレーターでのピンチ/ダブルスライド動作の仕方

iOSシミュレータ上で、ピンチ(ズーム)やダブルスライドの動作がメニュー上には存在しないし、マウスでどうするのかがわかりにくい。

やり方は以下の通り。

・マウスカーソルがシミュレーター上にあるとき、Optionキーを押すと○が2つ出てくるが、
これを離したり近づけたりするとピンチ動作になる。

・その状態でShiftを押すと○の位置が固定されてダブルスライド出来る
 基本的には、○を近づけた状態で+Shiftを押して、その状態でマウスを動かす

私もこれを知るまではズームとかダブルスライドだけは実機で確認してた。

後解ってないのはトリプル以上のタッチやスライド。

ひょっとしてマウスを2本以上つながないとだめなんだろうか。
MagicTouchPadでもだめなのは確認済み。

(2012/01/20;ダブルスライド追記)

2011年12月12日月曜日

「安心メール v2.00」「手書きメール v1.0」発売開始

弊社 iOS用第5弾アプリ、「安心メール」がV2.0になり、 写真撮影機能が追加されました。 ボタン1つ押すだけ、非常に簡単にメールを送信できます。
http://itunes.apple.com/app/reliable-mail/id474716740?mt=8&ls=1


また、第6弾アプリ「手書きメール」が発売になりました。 手書きメールは、手書きの内容をそのままメールで送ることが出来ます。 写真との合成も可能です。
http://itunes.apple.com/app/handwrite-mail/id486255587?mt=8&ls=1

「安心メール」はとにかく簡単に連絡を付けたい人向け、「手書きメール」は、少数の相手に簡単にメールを出したい人向けです。 どちらも、iPhone/iPadの特性を活かし、キー操作がほとんど不要ですので、電子機器に不慣れな方にでも使ってもらえます。

よろしくお願いします。

2012/02/05追記
「手書きメール」がV1.50になりました。
ダブルスライドによるページ送りと、疑似筆圧処理が追加されました。
細かいバグも修正されています。

ここの維持のためにもお買い上げいただくとありがたいかとm(_ _)m

2011年12月11日日曜日

UIImagePickerControllerの使い方(3)

撮影処理中は、画面の回転が検知されない。実際には
- (void)willAnimateRotationToInterfaceOrientation:が呼び出されない。
.view.frameも変化しない。

カメラ自体を横向きに出来ても、画素そのものは回転しないからであろう。
写真アプリで画面を横にしてもボタンの(方向は変わるが)位置は変わらないのはそのためである。

しかし、強制的に回転処理を作り出すことは出来る。
回転に対する通知を登録し、それを受信してビューに細工すればいい。

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(orientationChanged:)
                                                     name:UIDeviceOrientationDidChangeNotification
                                                   object:nil];
        - (void)orientationChanged:(NSNotification *)notification

ただ、回転処理をOSに代わって行う必要があるため、かなり面倒である。
ラベルは、文字列には縦書きがないため基本的に使えないし、
アイコンを使うなら回転したアイコンを用意する必要がある。
実はこれをうまく処理する方法もある。Viewを回転させれば良いのである。

UIKitはよく出来ていてViewを回転させると、その上にある各要素の座標も適宜変換してくれる。だから、ボタンなども、回転後の位置でちゃんと効く。

具体的な方法は省略。
少しは自分で調べよう、と言うことで。







2011年12月9日金曜日

UIImagePickerControllerの使い方(2)

iOS標準のカメラ画面ではなく独自に画面を作成する場合、
        picker.showsCameraControls  = NO; // デフォルトのカメラコントロールを非表示にする

を入れ、独自にビューを作成する。
そのビューはpicker.cameraOverlayViewに設定するか、もしくは
            [window addSubview:ビュー];
で表示する。

オーバーレイの場合、viewDidLoadなどが呼び出されないので、nibが非常に使いにくい。
特に、pickerは起動時間にばらつきがあり、この時間をプログラム的に判定するには
viewWillApearを使う必要があるため、オーバーレイは使えない。
従って、オーバーレイをnibで作成する場合はaddSubviewで表示した方が良い。
というか、オーバーレイを使う意味はほとんどないと思うのだが。

カメラ画面を独自に作ってシャッター([picker takePicture])をユーザーが処理する場合、
その押下タイミングには注意が必要である。
まだ前回の撮影に伴う画像処理が終わっていないのに再度シャッターを切った場合、
内部的にエラーが発生し、撮像されない(シャッターが無視された形になる)。
このエラーはコンソールにログを出していると見えるのだが、プログラム的には検出できない。

これを避けるには、フラグを作って、imagePickerController:が呼び出されるまでは
次のシャッターを切らないようにする必要がある。

2011年12月7日水曜日

UIImagePickerControllerの使い方(1)

UIImagePickerControllerはiOS上で画像の撮影・選択処理を一括して行えるクラスである。
この「撮影」と「選択」処理を1つのクラスで扱えるとはいえ、
この2つは無理矢理統合されている感が強く、基本的にはどちらで使うかによって
呼び出し方が異なる。

撮影処理は、クラスインスタンスを確保し、モードを設定して表示するだけで良い。
シャッターや前後カメラ切り替えなど、必要なボタンなどもすべて用意してくれる。

    UIImagePickerController *picker= [[UIImagePickerController alloc]init];
    picker.sourceType = UIImagePickerControllerSourceTypeCamera; // カメラを使う

    // 以下↓の設定は、↑この設定の後でなければならない
    picker.delegate             = self;
    picker.cameraCaptureMode    = UIImagePickerControllerCameraCaptureModePhoto; // 静止画
    [self presentModalViewController:picker animated:YES];
UIImagePickerControllerはUINavigationControllerの子クラスでありそれを含んでいるが、
外部のUINavigationControllerとは連結できない。
従って、上位のビューがUINavigationControllerの管理下にあったとしても、
[self.navigationController pushViewController:ビューコントローラー animated:YES];
で表示するのではなく、上記のようにpresentModalViewControllerを使う。

pickerの表示は、初回のみ時間がかかる。
シャッターを押すと撮影されるが、実際の画像ができあがるまでには時間がかかるため、
できあがった時点でデリゲートが呼び出される。
-(void)imagePickerController:(UIImagePickerController *)picker_ didFinishPickingMediaWithInfo:(NSDictionary *)info
// 撮像処理(delegate)
// 写真が取得可能になったら並行動作で呼び出され、この中を走っている間、メインも走り続ける
{
    // 画像はinfoから取り出す。
    UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage];
}

(次回に続く)







2011年12月5日月曜日

Webページの表示仕方

WebページはUIWebViewで簡単に表示できるが、その中に画像の貼り付けなどがある場合、
その参照を解決する指定が必要になる。

クラスはこんな感じ。外部のURLへのリンクを貼る場合は、その確認のためUIAlertViewを表示する必要があるため、UIAlertViewDelegateも必要となる。

@interface HTMLViewController : UIViewController
<
UIWebViewDelegate,UIAlertViewDelegate
>
{
    IBOutlet    UIWebView *webView;
    NSURL       *url;
    UIAlertView *aview;
    BOOL        fopenAlert;
}
@property (nonatomic, retain) UIAlertView *aview;
ここではwebViewを含むnibを別途読み込んでいる。
(だからIBOutletがある。)

nib上でwebViewに以下の属性をチェックしておく。
Scaling Scales Page Fit
Detection Links
HTMLの読み込みはviewDidLoadで行う。

NSString *path=[[NSBundle mainBundle]pathForResource:@"HTMLファイル名" ofType:@"html"]; // リソース内のパスを得る
    NSString *html=[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
    NSURL *base=[NSURL fileURLWithPath:path]; // HTML内の画像のリンクを解決するため、これが非常に重要

表示
[webView loadHTMLString:html baseURL:base];
読み込んでからの設定が必要
webView.delegate=self;
    aview.delegate  =self; // 念のため
    url=nil;

    aview=[[UIAlertView alloc]initWithTitle:@"リンク"
                                    message:@"サポートページを開きますか?"
                                   delegate:self
                          cancelButtonTitle:@"いいえ"
                          otherButtonTitles:@"はい",nil];
    //
    fopenAlert=NO; // アラートを表示していないフラグ
    ~

HTML内のリンク(a href=)はそのままではwebView内で開いてしまうので、
内部のリンクはそのまま、外部へのリンクはSafariブラウザが開くように処理を差し替える。
そのため、webViewからのデリゲートを使う。

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    switch (navigationType) {
        case UIWebViewNavigationTypeLinkClicked: // リンクがクリックされたとき
            { // switch内でローカル変数を作るときはスコープ宣言が必須
            NSURL *url0=[request URL];
            if ([[url0 scheme]hasPrefix:@"file"]) {
                break; // file:の時は内部なので、何もしないで開けば良い
            }
           
            // 外部へのリンクの時は、飛ぶ前にUIAlertViewで確認する
            // 確認なしで飛ぶアプリも多いが、アップルの仕様書中では「確認せよ」と書いてある。
 
            url = [url0 copy]; // copyしないと消えてしまうので要注意
            } // ローカルスコープ
            [aview show]; // 表示したらすぐ終わる
            fopenAlert=YES; // アラートを表示しているフラグ
            return(FALSE);
        defalt:
            break;
    }
    return(TRUE);
}
urlをcopy属性で保存しているのは、アラートビューは独立して実行されるからである。

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
// アラートビューでボタンがクリックされた時に呼び出されるデリゲート
{
    NSLog(@"button=%d",buttonIndex);
    if (buttonIndex!=alertView.cancelButtonIndex) { // 「はい」のとき
        NSLog(@"直前url=%@",url);
        // リンクへ飛ぶ
        [[UIApplication sharedApplication] openURL:url]; // httpがあるので自動的にブラウザが立ち上がる
    }
}

- (void)dealloc
{
    webView.delegate=nil; // なんか必要なのだそうな
    if (url!=nil) [url release]; // copy属性だから
    [aview release];
    [webView release];
    [super dealloc];
}
画面の回転を有効にしている場合、回転ごとに内容の再読込をしないと画面幅が調整されない。

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
// 回転検出
{
    [webView reload]; // こうしないと画面幅が調整されないので
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // 回転有効
    return YES;
}
「重要」なこと。
UIWebViewで扱う文字エンコードはUTF-8にしないといけない。
HTML内のエンコード指定

とファイル自身の文字エンコードをUTF-8にするのを忘れないように。
(秀丸エディタだとファイル書き出し時に文字コードが指定できて楽。)









2011年12月3日土曜日

設定値の保存/読み出し方

設定値の保存にはNSUserDefaultsを使う。

設定値は辞書形式で記録されるので、その名称となるキー値を決めておく必要がある。
NSString *文字列である。
これらは通常.hに書いておく。

#define keyCameraDevice     @"cameraDevice"
#define keyFalarmRetrigger  @"falarmRetrigger"
#define keyExitDelay        @"ExitDelay"
#define keyLatestDate       @"LatestDate"

書き出し側
    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; // これは定型書式

    // 記録するオブジェクトの型によってメッセージが変わる
    [defaults setInteger:cameraDevice       forKey:keyCameraDevice ];
    [defaults setBool:falarmRetrigger       forKey:keyFalarmRetrigger];
    [defaults setFloat:tmExitDelay          forKey:keyExitDelay];
    [defaults setObject:latestDate          forKey:keyLatestDate];

    // 書き込み終わったら同期をかける。iOS4以降では必須
    [defaults synchronize];
読み出し側
    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
    // インストール直後かどうかを判定
    if ([defaults boolForKey:keyInitialSetup]==NO) {
        [self initSetup];
    } else {
        cameraDevice        =[defaults integerForKey:keyCameraDevice];
        falarmRetrigger     =[defaults boolForKey:keyFalarmRetrigger];
        tmExitDelay         =[defaults floatForKey:keyExitDelay];
        latestDate          =[defaults objectForKey:keyLatestDate];
    }

一番最初の初期化
-(id)initSetup
    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
    [defaults setBool:YES forKey:keyInitialSetup];  // 初期設定をした印
    // 以下初期値で書き出す処理を書く

というような感じ。







2011年12月1日木曜日

viewDidLoad/viewWillApear/viewDidApear等のタイミング

viewの読み込み・表示の各タイミングでデリゲートが呼び出されるが、
それぞれには重要な意味がある。

特に、nib内容に対する設定、読み出しはviewDidLoad以降でないとすべて無効になる。
中で読み込んでいるUI要素へのデリゲート設定もそう。
これ以前にやってもエラーは出ないのに誤動作する(というか動作しない)のでわかりにくい。

    class=[[ClassName alloc]initWithNibName:@"ClassNibName" bundle:[NSBundle mainBundle]];
    // この先並行動作する
    // nib読み取り終了はviewDidLoadで判定(かなり先になることもある)

    //view表示命令
    [window addSubview:navigationController.view];
とか
[mainView.navigationController popToViewController:self animated:YES];
と記述した場合、

viewDidLoad
Nibの読み込みが実際に終わったときに呼び出される
メモリーオーバーによってViewDidUnloadが呼び出されない限り、初回のみ。

viewDidUnload
メモリーオーバーが発生したときに呼び出される。
不要なワークがあれば、ここで解放する。
次回Viewを表示する際には先にviewDidLoadが呼び出される

viewWillApear
表示直前に呼び出される
要素表示のon/off(.hidden=YES/NO)やラベルの表示内容の設定などはここまでで行う

UIPickerViewのカーソル位置設定はここでしないとうまくいかない
[pickerView_ selectRow:selectRow inComponent:0 animated:NO];

viewDidApear
表示直後に呼び出される
この後Viewへ制御が移る

viewWillDisapear
ビューが消える直前に呼び出される

viewDidDispear
ビューが消えた直後に呼び出される


なお、UIImagePickerControllerのオーバーレイではこれらの処理は通らない。
従って、オーバーレイはnibで作ると制御が難しいため、
オーバーレイではなく、普通のUIViewControllerで作って重ね合わせる方が得策である。


・・・2012/11/30追記
iOS6では細かい呼び出しタイミングが変更になっている。
各処理の終了を待たずに次の処理が走ることがあるのだ。
これに依存している場合は変更が必要になるので注意。