Powered By Blogger

2011年6月10日金曜日

iOS:キーボードをカスタマイズ

元ネタ

カスタムキーボード画面1
顔ボタンを押すとキーボードが変化します。

カスタムキーボード画面2
変化後のカスタムキーボード。ABCボタンで元に戻ります。


このUITextFieldから派生したクラスを画面(*.xib)のカスタムキーボードで入力したいテキストフィールドに設定します。
//
//--------------------------------------------------
//  CustomTextField.h
//--------------------------------------------------

#import 
#import "CustomKeybord.h"


@interface CustomTextField : UITextField {
  @private
  UIView *inputAccessoryView_;
  BOOL useCustomKeybord_;
  NSString *customBtnTxt_;
}

@property (nonatomic, retain) IBOutlet CustomKeybord *keybord;
@property (nonatomic, retain) UIButton *compButton;

- (IBAction)faceBtn1:(id)sender;
- (IBAction)faceBtn2:(id)sender;
- (IBAction)faceBtn3:(id)sender;

@end


//---------------------------------------------------------
//  CustomTextField.m
//---------------------------------------------------------

#import "CustomTextField.h"


@implementation CustomTextField

@synthesize keybord=keybord_;
@synthesize compButton;

- (void)changeKeybord:(id)sender {
  self->useCustomKeybord_ = !self->useCustomKeybord_;
  self->customBtnTxt_ = (self->useCustomKeybord_) ? @"ABC" : @"(^-^)";
  [self reloadInputViews];
}

- (UIView *)inputAccessoryView {
  if (self->inputAccessoryView_ == nil) {
    CGFloat width = [[UIScreen mainScreen] applicationFrame].size.width;
    CGRect accessFrame = CGRectMake(0.0, 0.0, width, 50.0);
    self->inputAccessoryView_ = [[UIView alloc] initWithFrame:accessFrame];
    self->inputAccessoryView_.backgroundColor = [UIColor blueColor];
    self.compButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.compButton.frame = CGRectMake(20.0, 5.0, 80.0, 40.0);
    [self.compButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.compButton addTarget:self
                   action:@selector(changeKeybord:)
         forControlEvents:UIControlEventTouchUpInside];
    [self->inputAccessoryView_ addSubview:compButton];
    self->customBtnTxt_ = @"(^-^)";
  }
  [self.compButton setTitle: self->customBtnTxt_ forState:UIControlStateNormal];
  return self->inputAccessoryView_;
}

- (UIView *)inputView {
  if (self->useCustomKeybord_) {
    if (self->keybord_ == nil) {
      UINib *customKeybord = [UINib nibWithNibName:@"CustomKeybord" bundle:nil];
      [customKeybord instantiateWithOwner:self options:nil];
    }
    return self->keybord_;
  } else {
    return [super inputView];
  }
}

- (IBAction)faceBtn1:(id)sender {
  self.text = [NSString stringWithFormat:@"%@(^-^)", self.text];
}

- (IBAction)faceBtn2:(id)sender {
  self.text = [NSString stringWithFormat:@"%@(^^;)", self.text];
}

- (IBAction)faceBtn3:(id)sender {
  self.text = [NSString stringWithFormat:@"%@(-.-;)", self.text];
}

@end


カスタムキーボードとしてこのようなNibを作ります。


上のNibに対応するUIViewの派生クラスを作ります。

//
//------------------------
//  CustomKeybord.h
//------------------------

#import 


@interface CustomKeybord : UIView {
}

@end


//-------------------------------
//  CustomKeybord.m
//-------------------------------

#import "CustomKeybord.h"


@implementation CustomKeybord

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)dealloc
{
    [super dealloc];
}

@end

2011年6月5日日曜日

ブログに数式を貼る


  

このhttp://formula.s21g.com/の後をLaTeXフォーマットで記述すれば下の様になる。



詳しくはここを参照。

2011年5月17日火曜日

MacとWinでキーボード/マウス共用

MacBookとWinPc同時に使いたいけど机の上が狭くて2セットのキーボード/マウスを置けない。
MacBookのキーボード/マウスでWinPCも同時に使いたい、という人にはこのソフト。
Synergy
マウスカーソルを画面の端に移動させるだけでMac→Win,Win→Macと操作が切り替わり、あたかもマルチスクリーンモードの如く一組のキーボード/マウスで2台以上のPCを操作出来ます。
構成は他のパターンでもOKでWinPCのキーボード/マウスでMacを操作したり、もちろんMac同士、Win同士も出来るらしいです。
私はMac→Winの構成しか試してないので、他の構成については他の方のページを参考下さい。

MacとWindowsPCの両方にSynergyをインストールする必要があります。

Macへのインストール

macportを使ってインストールします。
*以下はmacportはインストール済みという前提で進めます。macportのインストール方法は検索すればすぐに分かりますので省きます、悪しからず。
ターミナルで
sudo port install synergy
とすればMacへのインストール完了、これでsynergyをコマンドライン操作で使える様になります。
と…言いたい所ですがGUI慣れした人にはコマンドをぽちぽち入力するのはカッタるいので、
SynergyKMもインストールしましょう。
このインストールでシステム環境設定の中にSynergyKMアイコンが追加されてGUIで操作出来る様になります。

Windows PCへのインストール
ここから

2011年4月30日土曜日

MacにThriftをインストール

Thriftをダウンロード

2011年現在、Thriftはmacportには無いらしい。
なので、
Apache Thriftからダウンロードする。

インストール

ターミナルでダウンロードしたディレクトリに移動する。
インストール先を指定してconfigureを実行する。私の場合、/optの下にしたので、
./configure --prefix=/opt/thrift/
make
とターミナルに打ち込むと普通は問題無いがエラーになった。
ググるとOSX Snow leopard 64 bitな人はそのままではダメらしい。
この記事にしたがって、
export ARCHFLAGS="-arch x86_64"
と打ち込み、もう一度makeしてみる。
make
よし!エラーが無くなった、次に進もう。
sudo make install
OK、これで無事にインストール完了。

2011年4月26日火曜日

iOS:年月日時分を一つのUIDatePickerで入力する

UIDatePickerはプロパティdatePickerModeを変更することで、年月日モードと月日時分(または時分)モードに変更出来る。


それでは年月日時分を一つのUIDatePickerで入力したい場合はどうするのだろうか。
Pickerを二つ用意してそれぞれ年月日用と時分用に分けるのは常套手段かもしれないが、画面を二つに分ける事になり少し面倒臭い。


ユーザーのボタン選択によりdatePickerModeを動的に変えれば良いのではないか。
ボタン押下で下のswitchDateTimeを呼ぶようにする。
//

- (IBAction) switchDateTime {
  switch (datePicker_.datePickerMode) {
    case UIDatePickerModeDate: {
      datePicker_.datePickerMode = UIDatePickerModeDateAndTime;
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_date", @"") forState:UIControlStateNormal];
    } break;
    case UIDatePickerModeDateAndTime: {
      datePicker_.datePickerMode = UIDatePickerModeDate;
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_time", @"") forState:UIControlStateNormal];
    } break;
    default:
      assert(0);
      break;
  }
}
うまくいった!!ボタンを押す度にPickerの形が変わる。
と、思いきや駄目だ、モードを切り替えると時分が0時0分に戻ってしまう。
年月日は入力した値が保たれているが、時分だけは幾ら入力してもモードを切り替えると0:00になってしまう。
この様な使い方は想定されていないのか、UIDatePickerは UIDatePickerModeDateでは時分の値を保持しないらしい。


ならばUIDatePickerModeDateに変える直前で日付データを一時退避し後で書き戻す事にする。

//

- (IBAction) switchDateTime {
  switch (datePicker_.datePickerMode) {
    case UIDatePickerModeDate: {
      datePicker_.datePickerMode = UIDatePickerModeDateAndTime;
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_date", @"") forState:UIControlStateNormal];
    } break;
    case UIDatePickerModeDateAndTime: {
      NSDate *tmp = [datePicker_.date copy]; //<-ここで一時退避
      datePicker_.datePickerMode = UIDatePickerModeDate;
      datePicker_.date = tmp;                          //<-ここで書き戻す
      [tmp release];
      [swBtn_ setTitle:NSLocalizedString(@"swichBtn_time", @"") forState:UIControlStateNormal];
    } break;
    default:
      assert(0);
      break;
  }
}
モードを切り替えても時分は保持されている。
取りあえず問題は解決した様だ。

2011年4月5日火曜日

iOSでBlocks(クロージャ)2

●Objective-CのクラスのインスタンスをBlockから使用する場合に注意する事
思いがけずオブジェクトが自己参照してしまい、そのオブジェクトが解放されなくなるケース。

以下の様なインスタンス変数を持つクラスがあるとする。

//

//--- TestClass.h ---
typedef void (^block_t)();

@interface TestClass : NSObject {
@private
NSNumber* num_;
block_t block_;
}

- (id)initWithNumber:(NSNumber*)num;
- (void)doBlock;

@end

//--- TestClass.m ---
@implementation TestClass

- (id)initWithNumber:(NSNumber*)num {
  self = [super init];
  if (self) {
    num_ = num;
  }
  return self;
}

- (void)dealloc {
  num_ = nil;
  Block_release(block_);
  [super dealloc];
}

- (void)doBlock {
  if (block_) return;

  block_ = Block_copy(^{NSLog(@"memberObj : %@", num_);});
 //ここでselfのretain countが1つ増加、自己参照!!
 //num_のretain countは変化なし

  block_();
}

@end
一見問題の無い様に見えるが、実際は自分自身(self)をretainしている(循環参照している)ので、このクラスのオブジェクトを最後に破棄しようとreleaseしてもdeallocが呼ばれない(リーク!!)

//

TestClass* testObj = [[TestClass alloc] initWithNumber:[NSNumber numberWithInt:10]];
  [testObj doBlock];
  [testObj release]; //ここで破棄、しかしdeallocが呼ばれない
●何故循環参照してしまったのか
ブロック内でnum_を参照しているのでnum_の参照カウンタが増加すると思うだろうがそうではない。
オブジェクトのメソッド内でそのオブジェクトのインスタンス変数を参照するときは、明示的に書かなくてもself->変数、と解釈され、ブロックはselfの参照カウンタを増加させてしまう。

●解決方法
オブジェクトのメソッド内のブロック内でselfを参照しない。
上のケースの様にselfを明示してなくても実はselfを参照するケースに注意する。
上のケースの場合

//

# if 0
  block_ = Block_copy(^{NSLog(@"memberObj : %@", num_);}); //ここを
# else
  NSNumber* tmp = num_;
  block_ = Block_copy(^{NSLog(@"memberObj : %@", tmp);});   //こう書き換える
# endif
一旦ローカル変数へコピーする事でブロック内でself->と参照される事を防ぐ。
これでブロックはselfを参照せずにnum_だけを参照する様になる。

●雑感
クラスのメソッド内でBlocksを使うときは毎回こういう冗長なことをしないといけないのだろうか。
一見意図の見えづらいコードだし:(

2011年4月4日月曜日

iOSでBlocks(クロージャ)1

iOS 4プログラミングブックはBlocksについて分かりやすく書いてある。

●Blocksとは
いわゆるクロージャ。
関数内で宣言できるいわゆる無名関数だが、ただの無名関数ではない。
引数以外に外側のスコープのローカル変数を参照出来る。

前に^が付いているのがBlock構文である。
戻り値がvoidなので省略が可能で
^(int arg) { /* Block処理 */ };
としたが、省略しない場合は
void ^(int arg) { /* Block処理 */ };
となる。
ちなみに引数もvoidなら更に省略できて
^{ /* Block処理 */ };
と記述出来る。

下のコードでは変数fnにBlockを代入し、aとbをインクリメントした後でfnを関数ポインタの様に呼び出している。

//
int a = 1, b = 1;
void (^fn)(int) = ^(int arg) {printf("a=%d, b=%d\n", arg, b);};//(1)
++a; ++b;
fn(a);  //a=2, b=1 と出力される
//
ローカル変数aは引数として渡される直前でインクリメントされているのでa=2と表示される。
一方bはb=1と表示される。
aと同じ行でインクリメントされているのでfn(a)呼び出し時点でbも値は2である。

では何故b=1表示されるのだろうか。
これがクロージャたる所以で(1)でfnにBlockが代入された時点で変数bをキャプチャ(コピー)されるからである。

●キャプチャされたローカル変数はどこに記憶されるのか?
ローカル変数を使っていない場合:.dataセクション
ローカル変数を使っている場合:スタック
上の例はローカル変数をキャプチャするので、この場合ブロックはスタック上に配置される。
このスタック上に配置されるブロックがうっかりすると厄介な間違いの元になる。
★間違った使い方の例

//
typedef void (^block_t)();
  block_t blocks[5];
  int i;
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i) //Loop1
    blocks[i] = ^{printf("%d\n", i);};
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i) //Loop2
    blocks[i]();

実行結果
4
4
4
4
4

意図した実行結果は以下だったのだが...
0
1
2
3
4

何が間違っているのか?

Loop1のブロック^{printf("%d\n", i);};はループ毎に一時的にスタック上に配置され、そのスタック位置のアドレスは各ループiのblocks[i]に保存される。 ループが進むたびに破棄されスタック位置は元に戻る。 つまりループ毎にブロックはスコープから外れるので、このケースではループの全ての回で同じスタック位置に一時ブロックが上書きされる事になる。 blocks[0]からblocks[4]には結果的に同じアドレスを指しているので実行結果は全て4になるのだが、Loop1を抜けた時にはこのアドレスはダングリングなのでLoop2を実行する事自体が間違っている。

●この問題の回避方法
Block_copyを使ってスタックに一時的に生成されたブロックをヒープにコピーする。

//
typedef void (^block_t)();
  block_t blocks[5];
  int i;
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i)
    blocks[i] = Block_copy(^{printf("%d\n", i);});//Blockをヒープにコピー
  for(i=0; i < sizeof(blocks)/sizeof(blocks[0]); ++i){
    blocks[i]();
    Block_release(blocks[i]);//ヒープ上のものは使い終わったらreleaseする
  }

実行結果
0
1
2
3
4