Hi-farm blog

ActionScript 3.0 , AIR , FLEXからCocoa, OpenGL

Archive for the ‘Cocoa(Objective-C)’ Category

試行錯誤した後のコードをもとに書いているので無駄なコードがあるかもしれません。ご了承ください。。。

iOS5からはOSベースでのTwitter連携のサポートがありますが、2012年1月現在だとまだiOS4にも対応させたいですよね。

そんな時の便利なライブラリとしてこちらがあります。
https://github.com/bengottlieb/Twitter-OAuth-iPhone

サンプルコードもついていて、サンプルをビルドするとすぐに挙動が確認できて、テスト投稿なんかも簡単にできてしまいます。
この取っ付きやすさはライブラリを使う際に「簡単なんだ」と思う事ができてやる気をあげてくれます。

ただ、このライブラリはログアウトの事はあまり考えていないようで、ログアウトしてアカウントを切り替えるなどしたい場合は結構大変です。

僕は下記の方法をとりました。あくまで一例ですのでご容赦ください。


SA_OAuthTwitterEngine.m

- (BOOL) logout
{
	BOOL res = NO;

	NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
	[defaults removeObjectForKey:@"authData"];
	res = [defaults synchronize];
	[self clearAccessToken];
	[_accessToken release];
	_accessToken = [[OAToken alloc] initWithKey: nil secret: nil];

	return res;
}

アクセストークンもクリアしてるしこれで問題ないはずなのですが、これだけだとキャッシュデータが残ってしまっているようで、ログアウトして再度認証画面を呼んでもテキストの入力ができません。

なのでSA_OAuthTwitterEngineを保持しているクラスで認証画面を呼ぶ処理でこの用に書きました。

if (_engine){	return;}
_engine = [[SA_OAuthTwitterEngine alloc] initOAuthWithDelegate: self];
_engine.consumerKey = kOAuthConsumerKey;
_engine.consumerSecret = kOAuthConsumerSecret;

[_engine setClearsCookies:YES];

_authorizeViewController = [[SA_OAuthTwitterController controllerToEnterCredentialsWithTwitterEngine: _engine delegate: self] retain];

[_engine setClearsCookies:NO];

_authorizeViewControllerを初期化する前に

[_engine setClearsCookies:YES];

を呼んでキャッシュをクリアできるようにしておき、初期化後に

[_engine setClearsCookies:NO];

としてデフォルトのNOに値を戻します。
これでログアウト後に再度ログイン用の認証ダイアログを呼んでも新たにアカウントを入力する画面が表示されたはずです。

ちなみに、iOS5以降は送信先URLがhttpではなく、httpsでないとうまく認証できないのでご注意を。ちなみにhttpsにするとiOS4でも問題ないので修正しておくと良いと思います。

ちなみに「強火で進め」様のサイトが詳しいです。
http://d.hatena.ne.jp/nakamura001/20100519/1274287901

ちゃんと精査して書ききれていない記事ですみませんが、同じようにはまる方の助けになればと思います。

何かありましたらコメント欄でも twitterの @mmlemon でもお気軽に突っ込んでください。

iOSアプリの開発で、adlantisの広告を入れて試すにはlibz.dylibが必要なのですが、プロジェクトにファイルを追加するのを横着して、間違ってsdk4.3のものを追加してしまった後、ビルドするとわけのわからないエラーが出て、どれだけ戻してもエラーが解消されなかったのですが、下記の画像のように Library Search Pathsに赤枠の”DeviceSupport/4.3.5″が追加されていることが原因でした。

これが現行のsdkと衝突して問題を起こしていたのかな?

library_searchpath

この赤枠の参照を外すと無事動作するようになりました。

最近iOSのGameCenter、特にLeaderBoard周りに興味があって調べているのですが、はまりまくっている状態なので残しておきます。
iTunes ConnectでLeaderboardを作ってみて、一応アプリから表示はできるもののなぜかデータが登録できないなどある場合に意外に気づきませんでした。

iTunes ConnectでLeaderboardを作るだけではだめで、Manage Your Appsから対象となるアプリのVersionsにある View Details からGame Centerの横にあるグレーのボタンをクリックして、 ”Enabled for This Version”に変える必要があります。

赤く囲んでいるボタンをクリックします。
to_view_details

↓ここの赤枠で囲んでいるところがグレーの場合はクリックして画像の状態にしてください。
gamecenter_itunesconnect

その他についてはこちらのブログ記事が非常に参考になるのでご紹介します。
iOS Game CenterでLeaderboardのScore送信が反映しない « istb16 blog
http://istb16.wordpress.com/2011/09/13/ios-game-center_leaderboard_scoresend/

これには気づいたもののまだまだ解決できていないところがあるので解決できたらまた記事あげますね。。。

連投すみません。

UINavigationControllerを使って階層化されたViewを使うときにはUITableViewと組み合わせる事もあると思うのですが、一つ前の画面に戻るときにデータを再読み込みさせたい時の方法が分からなかったのでメモ。

NSArray* naviary = [self.navigationController viewControllers];
    NSInteger current = [naviary count]-1;
    UITableViewController* prev = (UITableViewController*)[naviary objectAtIndex:current-1];
    [prev.tableView reloadData];

navigationControllerがviewControllersという配列で今までのUIViewControllerの参照を保持しているので、一つ前の階層なら最後から一つ前の要素の参照を取得し、reloadDataをすることで現在の画面で変更したデータを反映させた結果を表示する事も可能です。

今日はメモです。

iOSアプリ開発で、独自に作成したUITableViewCellをタップした時にデフォルトのUITableViewCellと同じように文字色を変えたかったのですが、見つかるまでに少し時間がかかったのでメモしておきます。

独自で作成したUITableViewCellのサブクラス内に

-(void)setHightlighted:(BOOL)highlighted animated:(BOOL)animated

というメソッドがあるので、それをオーバーライドしてhighlightの値を元に表示を切り替えると画面に触れたときに、文字色を変える事ができます。

-(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
    if(highlighted){
        [self setWhite];
    }else
    {
        [self setBlack];
    }
    [super setHighlighted:highlighted animated:animated];
}

※setWhite, setBlackは文字色を変更するための独自メソッドです。

iPhoneのSimulatorと実機での違い

iPhoneアプリの開発をしていてハマった事がまたありましたので残しておきます。

Objective-Cで、
例えばクラスAの@interface部で@protectedとして宣言していた変数aをサブクラスで呼ぶ場合、通常であればそのまま使えるはずなのですが、実機に向けてビルドするときにはエラーになってしまいました。

これは調べてみるとどうやら、
・シミュレータではデフォルトの設定が@protected
・実機ではデフォルトが@private
と言う事に原因がある様です。(そういった差が生まれないように@で明示的に宣言したつもりだったのですが)

最善の解決策ではないですが、-(id)gethoge;といったメソッドを作成しました。
カプセル化がしっかりできていないとは思うのですが、これでひとまずはエラー出ずにビルドし、問題なく動作するようになりました。

参考URL(StackOverflow:英語):
http://stackoverflow.com/questions/5513605/inheritance-working-on-simulator-but-not-on-iphone

DarwiinRemote

WiiRemoteでMacを操作するアプリ、DarwiinRemote

これを使えばマウスになるよ!っていうものです。
だけど、僕はまだまだ使いこなせていません。使いこなせなくて、マウスポインタがおかしなところに行っているのを停止させるためにまたぎこちない操作でなんとか停止ボタンまでたどり着いては必死にクリック、とまあこれはこれはひどい。。
自分が古い世代の人間だな、と実感したのでした。

ビデオはこれ。

ペアリング方法:

とはいってもやはり一度は試してみたいという方もいらっしゃると思いますので、簡単な使い方をご紹介します。

アプリケーションを起動した直後の画面がこれなのですが、「Find Wiimote」ボタンをクリックすると、下のログ表示部に
「Please press 1 button and 2 button simultaneously」と表示されますので、この間に1, 2のボタンを交互に押して下さい。あんまり速度を気にする事なしに操作しているとペアリングに成功します。

その後はウィンドウ左上の場所に傾きが解るようなグラフが表示されますので傾きなどを確認して下さい。

その後、「Mouse Mode Off」と表示されているところを変更すると、マウスとして認識するようになります。Mouse Mode On (motion)を選んで下さい。

これはこれで良いのですが、なんとソースコードまでリンク先のsourceforgeからダウンロードできます。
ってことは、僕がずっと気になって探していたマウスエミュレートやキーボードエミュレートができる訳であり、、、それはわくわくです。
そのあたりについてはまた次の記事にて書かせていただくつもりです。
この仕組みを理解すれば無理にWiiRemoteを使う事なく、任意のデバイスで操作できるようになるので、iPhoneやほかの何かの信号を出す機械を自由にマウスでバイスやキーボードにできるわけですね。例えば流行のkinectとか使って。

やっぱり楽しい。

いままでずっとiPhoneアプリ作ってる作ってると言うばっかりで作れてなかったですが、やっとリリースできました。
しかもiPadアプリです。

最近VJしているときに自分で使っていて、自分自身がユーザーであり続けるので今後も愛情たっぷりで育てて行こうと思います。

何かご指摘等あればコメントいただけるとうれしいです。

後々色々かけるといいなあ。

buy iMIDICon from app store

[iPad] 外部出力方法

やっぱり久々なポストです。

iPadでは外部映像出力が可能なので、それを試しているのですが、ちょっと解らずハマっていた事があったのでメモ程度に書いておきますね。デバッグするのも困難ですし。

基本的な実装の手順としては、下記の通りです。
1. つながっているスクリーンを[UIScreen screens]がNSArrayで取得する。
2. 1.で取得したUIScreenからお好みのUIScreenModeを取得する。
3. UIScreenを取得する。
4. 取得したUIScreenのcurrentModeに2.で取得した内容を設定する。
5. 表示させたいコンテンツを作成する。
6. 外部出力させるためにUIWindowを作る。
7. 6.に表示させたいコンテンツをaddSubView: する。
8. window.screen に3.のUIScreenを設定する。
9. windowの基礎的な設定を行う。

以上です。

——————————————

// ログとして外部モニターに表示する内容
// log message displayed in external monitor.
NSMutableString *str = [[NSMutableString alloc] init];
int screenCount = [[UIScreen screens] count];

// 設定されるスクリーンモード
// used screen mode
UIScreenMode *current = nil;

// checking each screen information
for(int i = 0; i < screenCount; i++)
{
 int modelen = [[[[UIScreen screens] objectAtIndex:i] availableModes] count];

 // checking each screen mode in screen.
 for (int j = 0; j < modelen; j++)
 {
   // getting screen mode
   UIScreenMode *curMode = [[[[UIScreen screens] objectAtIndex:i] availableModes]
 objectAtIndex:j];

   // get information of screen.
   NSString *sstr = [NSString stringWithFormat:@"screen:%i, mode:%i, w:%f, h:%f,
  ratio:%f ---", i, j, current.size.width, current.size.height,
current.pixelAspectRatio];

   [str appendString:sstr];

   // if curMode.size.width is larger than current, change current reference
   if(curMode.size.width > current.size.width){
    current = curMode;
    }
   }
  }

  // screen object
  UIScreen *another = [[UIScreen screens] objectAtIndex:1];
  another.currentMode = current;
  CGSize size = current.size;
  uvc = [[UIViewController alloc] init];

  // create new window.
  UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(
 0.0f,
 0.0f,
 size.width,
 size.height
)];

UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(
0.0f, 0.0f, 800.0f, 800.0f)
];
[uvc.view addSubview:tv];
tv.text = str;

[tv release];

[window addSubview:uvc.view];
window.screen = another;

[window makeKeyAndVisible];
[window setHidden:NO];

—————————————————
※syntaxhighlighterをうまくつかえていないので改行をくわえています。

ちなみにgithubにのせてますのでご興味のある方はご覧下さい。
http://github.com/mmlemon/iPad-external-display-sample

何か間違いやご不明な点があればお気軽にご質問下さいね!

iPhoneでFlashの画面を操作する

以前、名古屋のFxugにて少し発表した内容なんですが、ずっと記事にできてなかったので、今更ながらまとめます。

密かに前から社内用発表会のネタとして作ってはいたのですが、僕個人的にも、なかなか披露する機会がないまま、時間が過ぎてしまいました。

概要としては、
1. iPhone上でボタンをタップすると、AIR 2で作ったサーバーに、iPhoneからSocket経由で信号を送ります。
2. AIR 2のサーバーが、信号を受け取り、信号に応じた処理を行います。

これが作ったアプリのキャプチャです。

viewer

iPhoneとAIR 2サーバーの関係は、この通りです。

kousei

簡単に行ってしまえば、これだけです。
では、ここから説明します。

1. AIR 2のサーバー部分を作成
AIRでサーバーを作るには、UDPとTCPのサーバーがありますが、今回はTCPで通信するので、flash.net.ServerSocketクラスを使用します。

宣言〜受付開始

<pre>import flash.net.ServerSocket;

// 接続してきたSocketを保持する
var socks:Vector. = new Vector.();
// インスタンス生成
var server:ServerSocket = new ServerSocket();
// 接続時のイベントハンドラ作成
server.addEventListener(ServerSocketConnectEvent.CONNECT, serverSocketConnectHandler, false, 0, false);
// 待ち受けポートを指定(43243番で受け付ける)
server.bind(43243);
// 待ち受け開始
server.listenen();</pre>

接続時:

function serverSocketConnectHandler(event:ServerSocketConnectEvent):void
{
  // 接続してきたSocket
  var socket:Socket = event.socket;
  // 接続してきたSocketにデータを返すため、イベント設定
  socket.addEventListener(ProgressEvent.SOCKET_DATA, receiveHandler, false, 0, false);
  // 接続してきたSocketを保持する
  socks.push(socket);
}

受信時:(本来は宛先を指定するべきだが、特に指定せず、全てのSocketに送信しています)

function receiveHandler(event:ProgressEvent):void
{
  // データを送信したSocketを取得
  var socket:Socket = event.target as Socket;
  if(socket!=null)  // nullチェック
  {
    // 受信データを取得
    var recv:String = socket.readUTFBytes(socket.bytesAvailable);
    // 操作対象のVideoPlayerを取得
    var target:VideoPlayer = getCurrentVideo(); // 自作のメソッドです
    // コマンドを取得し、操作を実行。コマンドを取得するため、indexOfを使っています。
    if( recv.indexOf("prev") >= 0) {
      setPos("prev");
    } else if( recv.indexOf("next") >= 0) {
      setPos("next");
    } else if( recv.indexOf("play") >= 0) {
      if( target.playing ) {
        target.pause();
      } else {
        target.play();
      }
    }
  }
}

AIRで作るサーバー周りは、こんなところです。エラー処理や切断時の処理などは書いてなくてごめんなさい。

iPhone側アプリケーションのSocket処理
では、次。iPhoneのほう。これも簡単な書き方でごめんなさいね。と、いうかCで最低限の実装しかしていないです。

TCPSendClient.m
接続:

-(void)connect:(NSString *)ip toPort:(NSInteger)port
{
  	if(_connected)  // TCPSendClientが持っている、接続状態を保持する変数
	{
		return;
	}
	_connected = NO;
	// IPアドレスを取得
	servIP = [ip UTF8String];
	// portを取得
	servPort = port;
	// socketが正しく生成できているかチェック
	if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
	{
		NSLog(@"socket creation error.");
		return;
	}
	// socketの設定
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr(servIP);
	servAddr.sin_port = htons(servPort);
	// 接続
	if(connect(sock, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
	{
		NSLog(@"connect error.");
		return;
	}
	_connected = YES;
	NSLog(@"connect succeed.");
}

送信:

-(void)send:(NSString *)message
{
  mes = [message UTF8String];
  mesLen = strlen(mes);
  // 送信
  if(send(sock, mes, mesLen, 0) != mesLen) {
    NSLog(@"send failed.");
  }
}

ちなみに、
TCPSendClientクラスは、Socket処理を受け持つクラスで、自作しています。
TCPSendClient.hはこんな風に書いています。

#import <Foundation/Foundation.h>
#import <stdio.h>
#import <sys/socket.h>
#import <arpa/inet.h>
#import <stdlib.h>
#import <string.h>
#import  <unistd.h>

@interface TCPSendClient : NSObject {
	int sock;
	struct sockaddr_in servAddr;
	unsigned short servPort;
	const char *servIP;
	const char *mes;
	unsigned int mesLen;
	BOOL _connected;
}

-(void)connect:(NSString *)ip toPort:(NSInteger)port;
-(void)send:(NSString *)message;
-(void)close;
-(BOOL)getConnected;
@property(readonly) BOOL connected;

@end

このサンプルでは、このようにTCPSendClientを使っています。

PagerViewController.m

-(IBAction) processConnect:(id)sender  // ボタンをタップした時のAction
{
	if([connectSwitch isOn])  // これは、接続のon/offを切り替えるUISwitchです。
	{
		[sendc connect:@"10.0.2.1" toPort:43243];  // sendcというのがTCPSendClientです
		stateField.text = @"connect";
	}
	else
	{
		[sendc close];  // 本文では紹介しなかったですが、切断します。
		stateField.text = @"disconnect";
	}
}

データ送信:

-(IBAction) btnUp:(id)sender  // 各ボタンのTouchUpInsideと関連づけています。
{
	isPress = NO;
	prevValue = 0;
	if( (UIButton*)sender == btn) // btnとは、一つ前の動画にフォーカスをあてるためのボタンです。
	{
		dir = @"prev";
		[sendc send:dir];
	}
	else if((UIButton*)sender == nextBtn) { // nextBtnとは、一つ次の動画にフォーカスをあてるためのボタンです。
		dir = @"next";
		[sendc send:dir];
	}
	else if((UIButton*)sender == playBtn) { // playBtnとは、フォーカスがあたっている動画を再生するためのボタンです。
		dir = @"play";
		[sendc send:dir];
	}
}

ちなみに、こちらがiPhoneアプリの画面です。(すごい地味ですね。。。笑えない)

Interface Builderのキャプチャです。

ib

Macをルーターにする
最後に、iPhoneとAIRサーバーを接続するための設定をします。Macなら「インターネット共有」を使えば簡単にルーターにして連携させる事が可能です。こういうときに、一つのメーカーが作っている事が良い事に思えますね。
今回は、AirMac経由で共有します。

kyouyuu

1. 「システム環境設定」を開く
2. 「共有」を開く
3. 「インターネット共有」を選択する
4. 「共有する接続回路」がAirMac以外であることを確認。なっていなければ変更します。
→iPhoneとMacをAirMac(無線)で接続する
5. 「インターネット共有」のチェックを入れると、共有を開始するか確認してきますので、開始をクリックします。すると、画面右のAirmacのアイコンがこのようになります。

airmac

開始順としては、AIRサーバーとiPhoneが接続する必要があるので、
1. Macをルーターにする
2. AIRサーバーアプリを起動する
3. iPhoneアプリを起動して接続する。
4. iPhone上で操作する。

というところです。

今回簡単に紹介しすぎててすみません。このままでわかりにくいなどご意見ありましたら整理した上で、ソース込みで全て公開します。