タグ別アーカイブ: objective-c

objective-c関連

UITabBarControllerのTabItem画像が表示されない!

UITabBarControllerを利用して画像が表示されなくてこまりました。


Tabbar Doesn't appear.

原因は画像データには alpha channel を含んでないとだめだそうで、よくよく調べるとalphaをグレースケール表示しているようでした。

例のごとく、
stackoverflow:Tab bar image icon doesn’t appear

Photoshopでalphaチャンネル付き画像データを作る

※Photosho CS6(Mac)
せっかくなのでPhotoshopを使って alpha 含むTabBarItem用のpng作成手順を残します。
alphaChannel000
最終的に Script > Export Layers to Files…を行うので、画像のようにレイヤー単位で画像を保持させておきます。
一番上のレイヤーのブラジル国旗をベースに作成をします。
alphaChannel001
Layer > Layer Mask > Revel all で真っ白なレイヤーマスクを追加。

alphaChannel002
Window > Channel で Channel を表示。Layer Maskを追加しているので(画像では黒だけど)白いチャンネルが表示され、非表示になってる。
Greenのチャンネルをコピーし、全体を非表示にしてから、Alphaだけを可視化(目のマーク)にする。
alphaChannel003
可視化したらGreenのチャンネルをペーストする。

alphaChannel004
ペーストしたら、可視状態をalphaを不可視化してRGBを可視化する。

alphaChannel005
完成はご覧の通り。

Screen Shot 0024-12-04 at 20.16.57
やったね!

ほめられデザイン事典 グラフィック・ワークス Photoshop & Illustrator

iAdを固定位置にするサンプル

iAdの位置固定方法を調べて最適解を得たのでメモ。

やっていること

iAdを表示するviewを作成。
iAdのviewを固定位置にするためだけのUIViewを生成。
UIViewはスクロールの度に走る次のイベントで位置を設定しなおす。

- (void)scrollViewDidScroll:(UIScrollView *)

サンプル

- (void)viewDidAppear:(BOOL)animated{
   
    [super viewDidAppear:animated];
    //for iAd initialization
    //tmp view

    // iAdViewContainerはUIViewクラスのメンバ。
    if(iAdViewContainer_ == nil){
        iAdViewContainer_ = [[UIView alloc]initWithFrame:self.view.frame];
        [self.view addSubview:iAdViewContainer_];
       
        //adView_ はADBannerViewクラスのメンバ
        adView_ = [[ADBannerView alloc] initWithFrame:CGRectMake(0.0, 367.0, 320.0, 50.0)];
        adView_.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
        [iAdViewContainer_ addSubview:adView];
    }   
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [self resetIAdPosition];
}
- (void)resetIAdPosition
{
    [iAdViewContainer_ setFrame:CGRectMake(
                                           self.tableView.contentOffset.x,
                                           self.tableView.contentOffset.y,
                                           0,0)];
    [self.view bringSubviewToFront:iAdViewContainer_];
}

解説

画面下部に固定させたいので、iAd自身とそれを固定位置で保持するviewの2つを生成する。
片方は色々なところで参照するのでメンバ変数として定義しておく。
サンプル上ではiAdViewController_がUIView型のメンバだ。

viewDidApearイベント時にで、画面に指定のコントローラが保持するViewが出現した際にiAdを生成する。

補足

tableView:titleForHeaderInSection:で項目名や、
tableView:titleForFooterInSection:で説明文を入れていると、
viewが手前に配置されてしまい、iAdのバナーよりもうしろになる。
これを回避するために、奥に成ってしまっているiAdのz-indexを手前に描画されるようにresetIAdPositionでviewのソートをさせている。
これで手前側に移動させることができる。

ただし、推奨とされている表示方法はもう一段階踏み込まなければ成らない。
それは表示可能になった際にはじめて表示するというもの。
オブジェクトとして生成してから表示が可能になるまでに時間がかかるので、その間は画面外にでも配置しておくべき。
これは別途記事にする。

参考

サルにもできるiPhoneアプリの作り方iAdを設置してみよう! その1
※ソースはほとんどここのものがベース。
StackOverFlow:How to set iPhone UI View z index?

iOS5プログラミングブック
加藤 寛人 吉田 悠一 藤川 宏之 西方 夏子 関川 雄介 高丘 知央
インプレスジャパン
売り上げランキング: 2989

[XCode4][UIKit]UITableVieCellをオリジナルのXibを使用して表示する

ここに凄くシンプルな方法が載っていたのでまとめ。
http://stackoverflow.com/で載っていたサンプル

用意するもの

  • Cell.h
  • Cell.m
  • Cell.xib
  • MasterViewController.h
  • MasterViewController.m

前提として・・・

Cellは基本的に表示してる領域+前後いくつか分のViewしか持たないのでCellそのものにデータ保持をさせるのは危険。

やっていること

独立したUITableViewCellクラス専用に1つのXibを持たせる。
Cell自身がXibを呼び出すので、他のサイトで載っているようなController側が保持するやりかたのように複雑風にならない。

Cell.h

#import 

@interface Cell : UITableViewCell
{
    NSNumber* num_;
@private
   IBOutlet UILabel* label1_;
   IBOutlet UILabel* label2_;
   IBOutlet UIImageView* icon_;
}
@property (nonatomic,strong) UILabel* label1;
@property (nonatomic,strong) UILabel* label2;
@property (nonatomic,strong) UIImageView* icon;

+ (Cell*)cellFromNibNamed:(NSString *)nibName;
@end

Cell.m

#import "Cell.h"
@implementation Cell
@synthesize label1 = label1_;
@synthesize label2 = label2_;
@synthesize icon = icon_;

+ (Cell *)cellFromNibNamed:(NSString *)nibName {
    NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    Cell *customCell = nil;
    NSObject* nibItem = nil;
    while ((nibItem = [nibEnumerator nextObject]) != nil) {
        if ([nibItem isKindOfClass:[Cell class]]) {
            customCell = (Cell *)nibItem;
            break; // we have a winner
        }
    }
    return customCell;
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    NSLog(@"%s",__func__);
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
    }
    return self;
}

+ (Cell *)cellFromNibNamed:(NSString *)nibName について

このクラスメソッドでは何でも格納ObjectNSArray型のnibContentsにXibを読み込んで格納し、列挙型NSEnumeratorを生成。
Xib内に独自Cell用のnibがあったら返してもらい、それを以てCellとし、列挙を離脱。
最後にCellを返して終了する、という流れ。

MasteViewController.m

セルの生成

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    cell = [tableView dequeueReusableCellWithIdentifier:@"orgCell"];
    if (cell == nil) {
        cell = (Cell *)[Cell cellFromNibNamed:@"Cell"];
        cell.label1.text = @"test";
    }
    return cell;
}

ここでは先のCellで定義したNibを読み込んでViewを生成するクラスメソッドを呼び出して格納している。
label1.textはviewに載っているLabelにIBOutletで接続したUILabelへの接続。独自に作ったXibに合わせて変更を。

また、Cellにコントローラを埋める場合はCellの使い回しをセルの種類単位で分けないとぐちゃぐちゃになるとの事。

セルの高さ指定

必ずFloat型の数値を返しておくこと。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"%s",__func__);
    return 100.0;
}

セル選択時の表現の変更

セルを生成しなおしたりできる。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"%s",__func__);
}

セルの選択解除時の変更

変更をデフォルトに戻しておく。

-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"%s",__func__);
}

XibのIBOutletの設定

File’sOwnerのCustomClassの項目はMasterViewControllerにしておく。
※これは記述無しでも駆動する。記述が無い場合はデフォルトの値が入るので、Cell
Cell側のCustomClassの項目はCellにする。
Cells'Class

IBOutlet付きのメンバはCell下のLabel – Label1等に直接接続する。
IBOutlet

デフォルトのプロパティに準拠

UITableViewCellはデフォルトでUILabelを複数保持している。
これらのプロパティ名はtextLabelとdetailTextLabelなのでこれらの既存プロパティをメンバ変数と一緒に扱う事で上書きができる。無為にプロパティを増やさなくて済む。

サンプル

Cell.h

@interface Cell : UITableViewCell
{
    NSNumber* num_;
@private
    IBOutlet UILabel* label1_;
    IBOutlet UILabel* label2_;
    IBOutlet UIImageView* icon_;
    IBOutlet UILabel* detailTextLabel_;//既存プロパティ用に追加。
    IBOutlet UILabel* textLabel_;//既存プロパティ用に追加。
}

Cell.m

@synthesize detailTextLabel = detailTextLabel_;
@synthesize textLabel = textLabel_;

これで既存プロパティと共通化させる事が出来た。

下の画像のセル中央部分がオリジナルのプロパティ。右側がデフォルトのプロパティを利用した表示。
Screen Shot 0024-09-09 at 13.51.26

今更きけないUILabelの基本まとめ

概要

UILabelはUIViewを継承したクラス。
固定された内容のテキストを画面に貼付けるのに使う。
生成したら必ずどこぞのコントローラ下のViewにaddSubviewする。

UILabelTest

プロパティ

設定できるプロパティは以下の通り。

text

  • NSStringでパラメータ付与。
  • ラベル内に表示する文字列を指定する。

font

 

  • UIFontで指定。
  • フォントの種類を指定。フォントオブジェクトはその章を参照。
  •  

textColor

   

  • UIColorでパラメータ付与。
  • フォントの色を変える。
  •  

lineBreakMode

   

  • UILineBreakMode定数を指定する。
  • 文字数オーバー時の表現方法を指定。
  •  

enabled

   

  • BOOL指定。
  • グレー反転させて使用不可表現を行う。
  •  

adjustsFontSizeToFitWidth

   

  • BOOL指定。
  • textで指定した文字列がラベルの矩形内に納まる様にアジャストするか否かを指定する。
  • これをYESニシタバアイハlineBreakModeが無効になる。
  •  

baselineAdjustment

   

  • UIBaselineAdjustmentを文字列定数で指定。
  •  

minimumFontSize

   

  • CGFloatで指定。
  • 次に記述するnumberOfLinesが1の時のみ効果が出る。
  •  

numberOfLines

   

  • NSInteger指定。
  • textで指定された文字列を表示する限界の行数を指定する。
  •  

highlightedTextColor

   

  • UIColor指定。
  • ハイライトされた際の文字色を指定する。
  •  

highlighted

   

  • BOOL指定
  • ハイライトするか否かを指定する。
  •  

shadowColor

   

  • UIColor指定。
  • highlightedが有効だと
  •  

shadowOffset

   

  • CGSize指定。
  • CGSizeはCGFloat型のwidthとheightの2部構成。CGSizeMake(w,h)で作成。
  •  

userInteractionEnabled

   

  • BOOL指定。
  • タッチの検出をするか否か。しない、がデフォ。
  •  

メソッド一覧

    drawTextInRect:

    • このメソッドは直接呼ばない方が良い。継承したサブクラスのデフォルトの振舞で描画したい時に呼ばれるべきだ。
    • このメソッドが呼ばれる時、現在の描画コンテキスト(デフォルトの環境とテキストの色で)の設計が完了する。
    • Overrideしたメソッドではsuper使って同名メソッドを発動させよう。

    textRectForBounds:limitedToNumberOfLines:

       

    • ラベルのテキストの矩形(CGRect)を返す。
    • parameter
      • bounds(CGRect)
      •      

        • レシーバの矩形を渡す
      • numberOfLines:(NSInteger)
      •          

        • 最大行数を渡す
    • このメソッドは直接呼ぶべきではない。レシーバに指定した矩形に何かしらの計算を伴う動作をさせる前に、継承したサブクラスがOverrideしたメソッドから呼ぶべきだ。
    • 最大行数を指定するnumberOfLinesパラメータを使って高さの上限を返される矩形に指定できる。
    • メソッドを呼ぶためにはsizeToFitか、sizeThatFitsの呼び出しが先だ。
    • UITableViewCellオブジェクト内のUILabel、またはサイズ指定のないUILabelはセルの寸法ベースのサイズが適用される。
    •    

    UILineBreakModeについて

    矩形に対する文字列幅がオーバーした際の表現方法を指定する。
    もともと行数指定をしてた場合は矩形幅がたらなくなるところまでは大凡共通した仕様で改行が行われる。
    UILineBreakModeCharacterWrapは例外で、Word間で自動改行ではなく、たらなくなった時点で改行が行われるので1単語を切ることになるので注意が必要。
    UILabel_Preview

    typedef enum {
       UILineBreakModeWordWrap = 0,
       UILineBreakModeCharacterWrap,
       UILineBreakModeClip,
       UILineBreakModeHeadTruncation,
       UILineBreakModeTailTruncation,
       UILineBreakModeMiddleTruncation, } UILineBreakMode;
    

    サンプル

    このサンプルを実行すると本記事のトップで表示した画像とおなじViewになる。

    - (void)viewDidLoad
    {
        NSLog(@"%s",__func__);
        [super viewDidLoad];
        
        float margine = 5;
        float y = 5;
        float h = 70;
        CGRect rect1 = CGRectMake(10,y,300,h);
        UILabel* label_UILineBreakModeWordWrap = [[UILabel alloc]initWithFrame:rect1];
        label_UILineBreakModeWordWrap.lineBreakMode = UILineBreakModeWordWrap;
        label_UILineBreakModeWordWrap.text = @"label_UILineBreakModeWordWrapnthis is label_UILineBreakModeWordWrap this is label_UILineBreakModeWordWrap this is label_UILineBreakModeWordWrap this is label_UILineBreakModeWordWrap";
        label_UILineBreakModeWordWrap.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
        label_UILineBreakModeWordWrap.textAlignment = UITextAlignmentLeft;
        label_UILineBreakModeWordWrap.numberOfLines = 3;
        [self.view addSubview:label_UILineBreakModeWordWrap];
        
        y += h + margine;
        
        CGRect rect2 = CGRectMake(10,y,300,h);
        UILabel* label_UILineBreakModeTailTruncation = [[UILabel alloc]initWithFrame:rect2];
        label_UILineBreakModeTailTruncation.lineBreakMode = UILineBreakModeTailTruncation;
        label_UILineBreakModeTailTruncation.text = @"label_UILineBreakModeTailTruncationnthis is label_UILineBreakModeTailTruncation this is label_UILineBreakModeTailTruncation this is label_UILineBreakModeTailTruncation this is label_UILineBreakModeTailTruncation";
        label_UILineBreakModeTailTruncation.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
        label_UILineBreakModeTailTruncation.textAlignment = UITextAlignmentLeft;
        label_UILineBreakModeTailTruncation.enabled = NO;
        label_UILineBreakModeTailTruncation.adjustsFontSizeToFitWidth = YES;
        label_UILineBreakModeTailTruncation.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;//UIBaselineAdjustmentNone;//UIBaselineAdjustmentAlignCenters;//
        label_UILineBreakModeTailTruncation.numberOfLines = 3;
        label_UILineBreakModeTailTruncation.highlighted = YES;
        label_UILineBreakModeTailTruncation.shadowColor = [UIColor redColor];
        label_UILineBreakModeTailTruncation.shadowOffset = CGSizeMake(3.0, 2.0);
        [self.view addSubview:label_UILineBreakModeTailTruncation];
    
        y += h + margine;
    
        CGRect rect3 = CGRectMake(10,y,300,h);
        UILabel* label_UILineBreakModeMiddleTruncation = [[UILabel alloc]initWithFrame:rect3];
        label_UILineBreakModeMiddleTruncation.lineBreakMode = UILineBreakModeMiddleTruncation;
        label_UILineBreakModeMiddleTruncation.text = @"label_UILineBreakModeMiddleTruncationnthis is label_UILineBreakModeMiddleTruncation this is label_UILineBreakModeMiddleTruncation this is label_UILineBreakModeMiddleTruncation";
        label_UILineBreakModeMiddleTruncation.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
        label_UILineBreakModeMiddleTruncation.textAlignment = UITextAlignmentLeft;
        label_UILineBreakModeMiddleTruncation.numberOfLines = 3;
        label_UILineBreakModeMiddleTruncation.highlighted = NO;
        label_UILineBreakModeMiddleTruncation.highlightedTextColor = [UIColor blueColor];
        label_UILineBreakModeMiddleTruncation.shadowColor = [UIColor redColor];
        label_UILineBreakModeMiddleTruncation.shadowOffset = CGSizeMake(3.0, 2.0);
    
        [self.view addSubview:label_UILineBreakModeMiddleTruncation];
        
        y += h + margine;
    
        CGRect rect4 = CGRectMake(10,y,300,h);
        UILabel* label_UILineBreakModeHeadTruncation = [[UILabel alloc]initWithFrame:rect4];
        label_UILineBreakModeHeadTruncation.lineBreakMode = UILineBreakModeHeadTruncation;
        label_UILineBreakModeHeadTruncation.text = @"label_UILineBreakModeHeadTruncationnthis is label_UILineBreakModeHeadTruncation this is label_UILineBreakModeHeadTruncation this is label_UILineBreakModeHeadTruncation";
        label_UILineBreakModeHeadTruncation.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
        label_UILineBreakModeHeadTruncation.textAlignment = UITextAlignmentLeft;
        label_UILineBreakModeHeadTruncation.numberOfLines = 3;
        label_UILineBreakModeHeadTruncation.highlighted = YES;
        label_UILineBreakModeHeadTruncation.highlightedTextColor = [UIColor blueColor];
        label_UILineBreakModeHeadTruncation.shadowColor = [UIColor redColor];
        label_UILineBreakModeHeadTruncation.shadowOffset = CGSizeMake(3.0, 2.0);
    
        [self.view addSubview:label_UILineBreakModeHeadTruncation];
    
        y += h + margine;
    
        CGRect rect5 = CGRectMake(10,y,300,h);
        UILabel* label_UILineBreakModeClip = [[UILabel alloc]initWithFrame:rect5];
        label_UILineBreakModeClip.lineBreakMode = UILineBreakModeClip;
        label_UILineBreakModeClip.text = @"label_UILineBreakModeClipnthis is label_UILineBreakModeClip this is label_UILineBreakModeClip this is label_UILineBreakModeClip this is label_UILineBreakModeClip";
        label_UILineBreakModeClip.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
        label_UILineBreakModeClip.textAlignment = UITextAlignmentLeft;
        label_UILineBreakModeClip.numberOfLines = 3;
        label_UILineBreakModeClip.userInteractionEnabled = NO;
        [label_UILineBreakModeClip textRectForBounds:CGRectMake(50,y,100,h) limitedToNumberOfLines:0];
        [self.view addSubview:label_UILineBreakModeClip];
    
        y += h + margine;
    
        CGRect rect6 = CGRectMake(10,y,300,h);
        UILabel* label_UILineBreakModeCharacterWrap = [[UILabel alloc]initWithFrame:rect6];
        label_UILineBreakModeCharacterWrap.lineBreakMode = UILineBreakModeCharacterWrap;
        label_UILineBreakModeCharacterWrap.text = @"label_UILineBreakModeCharacterWrapnthis is label_UILineBreakModeCharacterWrap this is label_UILineBreakModeCharacterWrap this is label_UILineBreakModeCharacterWrap this is label_UILineBreakModeCharacterWrap";
        label_UILineBreakModeCharacterWrap.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
        label_UILineBreakModeCharacterWrap.textAlignment = UITextAlignmentLeft;
        label_UILineBreakModeCharacterWrap.numberOfLines = 3;
        label_UILineBreakModeCharacterWrap.userInteractionEnabled = YES;
        [label_UILineBreakModeCharacterWrap drawTextInRect:CGRectMake(10,y,100,h)];
        [self.view addSubview:label_UILineBreakModeCharacterWrap];
    }
    

    詳解 Objective-C 2.0 第3版

    Objective-C プログラマーズバイブル

[C#][ObjC]列挙子

Enumerator=調査員。なんだか不穏な感じ。納税状態とかチェックされちゃいそう。
ちゃんと調べてみます。

C#だと

列挙子はGetEnumeratorメソッドを持っていればforeachで使うコレクションにできるIEnumeratorを取得できる。
このメソッドを持ってる=IEnumerableってこと。

IEnumeratorを実装しないとforeach文で列挙できない。
というか、IEnumeratorはforeachの為にある。かのようにMSDNには記述されている。

IEnumerable.GetEnumerator

http://msdn.microsoft.com/ja-jp/library/system.collections.ienumerable.getenumerator(v=vs.80).aspx

IEnumeratorの最初の位置、Reset直後はundefinedが入ってる。というかC#だと何も入ってない?
いずれにせよ最初に動作させるにはMoveNext()を一発かませとな。
コレクションの要素が変更追加削除されると無効になり、これは回復不能。
コレクションの全ての処理が終わるまでロックすることが推奨される。

以上、MSDNより。

ロックってどうやんの?

Unityだと

内容は以下のリンクのp55。
http://www.slideshare.net/lucifuges/unityc
つまり、MoveNextメソッド内の処理に、yield が挟まる間隔単位でcaseが設けられたswitch構文入りの処理をメンバに持つクラスが生成されるとのこと。
newされる度にクラス変数がカウントアップし、返す内容がシフトする仕組み。

Objective-Cだと

http://www.atmarkit.co.jp/fcoding/articles/objc/05/objc05d.html

NSEnumeratorクラス。

NSArrayやNSDictionaryクラス(これを継承したNSMutable**も?)列挙子を内包しているので、for-in使える。

Objective-Cでの使い方

.MoveNextにあたるのが nextObject。
取得する要素が無いとnilを返す。

for( id obj in array )
{
     NSLog( @"value : %@" , obj );
}
for (id key in dic)
{
     NSLog( @"key : %@ nvalue : %@n", key , [dic objectForKey:key] );
}

列挙子の取得から列挙。

NSEnumerator *en = [dic objectEnumerator];
for ( id obj in en)
{
     NSLog( @"value : %@n ", obj );
}

これで中身をズラズラっと取得。

NSDictionaryはkeyの列挙できるkeyEnumeratorがある。

NSEnumerator *en = [dic keyEnumerator];
id key;
while (key = [en nextObject]) {
     NSLog(@"key: %@, value: %@n", key, [dic objectForKey:key]);
}

サンプル

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
    NSLog(@"ApplicationStart");
    
    NSLog(@"NSArray");
    NSArray *arr = [NSArray arrayWithObjects:@"arr0",@"arr1",@"arr2",@"arr3",nil];
    for(id obj in arr)
    {
        NSLog(@"value:%@",obj);
    }
    
    NSLog(@"NSDictionary");
    NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
                         @"value1",@"key1",
                         @"value2",@"key2", nil];
    for(id key in dic)
    {
        NSLog(@"key:%@, value:%@",key,[dic objectForKey:key]);
    }
    
    NSEnumerator *en = [dic keyEnumerator];
    id key;
    while(key = [en nextObject])
    {
        NSLog(@"en::key:%@, valu:%@",key,[dic objectForKey:key]);
    }
}

[iOS]Json形式のテキストを読み込んでNSDictionaryにする

このメモはSBJson frameworksに関するもの。
記述時点での正式リリースバージョン3.0.4
参考:http://stig.github.com/json-framework/api/3.0/
ARC適用する場合は3.1α以降でなければ動作しない。
http://ip7.biz/wordpress/?p=1008

導入手順

  1. ターミナルを立ち上げる
  2. gitから入手すべく以下を入力してENTER
    • git clone git://github.com/stig/json-framework.git
  3. DLしたファイルを解凍し、解凍したファイル下のClassフォルダ内のファイルを自分のプロジェクトフォルダにD&Dしてコピーする。
    • もしビルドが通らないのであれば、、Build Phases のCompile SourcesにSBJon関連全ファイル(.m,.h)を追加することでBuildを通す事が出来る。
    • この時のエラーメッセージ
      _OBJC_CLASS_$_SBJsonParser", referenced from:
  4. Json形式のファイルを扱うクラス(.m)内で以下を記述
    • #import 
  5. JSON形式のファイルを扱うクラスヘッダ内で以下を記述
    • @class SBJsonStreamParser;
    • @class SBJsonStreamParserAdapter;

テキストファイルから読み込むんでデータ化

テキストファイルから読み込む

        //read file
        NSString* fileName  = [NSString stringWithFormat:@"dummyData"];
        NSString* path      = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Documents"];
        path = [path stringByAppendingPathComponent:fileName];
        NSData* data = [NSData dataWithContentsOfFile:path];

読めてなかったら強制終了

if(data == nil) abort();

データを文字列にエンコード

        //Data encode to NSString.
        NSString* dataStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"data = %@",dataStr);

SBJsonでパースする

        //SBJson parse to NSDictionary.
        SBJsonParser* sbjsonparser =[[SBJsonParser alloc]init];
        NSError* error;
        error = nil;
        NSDictionary* dic = [sbjsonparser objectWithString:dataStr error:&error];
        NSLog(@"JSON dictionary=%@", [dic description]);

テスト

読み込んだテキストファイルの中身

{
"buttonArray":[
    {
      "word":"キウイ",
      "imgPath":"greenButtons_0000s_0003.png"
    },
    {
      "word":"うみがめ",
      "imgPath":"redButtons_0000s_0003.png"
    }
    ]
}

動作確認した際に出た文字列

CoreData[39477:fb03] JSON dictionary={
    buttonArray =     (
                {
            imgPath = "babylogButtons_0000s_0003.png";
            word = "U30adU30a6U30a4";
        },
                {
            imgPath = "babylogButtons_0000s_0003.png";
            word = "U3044U3061U3054";
        }
    );
}

JavaScript本格入門 ~モダンスタイルによる基礎からAjax・jQueryまで

[ObjC]ファイルの読み書きとNSHomeDirectory()と[[NSBundle mainBundle]resourcePath]

テキストファイルの読み込みでどうしようもないところでハマったので戒めを込めてちゃんとまとめておく。

やろうとしたこと

テキストファイルを読み込む
テキストファイルはコンパイル時には存在している
起動と同時に読み込んでそれを保持する。

ハマったところ

App内がファインダから参照できずにAppと同じディレクトリに置いてあるDocumentsフォルダが自前で用意したものと混同。
参照しても容易したファイルが存在せず、起動時も落ちる。
原因は[[NSBundle mainBundle]resourcePaht]で取得するべきパスをNSHomeDirectory()で取得していた事。
だって読み込み用のサンプルにはNSHomeDirectory()ベースで記述されてるんだもの…。

参考にしたソース

ボタンを押すとボタンのタグに応じて書込み/読み込みが実行される。

//ボタンクリック時に呼ぶ
- (IBAction)clickButton:(UIButton*)sender {
    if (sender.tag==0 {
        NSData* data=[self str2data:_textField.text];
        [self data2file:data fileName:@"test.txt"];
        NSLog(@"data = %@",data);
    } else if (sender.tag==1 {
        NSData* data=[self file2data:@"test.txt"];
        [_textField setText:[self data2str:data]];
        NSLog(@"data = %@",data);
    }
}


//バイト配列の書き込み
- (BOOL)data2file:(NSData*)data fileName:(NSString*)fileName {
    NSString* path=[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    path=[path stringByAppendingPathComponent:fileName];
    NSLog(@"path : %@",path);
    return [data writeToFile:path atomically:YES];
}

//バイト配列の読み込み
- (NSData*)file2data:(NSString*)fileName {
    NSString* path=[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    path=[path stringByAppendingPathComponent:fileName];
    NSLog(@"path : %@",path);
    return [NSData dataWithContentsOfFile:path];
}

コードの解説

-(IBAction)clickButton:(UIButton*)sender

ボタンが押された際にボタンに付属したタグに応じて処理を分岐させている。

- (BOOL)data2file:(NSData*)data fileName:(NSString*)fileName

分岐した先での処理。保存するパスをNSHomeDirectory()でルートを取得。
その後Documentsディレクトリ下を保存場所にして文字列化。
さらにファイル名を追加。ファイル名は引数で渡されている文字列を使用。
最後に保存時の結果を返す。

- (NSData*)file2data:(NSString*)fileName

書込み同様に読み込み先をDocuments下のディレクトリに指定。
ファイル名も指定。
最後に読み込んだデータを返す。

改変

データを書込みしてから読み込み、という流れであればNSHomeDirectory()ベースで良い。
なぜなら保存用ディレクトリであるDocumentsフォルダ内にファイルを置いているし、読み書きすべきところはそこだから。

やりたかったのは書き込みをする前からファイルが存在していて、それを読み込んでほしかった。
よって以下のように内容を修正

//バイト配列の読み込み
- (NSData*)file2data:(NSString*)fileName {
    NSString* path=[[[NSBundle mainBundle]resourcePath] stringByAppendingPathComponent:@"Documents"];
    path=[path stringByAppendingPathComponent:fileName];
    NSLog(@"path : %@",path);
    return [NSData dataWithContentsOfFile:path];
}

[[NSBundle mainBundle]resourcePath]をベースとしたディレクトリ内のファイルを読むように記述を変更。
App/Documents/data.txtというファイルが合った場合は

        NSData *data;
        NSString* fileName = [NSString stringWithFormat:@"data.txt"];
        NSString* path;
        path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Documents"];
        path = [path stringByAppendingPathComponent:fileName];
        data = [NSData dataWithContentsOfFile:path];

という記述で(NSData)data内にデータが格納される。

以降の処理は

        if(data == nil) abort();

これを挟んでから処理を記述した。
ファイルの読み込みに失敗するとその場でアプリが落ちるのでバグに気づく。

NSHomeDirectory()と[[NSBundle mainBundle] resourcePath]

前者のNSHomeDirectoryは.appが置いてあるディレクトリを指す関数で、アプリが自分で保存したファイル等を読み書きする際に使用する。
一方後者のディレクトリは.app内をRootとしたディレクトリで一つ階層が下になる。

プロジェクトファイルのTARGETSから対象を選択し、BuildPhaseから確認できるCopy Bundle Resourcesの項目が後者で取得したパスから見えるファイル群。
なのでここにあるファイルは前述のソースで言うところのNSHomeDirectory()を後者のメソッドに置き換える事で参照が可能になる。
NSHomeDirectory()
写真はiPhoneシミュレータの中のディレクトリ。
NSBundle (ry で指定された場所はAppより下になる。
NSHomeDirectory()で取得できるパスはAppやDocuments等がある一つ上の階層で直接ファインダーで確認できる。

参考

この本にファイル読み書きのサンプルが載っていたおかげで助かった。
iPhone/iPad/iPod touchプログラミングバイブル―iOS 5/Xcode 4対応 (smart phone programming bible)

UIScrollViewを使ってちゃんとスクロールさせる手順

達成した事があったにもかかわらずちょっとハマってしまったのできちんとメモって二度轍しないようにするための記事。

UIScrollViewを扱うにあたって最低限しておかなければ成らない事

  • 3重の入れ子構造にする
    • Window
      • UIScrollView
        • contentView
          • 実際に見せたいView群

やる事

  1. UIScrollViewを生成
  2. ScrollViewの上に乗せるコンテンツのViewを生成
  3. ScrollViewにaddSubViewでコンテンツのViewを乗せる。
  4. ScrollViewに必要なビューを乗せたいだけ沢山乗せる(載せる)。
  5. ScrollViewの可動域を指定する。
  6. 現在のViewにはUIScrollViewを上書きする。

サンプルコード

– (void)loadView
{
[super loadView];

UIScrollView* scv = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, 320, 520)];     //1
scv.scrollEnabled = YES;//4のいちぶ
scv.backgroundColor = [UIColor redColor];

UIView* mainView = [[UIView alloc]initWithFrame:CGRectMake(0,0,320, 7000)];//2
mainView.backgroundColor = [UIColor yellowColor];
//整形用の値
float margineX = 5.0;
float margineY = 5.0;
float buttonWith = 310.0;
float buttonHeight = 70.0;
float margine = 5.0;
float titleHeight = 200.0;

UILabel* titleImage = [[UILabel alloc]initWithFrame:CGRectMake(0,0,320,titleHeight)];
titleImage.backgroundColor = [UIColor blueColor];
titleImage.text = [[NSString alloc]initWithString:@”Title”];
titleImage.textColor = [UIColor whiteColor];
titleImage.font = [[UIFont alloc]fontWithSize:32];
[mainView addSubview:titleImage];//3,4

margine += titleHeight + margineY;

UIButton* btnCamera = [self makeButton:CGRectMake(margineX, margine,buttonWith,buttonHeight) text:@”Camera” tag:BTN_CAMERA];
[mainView addSubview:btnCamera];//3,4

margine += buttonHeight + margineY;

UIButton* btnPhoto = [self makeButton:CGRectMake(margineX,margine,buttonWith,buttonHeight) text:@”Photo” tag:BTN_READ];
[mainView addSubview:btnPhoto];//3,4

margine += buttonHeight + margineY;

UIButton* btnSaved = [self makeButton:CGRectMake(margineX,margine,buttonWith,buttonHeight) text:@”saved” tag:BTN_WRITE];
[mainView addSubview:btnSaved];//3,4

margine += buttonHeight + margineY;

UIButton* btnGraph = [self makeButton:CGRectMake(margineX,margine,buttonWith,buttonHeight) text:@”Graph” tag:BTN_WRITE];
[mainView addSubview:btnGraph];//3,4

margine += buttonHeight + margineY;

[scv setContentSize:CGSizeMake(320.0, 500.0)];//5
[scv addSubview:mainView];
self.view = scv;//6

}

margineの部分の処理はBlockにした方が美しいよね…。

参考

図入りで説明されてて分かりやすいし、ズームのさせ方まで載ってるので勉強になった。
テン*シー*シー:http://ameblo.jp/xcc/entry-10322378932.html

ObjCの継承に関する疑問とselfの暴走とか

詳細Objective-C2.0第3版を読み進めてます。

詳解 Objective-C 2.0 第3版
詳解 Objective-C 2.0 第3版
posted with amazlet at 12.01.26
荻原 剛志
ソフトバンククリエイティブ
売り上げランキング: 7869

その中の継承に関する項目にて。
A->B->Cという継承構造でsuperをつかうとCからAのメソッドを呼び出せるよ!みたいな事が書かれてました。

文献上の例だとAからCの3つですが、そもそもクラスは全てNSObjectから継承してるはずなので、Superいっぱつで親飛び越して先祖を参照する、っていっきにNSObjectを参照することになるの?どこまで上位を参照しにいくんだ?とか疑問が。

結論から言うと、継承を続けている際に、直近のOverrideされたメソッドを使用する、という挙動でした。
それ以外にも色々勘違い解消できました。

Screen Shot 0024-01-26 at 12.47.21-2

試してみた

実際に、以下の様にサンプルを作ってコンソールに経過を吐かせて動作を確認します。

A.h

#import 

@interface A : NSObject{
}

- (void)method1;
- (void)method2;
- (void)method3;

@end

A.m

#import "A.h"

@implementation A
@synthesize One = One_;
@synthesize Two = Two_;
@synthesize Three = Three_;

-(void)method1{
  NSLog(@"A::method1");
  [self method2];
  [self method3];
}
-(void)method2{
  NSLog(@"A::method2");
}
-(void)method3{
  NSLog(@"A::method3");
}
@end

B.h

#import "A.h"

@interface B : A
-(void)method1;
@end

B.m

#import "B.h"

@implementation B

//*
-(void)method1{
  NSLog(@"B::method1");
}
// */
@end

C.h

#import "B.h"

@interface C : B

-(void)method2;
@end

C.m

#import "C.h"

@implementation C

-(void)method2{
  NSLog(@"C::method2");
  NSLog(@"next line call super method 1");
  [super method1];
}
@end

AppDelegate.h

#import 

@interface AppDelegate : UIResponder 

@property (strong, nonatomic) UIWindow *window;

@end

AppDelegate.m

“- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions”に以下を追加で。

  A* a = [[[A alloc]init]autorelease];
  B* b = [[[B alloc]init]autorelease];
  C* c = [[[C alloc]init]autorelease];

  NSLog(@"a method");
  [a method1];
  [a method2];
  [a method3];
  NSLog(@"b method");
  [b method1];
  [b method2];
  [b method3];
  NSLog(@"c method");
  ;//←WPのバグで余計なのでてるので注意
  ;//←WPのバグで余計なのでてるので注意
  ;//←WPのバグで余計なのでてるので注意
  NSLog(@"end");

実行するとコンソールには以下の様に。

2012-01-26 12:51:36.090 classTest[51229:f803] a method
2012-01-26 12:51:36.092 classTest[51229:f803] A::method1
2012-01-26 12:51:36.092 classTest[51229:f803] A::method2
2012-01-26 12:51:36.093 classTest[51229:f803] A::method3
2012-01-26 12:51:36.094 classTest[51229:f803] A::method2
2012-01-26 12:51:36.094 classTest[51229:f803] A::method3
2012-01-26 12:51:36.095 classTest[51229:f803] b method
2012-01-26 12:51:36.107 classTest[51229:f803] B::method1
2012-01-26 12:51:36.109 classTest[51229:f803] A::method2
2012-01-26 12:51:36.110 classTest[51229:f803] A::method3
2012-01-26 12:51:36.111 classTest[51229:f803] c method
2012-01-26 12:51:36.113 classTest[51229:f803] B::method1
2012-01-26 12:51:36.114 classTest[51229:f803] C::method2
2012-01-26 12:51:36.114 classTest[51229:f803] next line call super method 1
2012-01-26 12:51:36.117 classTest[51229:f803] B::method1
2012-01-26 12:51:36.117 classTest[51229:f803] A::method3
2012-01-26 12:51:36.118 classTest[51229:f803] end
2012-01-26 12:51:36.119 classTest[51229:f803] Applications

ここで注目。Cクラスが送ったメッセージ[super method1]はどこに届いたか?
答え:Bクラスのオブジェクト。

つまり、superで指定した先は直近のクラスでした。

じゃ、もっと上のクラスのを参照するには?

とりあえずBクラスでOverrideしたmethod1をコメントアウトします。

B.h

#import "A.h"

@interface B : A
//-(void)method1;
@end

B.m

#import "B.h"

@implementation B
/*
-(void)method1{
  NSLog(@"B::method1");
}
// */
@end

もういっかい立ち上げる…とコンソールが激しいことに!!

2012-01-26 12:55:10.106 classTest[51311:f803] a method
2012-01-26 12:55:10.109 classTest[51311:f803] A::method1
2012-01-26 12:55:10.110 classTest[51311:f803] A::method2
2012-01-26 12:55:10.110 classTest[51311:f803] A::method3
2012-01-26 12:55:10.111 classTest[51311:f803] A::method2
2012-01-26 12:55:10.111 classTest[51311:f803] A::method3
2012-01-26 12:55:10.112 classTest[51311:f803] b method
2012-01-26 12:55:10.112 classTest[51311:f803] A::method1
2012-01-26 12:55:10.113 classTest[51311:f803] A::method2
2012-01-26 12:55:10.113 classTest[51311:f803] A::method3
2012-01-26 12:55:10.114 classTest[51311:f803] A::method2
2012-01-26 12:55:10.114 classTest[51311:f803] A::method3
2012-01-26 12:55:10.115 classTest[51311:f803] c method
2012-01-26 12:55:10.115 classTest[51311:f803] A::method1
2012-01-26 12:55:10.116 classTest[51311:f803] C::method2
2012-01-26 12:55:10.117 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.117 classTest[51311:f803] A::method1
2012-01-26 12:55:10.118 classTest[51311:f803] C::method2
2012-01-26 12:55:10.118 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.154 classTest[51311:f803] A::method1
2012-01-26 12:55:10.155 classTest[51311:f803] C::method2
2012-01-26 12:55:10.155 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.156 classTest[51311:f803] A::method1
2012-01-26 12:55:10.162 classTest[51311:f803] C::method2
2012-01-26 12:55:10.163 classTest[51311:f803] next line call super method 1
2012-01-26 12:55:10.163 classTest[51311:f803] A::method1
2012-01-26 12:55:10.164 classTest[51311:f803] C::method2
2012-01-26 12:55:10.164 classTest[51311:f803] next line call super method 1

途中から再帰呼び出し状態に。

これはCクラスのmethod2で呼び出した [super method1];はAクラスのmethod1がselfのmethod2を呼び出している。

-(void)method1{
  NSLog(@"A::method1");
  [self method2];
  [self method3];
}

つまりCクラスのmethod2を呼び出し、再度Aクラスのmethod1が呼ばれ、という再帰状態に。

あれ?selfってthisみたいなものじゃなかったの?

とどのつまり

継承に関しては直近の先祖の中でOverrideされたものを適用する、という事がわかりました。
superはOverrideされたか否かで参照先が変動するということです。

そして、自身が犯した勘違いは、selfはthisと同じ様に、自身を参照するのかと思っていた点。
実際は、呼び出したオブジェクトのメソッドをコールするのでした。

この挙動を実現するにあたって、存在しないメソッドをコールした際に動作が停止しないで無視される言語仕様にした理由がわかりました。

文献に対する理解力不足がおもわぬ勘違い解消に発展した変なお話。

【UINavigationController】ボタンを押すことで画面遷移する構造の基本構成

画面遷移する際の概念がやっと理解できたので、構成を残しておく。
サンプルは以下の通り。

処理順のイメージ

  1. AppDelegateがWindowを生成
  2. AppDelegateがTopViewControllerを生成してWindowに表示
  3. TopViewControllerがViewに載ったらイベント付きのボタンを2つ生成
  4. ボタンが押されたらViewController1を生成してWindowのViewに掲示。
  5. ViewController1がViewに載ったらイベント付きのボタンを1つ生成
  6. ボタンが押されたらTopViewControllerを生成してViewに乗せる。

気をつけなければ成らないのは以下の点

  • このサンプルではUINavigationControllerを利用しているので、遷移経路が全て保持されており、経路上の画面自体も保持され続ける。
  • よって、メモリオーバー必至。
  • メモリオーバー時用の対処はUIKit詳細リファレンスに載ってるのでそれを参照。

サンプル

AppDelegate.h

#import 

@interface AppDelegate:NSObject {
  UIWindow* window_;
  UIViewController* rootController_;
}

@property (strong, nonatomic) UIWindow *window;

@end

AppDelegate.m

#import "AppDelegate.h"
#import "TopViewController.h"

@implementation AppDelegate
@synthesize window = _window;

- (void)applicationDidFinishLaunching:(UIApplication *)application{
  // window を自分で作成
  CGRect bounds = [[UIScreenmainScreen]bounds];
  window_ = [[UIWindowalloc] initWithFrame:bounds];
  TopViewController* top = [[[TopViewController alloc]init]autorelease];
  rootController_ = [[UINavigationController alloc] initWithRootViewController:top];
  [window_addSubview:rootController_.view];
  [window_makeKeyAndVisible];
}

- (void)dealloc{
  [rootController_release];
  [window_release];
  [superdealloc];
}

@end

TopViewController.h

#import 
@interface TopViewController : UIViewController
@end

TopViewController.m

#import "TopViewController.h"

@implementation TopViewController

- (UIButton*)makeButton:(CGRect)rect text:(NSString*)text tag:(NSInteger)tag{
  UIButton* button = [UIButtonbuttonWithType:UIButtonTypeRoundedRect];
  [button setFrame:rect];
  [button setTag:tag];
  [button setTitle:text forState:UIControlStateNormal];
  [button addTarget:selfaction:@selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
  return button;
}

- (void)viewDidLoad{
  NSLog(@"top view did load");
  [superinit];
  UIButton* milkButton = [self makeButton:CGRectMake(10, 20, 300, 100) text:@"milk"tag:0];
  [self.viewaddSubview:milkButton];
  UIButton* soltButton = [self makeButton:CGRectMake(10, 120, 300, 100) text:@"solt"tag:1];
  [self.viewaddSubview:soltButton];
  NSLog(@"top view did load end");
}

- (IBAction)clickButton:(UIButton*)sender{
  NSLog(@"click Milk: tag is : %d",sender.tag);
  /*
   画面遷移の処理を行う。
   1:viewControllerを生成する。
   2:その画面に遷移。
   */
  //UIViewControllerを生成
  Class class = NSClassFromString(@"ViewController1");
  id viewController = [[[class alloc] init ]autorelease];
  [self.navigationController pushViewController:viewController animated:YES];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end

ViewController1.h

#import 
@interface ViewController1 : UIViewController
@end

ViewController1.m

#import "ViewController1.h"

@implementation ViewController1

- (void)viewDidLoad{
  [super viewDidLoad];

  //Hello,world!ラベルを追加
  //背景は白、文字は黒で。
  UILabel* label = [[[UILabel alloc] initWithFrame:self.view.bounds] autorelease];
  label.text = @"Hello, world";
  label.textAlignment = UITextAlignmentCenter;
  label.backgroundColor = [UIColor whiteColor];
  label.textColor = [UIColor blackColor];
  label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  [self.view addSubview:label];

  //これをタップしたら画面遷移する
  UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  [button setTitle:@"画面遷移" forState:UIControlStateNormal];
  [button sizeToFit];
  CGPoint newPoint = self.view.center;
  newPoint.y += 50;
  button.center = newPoint;
  button.autoresizingMask =
    UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
  [button addTarget:self
             action:@selector(buttonDidPush:)
   forControlEvents:UIControlEventTouchUpInside];
  [self.viewaddSubview:button];
}

-(id)init{
  if((self = [superinit])){
    self.title=@"Hello";
  }

  returnself;
}


- (void)buttonDidPush:(UIButton*)sender{
  NSLog(@"Pushed!!");
  id TopViewController = [[[TopViewController alloc] init ]autorelease];
  [self.navigationController pushViewController:TopViewController animated:YES];
}

@end

おまけ

ちなみにViewController1のbuttonDidPush:senderメソッドを以下の様に書き換えると、トップに戻る様になるので遷移経路を保持せずにメモリを節約できる。

- (void)buttonDidPush:(UIButton*)sender{
  NSLog(@"Pushed!! tag:%d",sender.tag);
  [self.navigationController popToRootViewControllerAnimated:YES];
//  id viewController = [[[TopViewController alloc] init ]autorelease];
//  [self.navigationController pushViewController:viewController animated:YES];
}

iOSプログラミング逆引きリファレンス108 ~知りたいことがすぐわかるiPhoneプログラミングテクニック~

iPhoneプログラミングUIKit詳解リファレンス

iOSデバッグ&最適化技法 for iPad/iPhone
詳解 Objective-C 2.0 第3版