2012年5月28日月曜日

Unknown type name 'MyClass'; did you mean 'SomeClass'?



Unknown type name 'MyClass'; did you mean 'SomeClass'?

クラス名が解決できない場合に出るメッセージですが、ヘッダファイルをimportしているのに発生することがあります。その場合は相互参照している可能性があります。

次のような場合に発生します。

#import "MyClass2.h"
@interface MyClass1
@property MyClass2 *myClass2;
@end

#import "MyClass1.h"
@interface MyClass2
@property MyClass1 *myClass1;
@end

この場合は一方のクラスでimportの代わりに@classを使うようにすると解決します。

@class MyClass2;
@interface MyClass1
@property MyClass2 *myClass2;
@end

#import "MyClass1.h"
@interface MyClass2
@property MyClass1 *myClass1;
@end

MyClass1のimplementationでMyClass2をimportする必要があります。

2012年5月22日火曜日

HTML5+JavaScriptによるSwipeの実装例

Manifest+LocalStorageによるオフラインアプリ作成(4)

サンプルアプリで、iOSに似せたSwipeを実装してあります。Swipe画面でテーブルセルをSwipeすると「削除」ボタンが表示がされ、「削除」ボタンタップでテーブルセルが削除されます。
デスクトップではマウス操作で行うことができます。IEではレイアウトが崩れますが、一応Swipe動作します。

このサンプルアプリで使用しているのswipeに関するJavaScriptは次のとおりです。

//Table Cellなどでイベント発生
function swipeCellClicked(event) {
  var div = event.srcElement;
  //Swipeオブジェクトを作る
  new Swipe(div, event);
}
//イベントハンドラの設定などを行う。
Swipe = function(div, event) {
  this.start = new Date().getTime();
  this.div = div;
  //iOSの場合
  if (isIOS) {
    div.addEventListener("touchmove", Swipe.touchMove, false);
    div.addEventListener("touchend", Swipe.touchEnd, false);
  } else {
    //SafariなどaddEventListenerを使うブラウザの場合
    if (typeof div.addEventListener != "undefined") {
      div.addEventListener("mousemove", Swipe.touchMove, false);
      //div.addEventListener("mouseout", Swipe.touchEnd, false);
      div.addEventListener("mouseleave", Swipe.touchEnd, false);
      div.addEventListener("mouseup", Swipe.touchEnd, false);
    } else {
      //IEなどattachEventを使う場合
      div.attachEvent("onmousemove", Swipe.touchMove);
      div.attachEvent("onmouseout", Swipe.touchEnd);
      div.attachEvent("mouseup", Swipe.touchEnd);
    }
  }
  if (typeof event.pageX != "undefined") {
    Swipe.x0 = event.pageX;
    Swipe.y0 = event.pageY;
  } else {
    Swipe.x0 = event.x;
    Swipe.y0 = event.y;
  }
}
//touchMoveまたはmouseMoveイベント。イベント発生位置を保存。
Swipe.touchMove = function(event) {
  if (event != null) {
    if (typeof event.pageX != "undefined") {
      Swipe.x1 = event.pageX;
      Swipe.y1 = event.pageY;
    } else {
      Swipe.x1 = event.x;
      Swipe.y1 = event.y;
    }
  } else {
    Swipe.x0 = Swipe.y0 = Swipe.x1 = Swipe.y1 = 0;
  }
}
//イベント終了時
Swipe.touchEnd = function(event) {
  div = (event == null ? this : event.srcElement);
  //イベントハンドラ削除
  if (isIOS) {
    div.removeEventListener("touchstart", Swipe.touchMove);
    div.removeEventListener("touchmove", Swipe.touchMove);
    div.removeEventListener("touchend", Swipe.touchEnd);
  } else {
    if (typeof div.removeEventListener != "undefined") {
      div.removeEventListener("mousedown", Swipe.touchMove);
      div.removeEventListener("mousemove", Swipe.touchMove);
      div.removeEventListener("mouseout", Swipe.touchEnd);
      div.removeEventListener("mouseleave", Swipe.touchEnd);
      div.removeEventListener("mouseup", Swipe.touchEnd);
    } else {
      div.detachEvent("mousedown", Swipe.touchMove);
      div.detachEvent("onmousemove", Swipe.touchMove);
      div.detachEvent("onmouseout", Swipe.touchEnd);
      div.detachEvent("mouseup", Swipe.touchEnd);
    }
  }
  var dx = Swipe.x1 - Swipe.x0;
  var dy = Swipe.y1 - Swipe.y0;
  //上下移動があまりなく、左右移動が十分ある場合にSwipeとする。
  if ((dx < -50 || dx > 50) && dy > -50 && dy < 50) {
    Swipe.div = div;
    setTimeout(Swipe.appendButton, 10);
  }
  Swipe.x0 = Swipe.y0 = Swipe.x1 = Swipe.y1 = 0;
}

Swipe.div = null;
Swipe.prevSwipedDiv = null;
//Swipeされたときに「削除」ボタンを追加、あるいは既に追加されている場合は削除する。
Swipe.appendButton = function() {
  var div = Swipe.findCellBoxDiv(Swipe.div);
  if (Swipe.prevSwipedDiv != null && Swipe.prevSwipedDiv != div) {
    Swipe.removeButton(Swipe.prevSwipedDiv);
    Swipe.prevSwipedDiv = null;
    return;
  }
  if (Swipe.removeButton(div)) {
    Swipe.prevSwipedDiv = null;
    return;
  }
  Swipe.prevSwipedDiv = div;
  var btn = document.createElement("INPUT");
  btn.type = "button";
  btn.value = "削除";
  btn.className = "swipeDelete";
  if (typeof btn.addEventListener == "undefined") {
    btn.attachEvent("onclick", Swipe.deleteCell);
  } else {
    btn.addEventListener("click", Swipe.deleteCell, false);
  }
  div.appendChild(btn);
}
//既に「削除」ボタンがセットされていたらそれを削除する。
//子要素にINPUT要素があったら削除し、trueを返す。ない場はfalseを返す。
Swipe.removeButton = function(div) {
  if (div.childNodes) {
    for(var i=div.childNodes.length - 1; i>= 0; i--) {
      if (div.childNodes[i].nodeType == 1 && div.childNodes[i].tagName == "INPUT") {
        div.removeChild(div.childNodes[i]);
        return true;
      }
    }
  }
  return false;
}
//「削除」ボタンがタップされたテーブルセルを削除
Swipe.deleteCell = function() {
  var div = Swipe.findCellBoxDiv(event.srcElement);
  div.parentElement.removeChild(div);
  Swipe.prevSwipedDiv = null;
}
//イベントが発生した要素を含むテーブルセル(DIV)を探す。
Swipe.findCellBoxDiv = function(div) {
  var cbDiv = div;
  while(cbDiv != null) {
    if (cbDiv.className == "TableCellBox") {
      return cbDiv;
    }
    cbDiv = cbDiv.parentElement;
  }
  return null;
}

HTML5+JavaScriptによるSwipeの実装例

2012年5月16日水曜日

HTML5+JavaScriptで画面をスライドさせて切り替える方法


Manifest+LocalStorageによるオフラインアプリ作成(3)

サンプルアプリではメイン画面のテーブル行を選択すると左にスライドして次の画面へ移動し、「戻る」ボタンでメイン画面に戻ります。

画面AからBへスライドして移動させる場合、次のような手順となります。
  • 同一HTML内にA、Bの両方のDIVを作る。
  • スライド前はAのみ表示する。
    画面固定にするために同時に表示される領域をデバイスのスクリーン内に収まるようにします。
  • スライド実行時にBをAの右側の位置に表示させる。
  • A、Bを同時に左方向にスライドさせる。
  • Aを非表示にする。
transformとtransitを用いて画面をスライドさせますが、アニメーションは非同期で行われるため、上記の手順を単純に記述したのでは一気に処理が終わり、アニメーション効果が出せません。そこでtimeoutを使用し、transit終了後に次の処理を開始するようにします。

サンプルアプリで画面スライドに関わる部分のJavaScriptは次のようになっています。

//グローバル変数
var mainBox = null;
var slideBox = null;

//body.onloadなどで初期化する。
function init() {
  mainBox = new SlideBox(divMainBox, null);
}

//テーブル行がタップされたとき呼ばれる。
function cellClicked(event) {
slideBox = new SlideBox(slideDiv, mainBox);
  slideBox.show();
}

//画面スライドを行うオブジェクト
SlideBox = function(div, prevBox) {
  this.div = div;
  this.div.className = "SlideBox";
  this.prevBox = prevBox;
}

SlideBox.sec = "0.3s";
SlideBox.msec = 300;
SlideBox.currBox = null;
SlideBox.divR = null;
SlideBox.divL = null;

//cellClickedから呼ばれ、画面スライドを開始する。
SlideBox.slide = function(moveX) {
  SlideBox.divL.style.webkitTransition = "all " + sec + " linear";
  SlideBox.divR.style.webkitTransition = "all " + sec + " linear";
  SlideBox.divL.style.webkitTransform = "translateX("+moveX+"px)";
  SlideBox.divR.style.webkitTransform = "translateX("+moveX+"px)";
}

SlideBox.hideBox = function(div1, div2) {
  div1.style.webkitTransition = "all 0s linear";
  div1.style.webkitTransform = "translateX(0px)";
  div2.style.display = "none";
}

SlideBox.prototype.show = function() {
  SlideBox.divR = this.div;
  SlideBox.divL = this.prevBox.div;
  SlideBox.divR.style.display = "block";
  setTimeout("SlideBox.postSlide()", 0);
}

SlideBox.postSlide = function() {
  SlideBox.slide(-320);
  setTimeout("SlideBox.postSlide2()", msec);
}

SlideBox.postSlide2 = function() {
  SlideBox.hideBox(SlideBox.divR, SlideBox.divL);
  return;
}

SlideBox.prototype.back = function() {
  SlideBox.divR = this.div;
  SlideBox.divL = this.prevBox.div;
  SlideBox.divL.style.display = "block";
  this.div.style.webkitTransform = "translateX(-320px)";
  setTimeout("SlideBox.postBack()", 0);
}

SlideBox.postBack = function() {
  SlideBox.slide(0);
  setTimeout("SlideBox.postBack2()", msec);
}

SlideBox.postBack2 = function() {
  SlideBox.hideBox(SlideBox.divL, SlideBox.divR);
  return;
}

HTML5+JavaScriptで画面をスライドさせて切り替える方法

2012年5月15日火曜日

HTML5+JavaScriptでiPhoneでスクロールしない画面を作る方法



Manifest+LocalStorageによるオフラインアプリ作成(2)

HTML+JavaScriptで作った画面は、通常はiPhoneの画面サイズに合わせてリサイズされ、ピンチ操作で拡大すると上下左右にスクロールします。これをネイティブアプリらしくiPhone画面に合わせ、スクロールを上下のみとするか、無効にする方法です。
サンプルはこのURLに置いてあります。

iPhone(およびiPod touch)に合わせて作ってありますが、デスクトップのSafari、Chiromeでも動作の様子を確認できます。Androideでも動作するはずですが、確認していません。(動作報告頂けると幸いです。)

次の点がポイントです。

どちらにも共通の設定

画面の横サイズをデバイスサイズ以下にする

iPhoneの画面サイズはScale=1の場合にwidth=320px, height=480pxですが、heightは状況により異なります。
Scale=1以外の場合はScale後のサイズに合わせます。scale=2であればwidth=640px, height=960pxとなります。

ユーザーによるリサイズを無効にする

次のメタタグを設定します。
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
initial-scaleとmaximum-scaleを同じ値にすることでピンチされても画面の拡大、縮小が起こらなくなります。scaleの値は1以外でも構いません。

アドレスバーが表示されない位置に移動する

画面表示時に次のJavaScriptを実行します。
window.scrollTo(0,0);

これは画面固定とは直接関係ありませんが、 アドレスバーが隠れるとよりネイティブアプらしくなります。また、上下方向も固定にした場合は「戻る」ボタンが隠れたままになることがあるため、必ずこれを行うようにします。

画面固定の場合の設定

上下方向も固定にしたい場合はbodyのontouchmoveイベントハンドラで、このイベントを無効にします。これを行うと画面の大きさに関わらず画面スクロールしなくなります。
<body ontouchmove="event.preventDefault()">

この方法では全ての画面でスクロールが無効になります。特定の画面でだけスクロールを無効にしたい場合はフラグを使用して、フラグがtrueのときだけイベントを無効にすればよいでしょう。
<body ontouchmove="event.preventDefault();">

パノラマ写真をiPhoneやiPadを左右にパンして見るアプリです。
自分の周りをぐるりと回すと、臨場感のあるパノラマ写真閲覧ができます。
写真ライブラリの画像を見ることができ、自分で撮影したパノラマ写真を見ることができます。
iCloudにある画像も見ることができます。
視野角度を画像の縦横比較からアプリが設定します。
視野角度は60°〜360°の範囲で調整できます。
ontouchmoveはかなりな頻度で発生するので、パフォーマンスへの影響が気になる場合は必要なときだけイベントハンドラをadd/removeしてください。

ホーム画面にアイコンを置いた場合の画面サイズ調整

ホーム画面にアイコンを置いた場合は、次のメタタグにより全画面表示となり、アドレスバー、ステイタスバーが非表示となります。
<meta name="apple-mobile-web-app-capable" content="yes" />

この場合の画面サイズはステイタスバーのheight=20pxを除き、360px×460pxとなります。

ステイタスバーのスタイルを次のメタタグでblack-translucentとすると、ステイタスバーに隠れる部分も表示領域となり、360px×480pxとなります。
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

これに合わせて画面レイアウトを調整したい場合は次の方法で画面サイズをチェックできます。
if (document.documentElement.clientHeight >= 460) {
//ホーム画面時のレイアウト設定
}

デスクトップとの共存

HTMLなのでデスクトップのブラウザでも表示可能です。デスクトップではなにもしなければ左寄せとなります。また右マージンでレイアウトしている場合は画面の幅の変動でレイアウトが崩れることがあります。デスクトップではセンタリングしたい、右マージンもそのまま有効にしたい、という場合はDIVのBOXを組み合わせることでiPhone、デスクトップの両方で同等の表示を行うことができます。
また、こうすることで画面サイズが異なるAndroid端末でもレイアウトが崩れないはずです(未確認)。

<body style="text-align:center;marigin:0px;padding:0px;">
    <div style="display:-webkit-box;width:320px;margin:0px auto 0px auto;padding:0px;">
        この中にHTMLを書く
    </div>
</body>

2012年5月5日土曜日

OSStatusのエラーコードを調べるコマンド: macerror

関数が返すOSStatusエラーコードの説明を調べるteminalコマンドです。

使用例:

$ macerror -48
Mac OS error -48 (dupFNErr): duplicate filename (rename)


2012年5月4日金曜日

リサイズした画像データの取得


UIImageViewで縮小画像の表示はできますが、画像データは元のままです。リサイズしてサムネイル画像データを作るには次のような方法でリサイズする領域に画像を描画し、UIImagを介して画像データを取り出します。

- (NSData *)makeResizedImageData
{    
    NSString *imagePath = @"http://.....";
    
    NSURL *imageUrl = [NSURL URLWithString:imagePath];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    if (!imageData) return nil;
    
    UIImage *image = [[UIImage alloc] initWithData:imageData];           
    CGSize smallSize = CGSizeMake(image.size.width / 5, image.size.height / 5);
        
    UIGraphicsBeginImageContext(smallSize);
    [image drawInRect:CGRectMake(0, 0, smallSize.width, smallSize.height)];
    UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    NSData *thumbnailData = UIImagePNGRepresentation(smallImage);
        
    NSLog(@"元データサイズ=%d, リサイズデータサイズ=%d", imageData.length, thumbnailData.length);
    return thumbnailData;
}