2012年12月31日月曜日

年越し

今年も間もなく終わる。
結局ラーメンは189杯。2日に1杯以上のペースで食べてしまった。来年は自制する。
appも結局公開できず。iPad及びiPadRetina用に画像を追加で作ってて気づいたら大晦日になっていた。
まあiPhone版はversion1.0がほぼ完成したしiPad版もテキストのサイズとかナビゲーションの位置を倍にしていく処理を入れれば結構そのままで動きそうなので年明け早々には申請したい。

年末はいろんな人と忘年したけど、昔からの友達のおかげで密かに気合を入れさせてもらった。
来年。2013。ここには書かんが7点。やること決めた。ありがとう。

それでは世界中のプログラミングとラーメンを愛するすべての人へ。
良いお年を。

2012年12月15日土曜日

projectの移行

mbp13r買ったし心機一転tumblrクライアントの各コードをすっきりさせるか!と思ったけどプロジェクトのもろもろの設定が超めんどくさくてすぐ心が折れた。昔の俺すげー。
とりあえず、普通にフォルダごとdropbox→googledriveに移すだけにして、ローカルのデータと同期させてとりあえずプロジェクトファイルを開いてみた。あっさり動いた。アンチエイリアスはretinaだとうまくいかんけどretinaでのmenloが綺麗すぎるのでとりあえずこのままいく。mbp13r最高。

2012年12月11日火曜日

macbook pro 13 retina

買っちゃった。ボーナス出たし買っちゃった。
最高やわー、windows7で2560x1600pxやってみたけどいいわーadobe系が超プロっぽくなったしchromeも表示200%でメイリオにしたら完璧やわー。CRT21inch使ってた頃思い出した。起動とかも超早いし。5年前のポリカmbに比べてやけど。macのretinaも綺麗すぎやし、ずーっと見てたらnexus7でさえつぶつぶに見える。やっぱiPad mini買わんでよかった。

で、あとやること。

・xcodeのプロジェクト移行。google driveに領域確保してappだけmacローカルと同期する。
・app内のゴミファイルは除く。これを機に全部.mの@implementationで変数宣言するよう書き換え?
・持っていくのはResources、audiostreamer、oauthディレクトリだけ?
・で、デベロッパ登録してtumblrクライアントを公開。年内目標。
・citibankの口座復活させる。
・itunesのライブラリを移行。
・winにofficeとか入れる。

かな。

2012年11月23日金曜日

nexus7

記念すべき100投稿目。前回から1ヶ月近く空いた。
仕事がピークだったのもあるが、iPad miniに失望してnexus7としばらく戯れてた。
mini発売日直後に心斎橋apple storeにmini買う気マンマンで行ったはいいが、うーん。。。
retinaじゃないけど画面サイズが小さいからppiは上がるし気にならんだろう、と期待したが、超汚い。はっきり言ってiPad2と印象は変わらん。
軽いのはいいけど片手では微妙にデカイし、親指がディスプレイにかぶるし、ソフトウェア的にエッジの部分の操作とホールドを区別して処理してます!ってそういう問題ちゃうやろ。隠れちゃってるやん。kindle大きく見たいし、日経も紙面で読みたいし、かなり葛藤して1時間近く占有して舐めるように確認したけど、結局やめた。iPad2持ってたらいらんわこれ。
で、次の日ビックカメラで見かけたnexus7買った。まさかandroid機買うなんて自分でもびっくり。でもよく自分の環境考えたらブラウザはchromeやしメールは仕事もプライベートもgmailやしここはblogger使ってるしdriveもreaderもmapsもバリバリに使ってるしIMEも当然Google日本語入力やしYouTubeもiPadじゃ公式使えんし、あれ?nexus7でいいんじゃね?ってなってビックでkindle入れて確認してみたら、見やすいやん。日経も紙面じゃないけど解像度大きいから見やすいし、値段も安いし、在庫僅少やし、決めちゃった。
2,000円のクーポンもらったからgoogle play見てたら、普通にエミュレータがあって腰抜けた。出先でking's fieldもlinda3もFFTもシレンも俺屍もmother1,2もバイオ2も全部オリジナルでできるってなにこれ。ゲームアプリいらんやん。googleサービスも当然純正の使い勝手でいちいち感動してた。mapsとか次元違うし。という感じで、しばらく遊んでた。
まあiOSappの開発は当然続けるし、携帯をiPhoneからandroid端末に変える気はさらさらない。tumblrクライアントのprefもほぼ完成やし、timelineすっ飛ばしてまずはデベロッパ登録するかもしれん。もうなんやかんやで1年近くなるねんなー。でもandroid向け(ていうかnexus7向け)にもアプリ作ってみよう。また楽しみが増えた。

昼に京橋の新福菜館でBセット。本店に比べて味濃い気がする。あとヤキメシ多すぎ。全部食べたけど死ぬかと思った。今年169杯目。2日に1杯ペース。やばい。

2012年10月29日月曜日

pref続き

久々にxcode。font settingに続きleft-hand modeとかphotoのresolutionとかview modeとかやった。
もーtimeline modeやる前に公開してまうかなー。でもやっぱsinglepost+timeline+univはいるか。
ていうかiPad mini欲しい。kindle store日本版来たし、日経iPhone5も全然来んし、買っちゃうか。
でも今期中にretina来たら泣くな。MBP13retinaも欲しい。2008macbookでやってるけどもう限界。
いまんとこ今年157杯ラーメン食べてる。2日に1回ペースになるよう、頑張る。
金曜東京出張やったからラーメン二郎@目黒行こうと思ってたけど結局叶わんかった。
あーとりとめない。酒弱いなー。幸あれかし。

2012年10月17日水曜日

since_id再最適化とfont size

since_id、short状態がなかなか完了しない状態が発生(これは前から認識)、とりあえずアクセラレータをつけてみた。なんとなく解消はされたけどもっともっと最適化はできそう。meta traderで為替の規則性を追っかけてた頃を思い出す。結局後付けの、理論というか言い訳というか、引力発見以前の惑星軌道計算みたいに、ゴテゴテ足していってとりあえずは計算ずくのように見せかけて実は近似がたまたまうまく行ってるように見えてただけでした、という感じ。そこまでひどくはないか。ひとまず、こんな感じで。今までのところの最適解。
if(!offsetflg){
if(lastid>topid){
NSLog(@"short! tid:%lld/sid:%lld/lid:%lld/difid:%lld (acc:%d/brk:%d)",topid,since_id,lastid,difid,acc,brk);
brk=2;difid=lastid-topid;difid*=acc++;since_id+=difid;
offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqDB];
return;
}else if(lastid<=[[[bd valueForKeyPath:post(@"id")]lastObject]longLongValue]){
NSLog(@"over! tid:%lld/sid:%lld/lid:%lld/difid:%lld (acc:%d/brk:%d)",topid,since_id,lastid,difid,acc,brk);
acc=1;
if(difid){since_id-=difid;difid/=brk++;since_id+=difid;}
else since_id-=(topid-[[[bd valueForKeyPath:post(@"id")]lastObject]longLongValue]);
offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqDB];
return;
}else{
NSLog(@"ok");
difid=0,acc=1,brk=2;
for(id idnum in [bd valueForKeyPath:post(@"id")]){
if(lastid<=[idnum longLongValue]) skip++;
else break;
}
}
}
あと、type毎のfont size設定も完了。これは別に何の苦労もなくできた。

iPhone5のケース到着。あらためて、これ
素晴らしい。

2012年10月16日火曜日

since_id再最終版

もうsince_idアルゴリズムは完成したつもりでデバッグというより普通にdashboard潜ってたらover(読み込み済みのpostの最後のidよりおかわりしたJSONの最後のidのほうが新しい現象)がいきなり出てきた。since_idは-=してるので原則short(おかわりしたJSONの最初のidが最後のpostidより古い)がまず起こるはずだが、おかしいなーと調べてたら多分おかわりしたJSONの最後のidと最後のpostidが一緒というケースが発生してる様子。
なのでoverはshortの時に足し過ぎて起こるだけでなく、いきなり出てくることもありうるという前提で、若干コードを書き換えた。
if(lastid<=[[[bd valueForKeyPath:post(@"id")]lastObject]longLongValue]){
NSLog(@"over/match! tid:%lld/sid:%lld/lid:%lld/difid:%lld (brk:%d)",topid,since_id,lastid,difid,brk);
if(difid){since_id-=difid;difid/=brk++;since_id+=difid;}
else since_id-=(topid-[[[bd valueForKeyPath:post(@"id")]lastObject]longLongValue]);
offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqDB];
return;
}

できた。これにNSTimerで自動的にぽんぽん次へ次へと行く仕組みを作ってデバッグ。
特に問題は無さそう。10,000件オーバーまで普通に行った。さすがにもー大丈夫だろう。

あとiPhone5用にまたケース買った。5用に3つ目。これ。
フィルムはマイクロソリューションのARSH買おうと思ってたけどぶっちゃけ今のsoftbankのフィルムでそんなに大きい不満はないからなー。3G時代からずーっとパワサポのアンチグレアやったけどこれからはやっぱり光沢系かな。

夜はマッチョで並野菜マシマシにんにくちょびっと背脂ちょいマシカラメ。美味い。

2012年10月14日日曜日

since_id決定アルゴリズム最終版

account,data source,post type,color themeがひと通りできてルンルンでデバッグしてたらjsonのおかわりがうまく動かないケースに遭遇。6月に、
since_idを確定できるのは、lastidがおかわりしたJSONの中に含まれ、且つlastidとJSONの最後のidが等しくない場合なので、それ以外のケースでは、
1.lastidよりおかわりしたJSONの最初のidが小さい場合
2.lastidよりおかわりしたJSONの最後のidが大きい場合
に切り分けて、1の場合はlastidとJSONの最初のidの差分をsince_idに+=し、2の場合は逆に-=して再度おかわりして確定できるまで繰り返していた。
などと書いていたが、そもそも2のケースは1での足し方がおかしい(足し過ぎ)から発生するのであって、任意の数値を引くんじゃなくて、一旦元に戻して足すべき差分を減らして(それでも2になれば減らし方を激しくする)再度「足す」のが正しい。2つのケースがあるんじゃなくて後者をエラー処理として考えてやればすっきりとまとまった。
if(!offsetflg){
if(lastid>topid){
difid=lastid-topid;since_id+=difid;
offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqDB];
NSLog(@"short! sid:%lld/lad:%lld (brk:%d)\n%@",since_id,lastid,brk,[bd valueForKeyPath:post(@"id")]);
return;
}else if(lastid<=[[[bd valueForKeyPath:post(@"id")]lastObject]longLongValue]){
since_id-=difid;difid/=brk++;since_id+=difid;
offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqDB];
NSLog(@"over! sid:%lld/lad:%lld (brk:%d)\n%@",since_id,lastid,brk,[bd valueForKeyPath:post(@"id")]);
return;
}else{
NSLog(@"ok");
brk=2;
for(id idnum in [bd valueForKeyPath:post(@"id")]){
if(lastid<=[idnum longLongValue]) skip++;
else break;
}
}
}
できた。あとはいよいよタイムラインか。
昨日は姫路でイベント。帰りに手打ち麺やす田@新大阪で塩と白飯。うまかった。

2012年10月11日木曜日

うーん

メッセージの書式が[]なので、配列の添字も同じだと見難いような気が。
例えば、[idArr addObject:[bd valueForKeyPath:post(@"id")][i]]; とか。
最初は狂ってるとしか思えなかったobj-cの記法にいつの間にか慣れてしまったのか。
まあとりあえずmodern化は全自動で一気にやったけどね。

preferencesはとりあえずtype選択とcolor theme選択は完了。
color themeは黒がしまって見えるので良い。こっちをデフォルトにしよう。
そろそろtimeline版も考えなくては。楽しい。

2012年10月7日日曜日

iPhone5+iOS6対応完了

iPhone5はちゃんと発売日に手に入れて楽しく触ってたけど仕事が結構忙しくて更に友人の結婚式の映像とかも作っててxcodeは全然いじれてなかった。とりあえず4.5にするだけしてほったらかしてたけどそろそろやっとくかーと再開してDefault-568h@2x.pngだけ作ってビルドしてみるも、パッと見動いてるけど細々不具合があって潰してた。

一つはvideoで、全画面再生をdoneしたら1番目のpostに飛んじゃうエラー。あまりにも意味不明なので泣きながらstackoverflowを舐めるように見てみるも同様の現象は見つからず。結局アカウントをpreferencesで変更したとき用にviewDidAppearでrefleshしてたのが原因。あまりの間抜けさに脱力。

それからphotoとaudioで、tapのジェスチャーを拾ってセレクタ投げた瞬間にdeallocatedで怒られてた。ちゃんと宣言してるし理由がよくわからず色々調べて、UIViewControllerのviewだけarrayに入れてたから新しくUIViewControllerをallocする度にその前のやつが保管されずにdeallocされてた様子。ていうかiOS5の時は動いてたと思うんですけど。。。とりあえずarrayにはvcで入れておいて使うときに[[vc objectAtIndex:hoge]view]をaddsubviewするようにしたら動いた。これでiPhone5+iOS6対応はたぶん完了。

あとは配列の添字の記法とかをmodernなものに変更して、Preferencesの続きやろう。

今日は高知出張。かつおたたき(塩)うまかった。
その前は京都だったので新福菜館。久々に行ったがやはりうまい。

2012年9月13日木曜日

iPhone5

だいたいは予想通りだった。絶対買う。
1136x640かー。今つくってるやつはまあ問題なく動くと思うけどどんな感じになるんかな。
ノイズキャンセリングは意外だった。かなり嬉しい。PHITEK blackbox i12買うかーでもコネクタ変わるしどうしよーかなーとうじうじ決めかねてたら公式が出た。及び腰ストラテジー万歳。
auはテザリングか。どうしようかな。ソフトバンクも来ないかな。iProxyあるけど。

とりあえずケース買った。こんなん

ついでにコーエー定番シリーズ 大航海時代も一緒に買ったった。勢いって怖い。でも当時これMSX2版で10,000円近くしてたように思う。めっちゃハマったなー。22年前か。時代の流れは恐ろしい。

followees/followers

地道にPreferences作成中。
マルチアカウントは意外にあっさり組めたけど、followee/erの取得処理につまずいた。
こいつらもlimitが最大20やからOADataFetcher→ticket受取を繰り返すねんけど、その間にアカウントを切り替えると当然followee/erが変わるのでエラーが返ってくるから、アカウント切り替えたらticketの受取キャンセルしようと頑張ったけど、フラグの判定か思考能力に問題があるのかなぜか最後まで行こうとする。
なのでNSObjectのサブクラス作ってこいつらにfetcher回させてアカウント変わったらnilして新品交換することにしたらうまく動いた。オブジェクト指向万歳。
あとavatarはGDCで非同期に取るようにしてたけど、follower300件弱程度でも負荷がでかいみたいで、timeoutを頻発してたから、結局NSURLConnectionでresponseを受け取る慣れ親しんだ仕様に切り替えたらあっさり動いた。フィーリングデバッグだが気にしない。

などと文章にするといかにも片っ端からソリューションしてるように見えるが、これらで2日は悩んだ。まあ何事も勉強だ。解けない問題はない。

アカウント絡みの処理は完了したので、後は細々カラーテーマやら文字サイズやらcontentModeやらやって、timelineに入りたい。PrefはひとつのUITableViewControllerクラスを再帰的(というのか)にpushしてるので、管理はしやすいがdelegateの継承管理などで大変。でもobjective-cがなんとなく手に馴染んできた感はある。気のせいかも。

ま、一言で言うといい感じに仕上がってきたかな。どうやろー。

2012年9月3日月曜日

アカウント、Photo、非同期

やっぱりOAuth前にログアウトしとくのが確実なような気がしたので最初hidden=YESでログアウト画面行って通常のログイン画面に戻った時点でhidden=NOにしてaccessTokenをrequestする形にした。

あと、設定画面で次の画面のtableをpushしたらdeallocatedと怒られた。調べるとnavigationcontrollerがnullになってる。interfaceで宣言しとかないとメモリ確保されないのか。なんか参考サイトではブロック内で宣言してるように見えるけど基本中の基本なんかな。まあdeallocatedの理由は自分の場合9割方こいつなので慌てふためくこともなくなってきた。

あとふと思い立って、Photoも現状は画像全体が画面にぴったりおさまるようにUIViewContentModeScaleAspectFitしてるけど、横幅いっぱいに広げて縦長の場合はスクロールするバージョンも用意して選択できるようサブクラスを作りなおした。完璧。

最後に非同期。avatarの画像を非同期で引っ張ってくるためにそれ用のUIImageViewのサブクラス作ろうとしたけどちょっと調べたらGCDであっさり並列処理できるみたいでちょっとやってみた。dispatch_queue_tというオブジェクトを生成してブロックで並列させたい処理をdispatch_asyncに入れるだけで簡単に動くみたい。

でもretinaだと画像が汚くて、大きめの画像を引っ張ってきてsetFrameしてもcellのimageViewに入れるとframeが効いてない。stackoverflowで探しても、UITableViewCellのlayoutSubviewsをオーバーライドしないといけないみたいで、めんどくさーと思いながらいろいろ探すとUIGraphicsBeginImageContextWithOptionsを見つけた。こいつの最後の引数(scale)に[[UIScreen mainScreen]scale]=通常1/retinaなら2を渡すとそれぞのディスプレイ解像度に応じた画像になるみたい。やってみた。

Preferences.m(抜粋)
if(item==@"Accounts"){
 cell.detailTextLabel.text=[[self.delegate getUserNames]objectAtIndex:0];
 if(!avatar){
  UIGraphicsBeginImageContext(CGSizeMake(32,32));
  [[UIImage imageNamed:@"img_white"] drawInRect:CGRectMake(0,0,32,32)];
  UIImage *white=UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  cell.imageView.image=white;
  NSString *url=[NSString stringWithFormat:@"http://api.tumblr.com/v2/blog/%@.tumblr.com/avatar/64",[[self.delegate getUserNames]objectAtIndex:0]];
  dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
  dispatch_async(queue,^{
   UIGraphicsBeginImageContextWithOptions(CGSizeMake(32,32),YES,[[UIScreen mainScreen]scale]);
   [[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]]drawInRect:CGRectMake(0,0,32,32)];
   avatar=UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();
   cell.imageView.image=avatar;
   [cell layoutSubviews];
  });   
 }else{
  cell.imageView.image=avatar;[cell layoutSubviews];
 }
 cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
}

できた。あとは設定項目を潰していって、timelineモードの検討。これはtableでやったほうがいいのかどうかわからなくなってきた。ちょっと考える。

2012年8月30日木曜日

cookie

アカウント登録時にOAuthで認証し、またすぐ別のアカウントを追加しようとすると前のアカウントでログインされた状態で許可を求める画面になることがある。そのページからOAuthの説明ページに行けばログアウトできるがそんなん多分だれも気付かないしカッコ悪い。アラートだすのもやっぱり間抜けな気がするし、ログアウトページに行ってから再度request tokenのページに飛ぶようにしたがこれもあまり美しくない。
ということで、cookie消せばいいんじゃね?と思って調べた。

このあたり。
Where are an UIWebView's cookies stored?
How to delete all cookies of UIWebView?

あとで調べる。

2012年8月29日水曜日

Preferencesとマルチアカウント

Preferences画面を作成。
UINavigationControllerとUITableViewControllerでちゃかちゃか作っていく。
このへんは参考書籍もサイトも充実してる(しすぎてて逆に探しにくいくらい)ので、デリゲートメソッドを使うやり方に戸惑いながらもハマってしまうことは少ないかな。先人たちに感謝。
このテーブルの仕様は慣れてくると非常によく考えられた汎用性の高い仕組みだと思う。


で、tumblrのマルチアカウント対応を検討中。

■アカウント登録されてない場合:
・起動時に最初にアラート、登録したいのとは別のアカウントでログインしてたらログアウトを促す。
・userdefaultの中の該当する配列(以下activeAccArr)見て空ならgetAToken貼る。
・帰ってきたらラップしたYESを配列に入れてUDに書く。(getATokenの中でやる)
・setATokenしてアカウントのusernameを取ってshortnameに入れる。
・reqDBする。

■アカウントを追加する場合:
・activeAccArrのcount分usernameとってAccounts配列に入れる。
・Accounts配列を展開して(DL完了前のNULLに注意)activeAccArrのYESの場所のAccounts配列の要素をcheckMarkする。
・追加ボタンで最初にアラート、登録したいのとは別の別アカウントでログインしてたらログアウトを促す。
・getAToken貼って、帰ってきたらactiveAccArrを全部NOにしてラップしたYESを最後に追加してUDに書く。
・setATokenしてアカウントのusernameを取ってshortnameに入れる。(変更Flag立てる?)
・変更Flag立ってたらPrefでdoneでreqDBする。

■アカウントを削除する場合:
・activeAccArrのcount分usernameとってAccounts配列に入れる。
・Accounts配列を展開して(DL完了前のNULLに注意)activeAccArrのYESの場所のAccounts配列の要素をcheckMarkする。
・削除されたrowのactiveAccArrを削除し、それがYESだった場合は最初の要素をYESにしてUDに書く。
・setATokenしてアカウントのusernameを取ってshortnameに入れる。(変更Flag立てる?)
・変更Flag立ってたらPrefでdoneでreqDBする。

■デフォルトアカウントを変更する場合:
・activeAccArrのcount分usernameとってAccounts配列に入れる。
・Accounts配列を展開して(DL完了前のNULLに注意)activeAccArrのYESの場所のAccounts配列の要素をcheckMarkする。
・選択されたら元のYESをNOにして選択されたrowのactiveAccArrをYESにしてUDに書く。
・setATokenしてアカウントのusernameを取ってshortnameに入れる。(変更Flag立てる?)
・変更Flag立ってたらPrefでdoneでreqDBする。


こんな感じか。usernameを度々読みに行くのがなんか気持ち悪いが最新のものを読むためには仕方ない。あとはこの自分にしかわからない日本語を自分にしかわからないObj-c語に翻訳するだけだ。
楽しい。

2012年8月24日金曜日

audioと虫取り

audioはMPMoviePlayerControllerとかも調べたけどどうもしっくりこないので結局AudioStreamerを入れた。バッチリ動いてる。すばらしい。mattgallagher氏に感謝。これで全type思ってた感じのインターフェイスができたかな。よくぞここまで来たものだ。

あとはUIWebViewで読込完了前に閉じてもっかい開いたらdeallocatedですよーと怒られたり、ポスト移動してたら何故か前のビューの残像が残ってたりといった虫取りに結構ハマってた。

気づき:
・UIWebViewではremoveする前にstopLoadingを入れないと次にUIWebViewをaddSubした瞬間に落ちる。
・ポスト移動のボタンを連打するとanimationのcompletionが呼ばれるときにremoveする対象のviewがずれる。

両方共気づくのに数時間を要した。次は設定画面かな。UINavigationViewControllerとUITableViewControllerの組み合わせでちゃかちゃか作っていくのかな。

2012年8月18日土曜日

スワイプ移動、photo再読込、linkとaudioリベンジ

ここ何日かでいろいろ捗った。

・スワイプ移動
でポストを移動できるようになったら、やっぱりシュルっとビジュアル的にスライドしないとなんか気持ち悪いのでやってみた。なかなか良い感じ。
- (void)npost{
 sw=self.view.bounds.size.width,sh=self.view.bounds.size.height;
 if([[pvlayer.view viewWithTag:now]isEqual:[pvArr lastObject]]) return;
 CGRect oldf=[pvlayer.view viewWithTag:now++].frame;oldf.origin.x-=sw;[self refself];
 [pvlayer.view addSubview:[pvArr objectAtIndex:now-1]];[self na];[self mkCaption];[self refpost];
 CGRect nowf=[pvlayer.view viewWithTag:now].frame;nowf.origin.x+=sw;[pvlayer.view viewWithTag:now].frame=nowf;nowf.origin.x-=sw;
 [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut
      animations:^{[pvlayer.view viewWithTag:now-1].frame=oldf;[pvlayer.view viewWithTag:now].frame=nowf;}
      completion:^(BOOL finished){[[pvlayer.view viewWithTag:now-1]removeFromSuperview];}];
 if(now>100) [pvArr replaceObjectAtIndex:now-101 withObject:[NSNull null]];
 if(now+20>[contArr count]){
  if(imout||loadflg)return;
  if(target==@"dshbrd"&&offset+limit>250) offsetflg=NO;
  if(offsetflg){
   offsinceval=[NSString stringWithFormat:@"%d",offset+=limit];
  }else{
   since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1));
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];
  }
  if(!loadflg)[self reqDB];
 }
}
- (void)ppost{
 sw=self.view.bounds.size.width,sh=self.view.bounds.size.height;
 if(now==1)return;
 if([[pvArr objectAtIndex:now-2]isEqual:[NSNull null]]){
  UIAlertView *lost=[[UIAlertView alloc]initWithTitle:@"DEAD END"
             message:@"sorry, but only 100 previous posts are retained for preventing a crash caused by insufficient memory."
               delegate:nil cancelButtonTitle:nil otherButtonTitles:@"oh well.",nil];
  [lost show];[self mkCtrlr:1];return;
 }
 CGRect oldf=[pvlayer.view viewWithTag:now--].frame;oldf.origin.x+=sw;[self refself];
 [pvlayer.view addSubview:[pvArr objectAtIndex:now-1]];[self na];[self mkCaption];[self refpost];
 CGRect nowf=[pvlayer.view viewWithTag:now].frame;nowf.origin.x-=sw;[pvlayer.view viewWithTag:now].frame=nowf;nowf.origin.x+=sw;
 [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut
      animations:^{[pvlayer.view viewWithTag:now+1].frame=oldf;[pvlayer.view viewWithTag:now].frame=nowf;}
      completion:^(BOOL finished){[[pvlayer.view viewWithTag:now+1]removeFromSuperview];}];
 if(now==1)[bprev removeFromSuperview]; 
 [[pvlayer.view viewWithTag:now--] removeFromSuperview];[self refself];
 [pvlayer.view addSubview:[pvArr objectAtIndex:now-1]];
 [self na];[self mkCaption];[self refpost];
 if(now==1)[bprev removeFromSuperview];
}

・photo再読込
SpeedLimitを使ってtimeoutさせてシングルタップで読込ミスったphotoを再読込したりalt_sizeなphotoの場合はoriginalを読むようにしたりした。これはコード書くまでもないかな。

・linkの当たり判定
よりリンクらしく、ボタンじゃなくてCoreText自体をクリックしたらページを開くようにしたくなって当たり判定を調べて入れた。tap時の座標をとってもいいけどとりあえずマーカー的にuiimageviewを貼ってそいつにgesture取らすようにした。
- (id)initWithFrame:(CGRect)frame string:(NSString *)string url:(NSString *)url_
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
  url=url_;
  NSMutableDictionary *attrDic=[NSMutableDictionary dictionaryWithCapacity:0];
  CTFontRef ctFont=CTFontCreateWithName((__bridge CFStringRef)@"Helvetica",24, NULL);
  [attrDic setObject:(__bridge id)ctFont forKey:(NSString *) kCTFontAttributeName];
  [attrDic setObject:(__bridge id)[UIColor blueColor].CGColor forKey:(NSString *)kCTForegroundColorAttributeName];
  [attrDic setObject:@((int)kCTUnderlineStyleSingle) forKey:(NSString *)kCTUnderlineStyleAttributeName];
  NSMutableAttributedString *linkedText=[[NSMutableAttributedString alloc]initWithString:string attributes:attrDic];
  CGMutablePathRef path=CGPathCreateMutable();
  CTFramesetterRef framesetter=CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)linkedText);;
  CGPathAddRect(path,NULL,self.bounds);
  ctFrame=CTFramesetterCreateFrame(framesetter,CFRangeMake(0,[linkedText length]),path,NULL);
  CFArrayRef lines=CTFrameGetLines(ctFrame);
  for(int i=0;i<CFArrayGetCount(lines);i++){
   CGPoint orig;CGFloat ascent,descent,leading,width;
   CTFrameGetLineOrigins(ctFrame,CFRangeMake(i,1),&orig);
   width=CTLineGetTypographicBounds(CFArrayGetValueAtIndex(lines,i),&ascent,&descent,&leading);
   UIImageView *blank=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"img_black"]];
   [blank setFrame:CGRectMake(0,self.bounds.size.height-orig.y-ascent+descent,width,ascent+descent)];blank.alpha=0;
   self.userInteractionEnabled=YES;blank.userInteractionEnabled=YES;
   UITapGestureRecognizer *st=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(openPage:)];
   st.numberOfTapsRequired=1;
   [blank addGestureRecognizer:st];
   [self addSubview:blank];
  }
  CGPathRelease(path);
  self.backgroundColor=[UIColor clearColor];
    }
    return self;
}
-(void)openPage:(UITapGestureRecognizer *)gesture{
 openPage=[[OpenPage alloc]initWithURL:url];
 [self.superview.superview.superview addSubview:openPage.view];
}

・audio_url
ここまで来たらaudioもリンクじゃなくて直接再生したくなって一度挫折してるけどもっかい調べてみたらなんか行けそうな記事があったので再挑戦中。ただmp3のURLからストリーミングさせるのが結構大変そう。ちょっとがんばる。

2012年8月14日火曜日

SpeedLimit

それから、今まで贅沢にもoriginal sizeのphotoを読み込んで使っていたが、3G環境をシミュレートしてどれくらいのストレスになるか調べてみた。いろいろ探してSpeedLimitなるものを入れて3Gを模してみたが、はっきり言ってoriginal sizeでは話にならない。60secでは読み込みが終わらん。こりゃまずいということで、tumblrのAPIから返ってくるalt_sizesを使ってみた。1,2段階低い解像度なら行けそうなので、5段階くらいを任意に選択できるようにして、original size以外ならダブルタップでoriginalを読むようにしてみた。
あと、スワイプでのpost移動もUISwipeGestureRecognizer使ったら簡単にいけた。着実に完成に近づいてるが、夏休み終わるまでの完了は無理かなー。

delegate

reblogメッセージはUIImageViewのサブクラスを作ってそいつが自らreblogのrequestを投げてresponseが返ってきたら自分の表示位置を示すシリアル番号(配列の添字)をdelegateに投げて表示位置をリセットして自分をフェードアウトしてremovefromsuperviewする形に変更。まだスマートかな。

rootのvc
- (void)rblg{
 int i;
 for(i=0;i<[rposArr count];i++){
  if([[rposArr objectAtIndex:i]isEqual:[NSNull null]]){
   [rposArr replaceObjectAtIndex:i withObject:@"used"];break;
  }
 }
 rblk=[[ReblogLike alloc]initWithFrame:CGRectMake(0,(sh-16)/2+i*16,sw,16) rorl:@"reblog" rid:[idArr objectAtIndex:now-1] rkey:[rblgkArr objectAtIndex:now-1] rpos:i
            num:now type:[typeArr objectAtIndex:now-1] cons:_consumer token:_accessToken
            url:[NSURL URLWithString:[NSString stringWithFormat:@"http://api.tumblr.com/v2/blog/%@.tumblr.com/post/reblog",shtname]]];
 rblk.delegate=self;
 [self.view addSubview:rblk];
}
-(void)rposReset:(int)rpos{
 [rposArr replaceObjectAtIndex:rpos withObject:[NSNull null]];
}
ReblogLike.h
@protocol ReblogLikeDelegate 
-(void)rposReset:(int)rpos;
@end

@interface ReblogLike : UIImageView{
 int rpos;
 iddelegate;
}

@property(strong,nonatomic)iddelegate;

- (id)initWithFrame:(CGRect)frame rorl:(NSString *)rorl rid:(NSNumber *)rid rkey:(NSString *)rkey rpos:(int)rpos_ num:(int)num type:(NSString *)type
      cons:(OAConsumer *)cons token:(OAToken *)token url:(NSURL *)url;

@end
ReblogLike.m
- (id)initWithFrame:(CGRect)frame rorl:(NSString *)rorl rid:(NSNumber *)rid rkey:(NSString *)rkey rpos:(int)rpos_
    num:(int)num type:(NSString *)type cons:(OAConsumer *)cons token:(OAToken *)token url:(NSURL *)url
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
  sw=self.bounds.size.width,sh=self.bounds.size.height;
  rpos=rpos_;
  self.image=[UIImage imageNamed:@"img_black"];self.autoresizingMask=15;
  UIView *rBase=[[UIView alloc]initWithFrame:CGRectMake(0,0,sw,16)];rBase.autoresizingMask=13;
  title=[[UILabel alloc]init];
  title.text=[NSString stringWithFormat:[rorl isEqual:@"reblog"]?@"REBLOG-ing...":@"LIK-ing..."];
  title.font=sf(13);title.textColor=[UIColor whiteColor];title.backgroundColor=[UIColor clearColor];
  CGFloat titlewidth=[title.text sizeWithFont:title.font constrainedToSize:CGSizeMake(sw,15) lineBreakMode:UILineBreakModeTailTruncation].width;
  UILabel *info=[[UILabel alloc]init];
  info.text=[NSString stringWithFormat:@"(number %d - %@)",num,type];
  info.font=sf(10);info.textColor=[UIColor whiteColor];info.backgroundColor=[UIColor clearColor];
  CGFloat infowidth=[info.text sizeWithFont:info.font constrainedToSize:CGSizeMake(sw,12) lineBreakMode:UILineBreakModeTailTruncation].width;
  title.frame=CGRectMake(ceilf((sw-(titlewidth+infowidth))/2),0,titlewidth,15);
  info.frame=CGRectMake(ceilf(title.frame.origin.x+titlewidth+10),2,infowidth,12);
  [rBase addSubview:title];[rBase addSubview:info];
  [self addSubview:rBase];//return self;

  OAMutableURLRequest *request=[[OAMutableURLRequest alloc] initWithURL:url consumer:cons token:token realm:nil signatureProvider:nil];
  [request setHTTPMethod:@"POST"];
  NSMutableArray *prmt=[NSMutableArray arrayWithObject:[[OARequestParameter alloc]initWithName:@"id" value:[NSString stringWithFormat:@"%@",rid]]];
  [prmt addObject:[[OARequestParameter alloc]initWithName:@"reblog_key" value:rkey]];
  //[prmt addObject:[[OARequestParameter alloc]initWithName:@"comment" value:@"comment"]];
  [request setParameters:prmt];
  OADataFetcher *fetcher=[[OADataFetcher alloc] init];
  [fetcher fetchDataWithRequest:request delegate:self
     didFinishSelector:@selector(rblgTicket:didFinishWithData:)
       didFailSelector:@selector(rblgTicket:didFailWithError:)];

    }
    return self;
}
- (void)rblgTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 if([[bd valueForKeyPath:@"meta.status"]intValue]==201){
  title.text=@"REBLOG-ed.";
  [UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionCurveEaseIn
       animations:^{self.alpha=0;}
       completion:^(BOOL finished){
        [self.delegate rposReset:rpos];
        [self removeFromSuperview];
       }];
  //NSLog(@"rebloged.");
 }
 else NSLog(@"rblg failed.");
}
- (void)rblgTicket:(OAServiceTicket *)ticket didFailWithError:(NSError *)error{
 NSLog(@"rblg Error! %@",error);
}

できた。
delegateも難しく考えてたけど、単にずーっとやりたかったallocate元のインスタンスにメッセージを投げる方法だと考えればどーってことない。というか非常に使える。むしろ必須。わざわざnotification使ってたaccessTokenの取得ルーチンもdelegateに書き換えた。

2012年8月12日日曜日

ずっこけ

reblog中及び完了のメッセージングを、どのポストを対象にしてるかわかって尚且つ並列で処理中であることがわかるように頑張ってたが、reblog完了時のレスポンスに含まれるidが、reblog元のポストのidではなく作成後の新ポストのidであることが最後の最後に分かって脱力。tumblrAPIの仕様はなんかちょっと抜けてる気がする。わざと?
他の手立てを考えてみたが素人に豊富なオプションが有るわけもなく、散々悩んだ挙句メッセージングをなんかのサブクラスにしてdelegateとしてレスポンスを受けるようにしてみることを思いついたからとりあえず寝る。うまくいくかは分からん。やっぱりプログラミングはおもしろい。

2012年8月10日金曜日

reblog!

setHTTPMethodの位置が悪くてnot authorizedとか出て冷や汗かいたりidのフォーマッティングがうまく行かずにinvalidとか言われたりしたけど、とりあえずリブログ完成。

-(void)reqUInfo{
 NSURL *url=[NSURL URLWithString:@"http://api.tumblr.com/v2/user/info"];
 OAMutableURLRequest *request=[[OAMutableURLRequest alloc] initWithURL:url consumer:_consumer token:_accessToken realm:nil signatureProvider:nil];
 [request setHTTPMethod:@"POST"];
 OADataFetcher *fetcher=[[OADataFetcher alloc] init];
 [fetcher fetchDataWithRequest:request delegate:self
    didFinishSelector:@selector(reqUInfoTicket:didFinishWithData:)
      didFailSelector:@selector(reqUInfoTicket:didFailWithError:)];
}
- (void)reqUInfoTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 shtname=[bd valueForKeyPath:@"response.user.name"];
}
- (void)reqUInfoTicket:(OAServiceTicket *)ticket didFailWithError:(NSError *)error{
 NSLog(@"reqUInfo Error! %@",error);
}

- (void)rblg{
 NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:@"http://api.tumblr.com/v2/blog/%@.tumblr.com/post/reblog",shtname]];
 OAMutableURLRequest *request=[[OAMutableURLRequest alloc] initWithURL:url consumer:_consumer token:_accessToken realm:nil signatureProvider:nil];
 [request setHTTPMethod:@"POST"];
 NSString *idstr=[NSString stringWithFormat:@"%lld",[[idArr objectAtIndex:now-1]longLongValue]];
 NSMutableArray *prmt=[NSMutableArray arrayWithObject:[[OARequestParameter alloc]initWithName:@"id" value:idstr]];
 [prmt addObject:[[OARequestParameter alloc]initWithName:@"reblog_key" value:[NSString stringWithFormat:@"%@",[rblgkArr objectAtIndex:now-1]]]];
 //[prmt addObject:[[OARequestParameter alloc]initWithName:@"comment" value:@"comment"]];
 [request setParameters:prmt];
 OADataFetcher *fetcher=[[OADataFetcher alloc] init];
 [fetcher fetchDataWithRequest:request delegate:self
    didFinishSelector:@selector(rblgTicket:didFinishWithData:)
      didFailSelector:@selector(rblgTicket:didFailWithError:)];
}
- (void)rblgTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 if([[bd valueForKeyPath:@"meta.status"]intValue]==201){
  NSLog(@"rebloged.");
 }
 else NSLog(@"failed.");
}
- (void)rblgTicket:(OAServiceTicket *)ticket didFailWithError:(NSError *)error{
 NSLog(@"rblg Error! %@",error);
}

できた。あとはメッセージング処理をやるか。

2012年8月9日木曜日

回転終了

やっと終了。かなり長い戦いだった。

・type=audio:autoresizingMask=45の置き場所によって動作が変わって手こずった。
・type=video:UIWebViewから映像を再生してその間に回転してMoviePlayerをdoneするとstatusBar分下にずれる。まさにこれと同じ状況:After playing HTML5 video, status bar white space remains
結局回転時にrootのvcを[UIScreen mainScreen].boundsでsetFrameし直したらいけた。ここにたどり着くのに死ぬほど苦労した。MoviePlayerから抜けた瞬間をnotificationさせて-20pxさせるもcaptionは除外したいとか考えてたらうわーってなったけど解決策はいつも単純。
・type=answer:まあこれはそんなに苦労はなかった。UIScrollViewにbaseとなるUIViewを貼ってたら画面の下半分でしかスクロールのタッチを検出しなくて悩んでたけど直接UIScrollViewにパーツ貼ったら解決した。

これでsinglepostviewの見栄えは完成。なかなか満足。timeline行く前に、いよいよ、reblogいくかー。OAuthの使い方だいぶ抜けてるから復習から。

昨日は來來亭@北巽でチャーハン定食麺堅め醤油濃い目ネギ背脂多め、今日はマッチョ@千日前でいつもの。ここは野菜マシマシがほかより少ない。でもうまかった。

2012年8月2日木曜日

回転は続く

次はchat。UILabelを使ってたけどUITextViewに変更。ていうかなんでUILabel使ったんやろ。縦幅制限したい場合は→UILabel、それ以外はUITextViewでいいんじゃないの。ただautoresizingMaskでtextのframeは変わるけどdialogueごとに入れてる線のyは可変にはできないから再描画or2viewの切替が必要。結局link同様切替にした。answerも同じ理由で切替が必要かな。とりあえずchatのみ修正。回転検知後のメソッドもちょっと変えた。

SinglePost2.m
}else if([type isEqualToString:@"chat"]){
  if(![cont isEqual:[NSNull null]]){
   for(int pl=0;pl<2;pl++){
    CGFloat npy=0;int j=1;
    UIScrollView *chatScr=[[UIScrollView alloc]initWithFrame:CGRectMake(0,30,pl?sh:sw,(pl?sw:sh)-50)];[self.view addSubview:chatScr];
    UIView *chatBase=[[UIView alloc]initWithFrame:CGRectMake(0,0,pl?sh:sw,pl?sw:sh)];
    for(id dialogue in cont){
     UITextView *npv=[[UITextView alloc]initWithFrame:CGRectMake(20,npy,(pl?sh:sw)-40,0)];
     npv.text=[NSString stringWithFormat:@"%@%@",[dialogue objectForKey:@"label"],[dialogue objectForKey:@"phrase"]];npv.font=sf(15);
     npv.textColor=[UIColor blackColor];npv.backgroundColor=[UIColor clearColor];npv.editable=NO;[chatBase addSubview:npv];
     CGRect f=npv.frame;f.size.height=npv.contentSize.height;npv.frame=f;
     npy+=npv.contentSize.height+10;j++;
     UIImage *line=[UIImage imageNamed:@"gray"];
     UIImageView *linev=[[UIImageView alloc]initWithImage:line];linev.alpha=0.7;[linev setFrame:CGRectMake(25,npy-8,(pl?sh:sw)-50,4)];
     UIImage *fuki=[UIImage imageNamed:j%2?@"fuki":@"fuki2"];
     UIImageView *fukiv=[[UIImageView alloc]initWithImage:fuki];fukiv.alpha=0.7;
     [fukiv setFrame:CGRectMake(j%2?5:(pl?sh:sw)-fuki.size.width-5,npy-8-fuki.size.height,fuki.size.width,fuki.size.height)];
     [chatBase addSubview:linev];[chatBase addSubview:fukiv];[chatBase setTag:(pl?4000000:3000000)+now];
     [chatScr addSubview:chatBase];
     if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){if(!pl)chatBase.hidden=YES;}
     else if(pl)chatBase.hidden=YES;
    }
    [chatScr setContentSize:CGSizeMake((pl?sh:sw)-40,npy)];
   }
  }
 }
rootのvc
-(void)refpost{
 int plid;
 if([[typeArr objectAtIndex:now-1]isEqual:@"link"]) plid=1000000;
 else if([[typeArr objectAtIndex:now-1]isEqual:@"chat"]) plid=3000000;
 if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
  [self.view viewWithTag:plid+now].hidden=YES;
  [self.view viewWithTag:plid+1000000+now].hidden=NO;
 }else{
  [self.view viewWithTag:plid+now].hidden=NO;
  [self.view viewWithTag:plid+1000000+now].hidden=YES;
 }
}
できた。ていうかここまで書いて気付いたけどUITextViewに線と吹き出し貼ったらautoresizingMask効くかな?あとで確認する。残すはaudio,video,answer。いけるやろ。
最近ラーメンは昼じゃなく夜食べるようにしたけど結局 並野菜マシマシ背脂ちょいマシカラメ@マッチョになる。今日も行ってきた。取り憑かれてんのかな。

2012年8月1日水曜日

基礎

coretextをdrawしたUILabelを回転に対応させた。

・縦横それぞれのUIViewを作ってUILabelにaddSubView。
・各viewにtagを貼る。縦は1000000+ポストNo.、横は2000000+ポストNo.。百万件掘る人がいたら再考する。
・rootのvcで回転を検知するたびに片方のviewのhiddenをYESにする。
・ボタンのcgrectを参照してrootのvcから縦横2つのviewに貼るのは面倒かつ醜いのでpostのviewが自前で貼るようにして、メッセージをrootのvcで受けるようにdelegateを使う。(初挑戦)
・viewDidLoadでself.view.boundsが呼ばれるたびに現状のウィンドウを取得すると思ってたらviewDidAppearでやらんとダメということを知ったので改修。

とりあえずこれで動いてる様子。

rootのvc
- (void)mkSinglePostView{
 for(int i=[pvArr count];i<[contArr count];i++){
  postv=[[SinglePost2 alloc]init];postv.delegate=self;
  [postv constructWithtype:[typeArr objectAtIndex:i] cont:[contArr objectAtIndex:i] bnamet:[bnameArr objectAtIndex:i] now:i+1];
  [naxArr addObject:postv.nax];
  if(postv.linkurl)[openArr insertObject:postv.linkurl atIndex:i];
  [postv.view setTag:i+1];
  [pvArr addObject:postv.view];
 }
 [pvlayer.view addSubview:[pvArr objectAtIndex:now-1]];[pvlayer.view setFrame:self.view.bounds];
 if(!now_all) now_all=[[UILabel alloc]init];[self na];
 if(!caption) caption=[[UILabel alloc]init];[self mkCaption];
}

-(void)reflink{
 if([[typeArr objectAtIndex:now-1]isEqual:@"link"]){
  if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){
   [self.view viewWithTag:1000000+now].hidden=YES;[self.view viewWithTag:2000000+now].hidden=NO;
  }else{
   [self.view viewWithTag:1000000+now].hidden=NO;[self.view viewWithTag:2000000+now].hidden=YES;
  }
 }
}
SinglePost2.m
}else if([type isEqualToString:@"link"]){
  _linkurl=[cont objectAtIndex:1];
  for(int pl=0;pl<2;pl++){
   Link *link=[[Link alloc]initWithFrame:CGRectMake(10,30,(pl?sh:sw)-20,(pl?sw:sh)-80)
             string:[[cont objectAtIndex:0]isEqual:[NSNull null]]||[[cont objectAtIndex:0]isEqual:@""]?@"(no text)":[cont objectAtIndex:0]];
   UIImage *openlinkimg=[UIImage imageNamed:@"openlink"],*openlinkimg_on=[UIImage imageNamed:@"openlink_on"];
   UIButton *open=[UIButton buttonWithType:UIButtonTypeCustom];
   [open setImage:openlinkimg forState:UIControlStateNormal];[open setImage:openlinkimg_on forState:UIControlStateHighlighted];
   [open addTarget:delegate action:@selector(openPage) forControlEvents:UIControlEventTouchUpInside];
   [open setFrame:CGRectMake(10,(pl?sw:sh)-link.origin.y-30,openlinkimg.size.width,openlinkimg.size.height)];
   UIView *lv=[[UIView alloc]initWithFrame:CGRectMake(0,0,pl?sh:sw,pl?sw:sh)];
   [lv addSubview:link];[lv addSubview:open];[lv setTag:(pl?2000000:1000000)+now];
   if(UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)){if(!pl)lv.hidden=YES;}
   else if(pl)lv.hidden=YES;
   [self.view addSubview:lv];
  }
}
OpenPage.m
- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view.
}

-(void)viewDidAppear:(BOOL)animated{
 page=[[UIWebView alloc]initWithFrame:self.view.bounds];page.scalesPageToFit=YES;page.delegate=self;page.autoresizingMask=18;[self.view addSubview:page];
 toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, self.view.bounds.size.height-36, self.view.bounds.size.width,36)];
 toolbar.tintColor=[UIColor blackColor];toolbar.translucent=YES;
 [self.view addSubview:toolbar];toolbar.autoresizingMask=42;
 back=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(back)];
 forward=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFastForward target:self action:@selector(forward)];
 done=[[UIBarButtonItem alloc]initWithTitle:@"done" style:UIBarButtonItemStyleDone target:self action:@selector(killme)];
 NSArray *buttons=[[NSArray alloc]initWithObjects:done,back,forward,nil];
 [toolbar setItems:buttons animated:YES];
 [page loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]]; 
}
できた。あとはchat,audio,video,answerの回転対応。余裕やと思ってたけどまだまだベーシックな部分で理解と知識が足りてない。

2012年7月31日火曜日

coretextの回転

また躓いた。Linkは青字の下線にしたいからcoretextでちまちま作ってたが、contentModeをいじってもサイズがピッタリにならない。力技で再描画するしかないのか。はたまた私が無知なだけか。
考えられるオプションは3つかな。

・回転を検知するたびに新しいviewを作って上書く。
・最初からportraitとlandscapeの2つのviewを作って回転するたびに片方をhiddenする。
・UIWebViewでHTMLを表示させる。

LTCoreTextでも回転への対応は2番目を使ってるようなので、これで試してみる。でもviewのhiddenプロパティにアクセスするやり方がすごいuglyなのしか思いつかん。入れ子のviewにtag使って触るとか。対症療法的やな。

2012年7月30日月曜日

一段落

とりあえずtiling routineは完成。ウィンドウの横幅固定で並べてた時には起こらなかった問題ができるだけオリジナルに近い横幅を保持するように変更した途端に溢れてきてそれを一個一個潰した。要はcurrentな画像とpreviousな画像の横幅を小さい方に揃える際に、previousを縮小する場合に考慮すべき値が漏れ続けてた。特殊相対性理論を一般相対性理論に昇華させる苦労がちょっとだけ(百億分の1くらい)わかった気がする。

Photo2.m
- (void)makeImage:(NSMutableData *)rdata{
 if(multiFlg){
  UIImage *img=[UIImage imageWithData:rdata];
  [imgArr addObject:rdata];
  [imgArr addObject:nwf(img.size.width)];[imgArr addObject:nwf(img.size.height)];[imgArr addObject:nwf(img.size.width/img.size.height)];
  if([imgArr count]/4<[photourl count]){
   imgnum++;[self loadPhoto];
  }
  else{ //tiling routine
   CGFloat curx=0,cury=0,neww=0,newh=0;
   for(int j=0;j<[imgArr count]/4;j++){
    if([[imgArr objectAtIndex:j*4+3]floatValue]<sr&&[imgArr count]/4>j+1){
     int over3=2;
     CGFloat firstw=ofv(imgArr,j*4+1),firsth=ofv(imgArr,j*4+2),nextw=ofv(imgArr,(j+1)*4+1),nexth=ofv(imgArr,(j+1)*4+2);
     newh=firsth<nexth?firsth:nexth;
     CGFloat row=firstw*newh/firsth+nextw*newh/nexth;
     while (row/newh<sr&&[imgArr count]/4>j+over3){
      nextw=ofv(imgArr,(j+over3)*4+1),nexth=ofv(imgArr,(j+over3)*4+2);
      if(nexth<newh){row*=nexth/newh;newh=nexth;}
      row+=nextw*newh/nexth;
      over3++;
     }
     j+=1+(over3-2);
     if(neww){
      if(row<neww){cury*=row/neww;neww=row;}
      else newh*=neww/row;
     }
     else neww=row;
     UIGraphicsBeginImageContext(CGSizeMake(neww,cury+newh));
     if(mixedimg) [mixedimg drawInRect:CGRectMake(0,0,neww,mixedimg.size.height*neww/mixedimg.size.width)];
     int k=1+(j-over3);
     while(k<j+1){
      UIImage *img=[[UIImage alloc]initWithData:[imgArr objectAtIndex:k*4]];
      CGFloat prtw=img.size.width*newh/img.size.height;
      if([[photourl objectAtIndex:k]hasSuffix:@"gif"]){
       if(!gifArr) gifArr=[[NSMutableArray alloc]initWithCapacity:0];
       [gifArr addObject:[imgArr objectAtIndex:k*4]];
       [gifArr addObject:nwf(curx)];[gifArr addObject:nwf(cury)];[gifArr addObject:nwf(prtw)];[gifArr addObject:nwf(newh)];
      }else [img drawInRect:(CGRectMake(curx,cury,prtw,newh))];
      curx+=prtw;k++;
     }
     cury+=newh;curx=0;
     mixedimg=UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
    }else{
     UIImage *img=[[UIImage alloc]initWithData:[imgArr objectAtIndex:j*4]];
     if(neww){if(img.size.width<neww){
      cury*=img.size.width/neww;
      if(gifArr){
       for(int m=0;m<[gifArr count];m++){
        if(m%5)[gifArr replaceObjectAtIndex:m withObject:nwf(ofv(gifArr,m)*img.size.width/neww)];
       }
      }
      neww=img.size.width;
     }}
     else neww=img.size.width;
     newh=img.size.height*neww/img.size.width;
     UIGraphicsBeginImageContext(CGSizeMake(neww,cury+newh));
     if(mixedimg) [mixedimg drawInRect:CGRectMake(0,0,neww,mixedimg.size.height*neww/mixedimg.size.width)];
     if([[photourl objectAtIndex:j]hasSuffix:@"gif"]){
      if(!gifArr) gifArr=[[NSMutableArray alloc]initWithCapacity:0];
      [gifArr addObject:[imgArr objectAtIndex:j*4]];
      [gifArr addObject:nwf(0)];[gifArr addObject:nwf(cury)];[gifArr addObject:nwf(neww)];[gifArr addObject:nwf(newh)];
     }else [img drawInRect:(CGRectMake(0,cury,neww,newh))];
     mixedimg=UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
     cury+=newh;
    }
   }
   [ai removeFromSuperview];
   self.image=mixedimg;
   if(gifArr){
    for(int l=0;l<[gifArr count]/5;l++){
     CGFloat rect[4]={ofv(gifArr,l*5+1),ofv(gifArr,l*5+2),ofv(gifArr,l*5+3),ofv(gifArr,l*5+4)};
     UIImage *gif=[self mkGifWithData:[gifArr objectAtIndex:l*5] rect:rect];
     UIImageView *gifv=[[UIImageView alloc]initWithImage:gif];
     gifv.frame=self.bounds;gifv.contentMode=UIViewContentModeScaleAspectFit;gifv.autoresizingMask=18;
     [self addSubview:gifv];
    }
   }
   [self abort];
  }
 }
 else{
  [ai removeFromSuperview];
  if([[photourl objectAtIndex:0]hasSuffix:@"gif"]) self.image=(UIImage *)[self mkGifWithData:rdata rect:nil];
  else self.image=[UIImage imageWithData:rdata];
  [self abort];
 }
}

-(UIImage *)mkGifWithData:(NSData *)rdata rect:(CGFloat *)rect{
 CGImageSourceRef src=CGImageSourceCreateWithData((__bridge CFDataRef)rdata,NULL);
 NSDictionary *prop=[(__bridge NSDictionary*)CGImageSourceCopyProperties(src,NULL) objectForKey:(NSString*)kCGImagePropertyGIFDictionary];
 size_t count=CGImageSourceGetCount(src);
 NSMutableArray *GIFimages=[NSMutableArray array];
 for(size_t i=0;i<count;i++){
  CGImageRef image=CGImageSourceCreateImageAtIndex(src,i,NULL);
  if(rect){
   UIGraphicsBeginImageContext(CGSizeMake(self.image.size.width,self.image.size.height));
   [[UIImage imageWithCGImage:image] drawInRect:(CGRectMake(rect[0],rect[1],rect[2],rect[3]))];
   UIImage *gif=UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();
   [GIFimages addObject:gif];
  }else [GIFimages addObject:[UIImage imageWithCGImage:image]];
  CGImageRelease(image);
 }
 NSTimeInterval delay=[[prop objectForKey:(NSString*)kCGImagePropertyGIFDelayTime] doubleValue];
 //if (!delay) delay=(1.0f/12.0f)*count;
 if (!delay) delay=1.2;
 CFRelease(src);
 return (UIImage *)[UIImage animatedImageWithImages:GIFimages duration:delay];
}

できた。これであらゆるケースに対応できるはず。

と思ってたらlink,audioでボタンを押すとpostのUIViewControllerがdeallocされてることに気付いた。UIViewの時はいけてた(はず)のに。strongで宣言してviewをNSMutableArrayに入れといても次のpostを同じivarでallocしたらpreviousなpostのivarはdeallocされる。viewが生きてるから気づかんかったけど、まあ当然っちゃ当然の振る舞いなんだろう。無理やりcopyしたりするのもややこしいしメモリも気になるのでボタンをrootのvcからpostのviewに貼ってイベントの受取をrootのvcになるよう変更した。

tManCtrlr.m
- (void)mkSinglePostView{
 for(int i=[pvArr count];i<[contArr count];i++){
  postv=[[SinglePost2 alloc]init];
  [postv constructWithtype:[typeArr objectAtIndex:i] cont:[contArr objectAtIndex:i] bnamet:[bnameArr objectAtIndex:i]];
  [naxArr addObject:postv.nax];
  if(postv.open){
   [openArr insertObject:[postv.open objectAtIndex:1] atIndex:i];
   UIImage *opimg,*opimg_on;
   if([[postv.open objectAtIndex:0]isEqual:@"link"]){
    opimg=[UIImage imageNamed:@"openlink"];opimg_on=[UIImage imageNamed:@"openlink_on"];
   }
   else if([[postv.open objectAtIndex:0]isEqual:@"audio"]){
    opimg=[UIImage imageNamed:@"listen"];opimg_on=[UIImage imageNamed:@"listen_on"];    
   }
   UIButton *open=[UIButton buttonWithType:UIButtonTypeCustom];//move.alpha=0.7;
   [open setImage:opimg forState:UIControlStateNormal];[open setImage:opimg_on forState:UIControlStateHighlighted];
   [open setFrame:CGRectMake(ofv(postv.open,2),ofv(postv.open,3),opimg.size.width,opimg.size.height)];
   [open addTarget:self action:@selector(openPage) forControlEvents:UIControlEventTouchUpInside];
   [postv.view addSubview:open];
  }
  [postv.view setTag:i+1];
  [pvArr addObject:postv.view];
 }
 [pvlayer.view addSubview:[pvArr objectAtIndex:now-1]];[pvlayer.view setFrame:self.view.bounds];
 if(!now_all) now_all=[[UILabel alloc]init];[self na];
 if(!caption) caption=[[UILabel alloc]init];[self mkCaption];
}

できた。とりあえず顕在しているエラーは潰せたはず。
次は全typeの回転対応とiPad対応してreblog,like処理やってsetting対応してtimelineか。長い。

2012年7月27日金曜日

もぐらもぐら

ここ何週間か記憶喪失になるくらい仕事が忙しくxcodeがほとんど触れず。
回転のロジックはうまく動いていたが、ポートレート状態で横長の画像をランドスケープにすると、image.size自体をself.view.boundsに合わせて縮小してたもんやから当然引き伸ばされてボケる。こらあかんと、タイリングのロジックで画像サイズを画面に合わせずオリジナルに近い状態で保持するように修正。そっから出るわ出るわ、あれを直せばこれがおかしい、エンドレスなもぐらたたき。

・1行目の画像と2行目の画像に隙間があく「場合がある」
→2行目の開始位置yを1行目のheightにしてたが、2行目の方が1行目より横幅が小さい場合、1行目を縮小するのでyも小さくする必要が有ることに気付かずハマった。

・3つ目の画像が表示されない「場合がある」
→1行目のwと2行目1つ目のwがたまたま同値で、2行目の横幅全体を縮小してないために表示されていない現象を、3つ目がレンダリングされていないと勘違いしてハマった。

・1行目に3つ横に並べると2行目の横幅に比べて大幅に小さくなる「場合がある」
→これも前後の関係を忘れてて余計に縮小してた。ここまで来れば原因はなんとなく予測できた。

・animatedなgifをUIImageViewとしてselfにaddSubViewしてもcontentModeが効かない
→問題はこれ。何をやってもオリジナルサイズのままになる(UIViewContentModeScaleAspectFillにすらならない)。UIImageViewの配列を返そうとかプロパティに入れるかと思ったが非同期なので簡単にはいかん。困ったときのstackoverflowでいろいろ探してたら、これか!ってのを見つけた。

UIImageView is ignoring my ContentMode setting

まだ試してないが、可能性は高そう。

今月前半は出張が多かったので、いろいろとめぐった。
7/9の週は月曜から順番に、五行@河原町でもやし醤油→さくら亭@さくら夙川で黒豚ラーメン→麺舎ヒゲイヌ@尼崎で牛スジつけ麺アメ玉→ロケットキッチン@白鷺で特別ラーメン→らぁめん嬉しや@三田でらぁめん正油とやきめし→マッチョ@長瀬で並野菜マシマシ。翌週月火は いまい@垂水で中華そば→純情屋@金剛でつけめんと半チャーハン。
ヒゲイヌ、いまいは期待通り。ロケキチと純情屋は思ったよりいけた。ほかはそこそこ。至福。

2012年7月8日日曜日

もぐらたたき

なんかできたと思ったらなんかが動かないってことの連続でなかなか前に進まず。

・blogの最後のpostにたどり着いたあとも永遠にsince_idを減らして掘り続ける。
→blogはoffsetの制限がないので、since_idを使わず、total_postを見てJSONのおかわりに歯止めをかけるようにした。

・next,prevなどのコントローラが回転時に変な位置に置かれる。
→よくわからないが、loadView時に取得するself.view.boundsと、あとのタイミングで取得するものに違いがある(多分ステータスバー分heightが少ない)みたいなので、コントローラの作成をpostを配置したあとに変更。あと長押し時にボタン切り替えのためのメッセージが2回呼ばれるのにハマった。UIGestureRecognizerStateCancelledが帰ってきたときはreturnすることで対応。

・画像が複数ある場合のgifの回転がうまくいかない。
→これがかなり手こずった。どうあがいても回転時に縮尺が合わないし、contentModeやautoresizingMaskの数値をどれだけいじっても変な動きになる。悩みぬいた末に、GIFの一枚一枚を画面サイズぴったりになるように余白をclearColorで埋めてからanimatedImageWithImagesしてGIFのファイル数分imageViewにaddSubViewするという荒業に出た。昔に比べてしょーもないことで苦労することはなくなったが、まだまだ精進。

2012年7月5日木曜日

animatedGIF with ImageIO

ImageIOを使ったアニメーションGIF(というかアニメーションUIImage)は意外にあっさり動いた。ただUIGraphicsBeginImageContext〜でmixしたら当然ながらアニメは止まっちゃう。とりあえずUIImageViewのサブクラス修正版はこんな感じ。

- (void)makeImage:(NSMutableData *)rdata{
 if(multiFlg){
  UIImage *img=[UIImage imageWithData:rdata];
  [imgArr addObject:img];
  [imgArr addObject:nwf(img.size.width)];[imgArr addObject:nwf(img.size.height)];[imgArr addObject:nwf(img.size.width/img.size.height)];
  if([imgArr count]/4<[photourl count]){
   imgnum++;[self loadPhoto];
  }
  else{ //tiling routine
   int curx=0,cury=0,k=0;
   for(int j=0;j<[imgArr count]/4;j++){
    if([[imgArr objectAtIndex:j*4+3]floatValue]<sr&&[imgArr count]/4>j+1){
     int over3=2;
     CGFloat firstw=ofv(imgArr, j*4+1),firsth=ofv(imgArr, j*4+2),firstr=firsth/firstw;
     CGFloat nextw=ofv(imgArr, (j+1)*4+1),nexth=ofv(imgArr, (j+1)*4+2);
     CGFloat newh=firstr*((sw*nexth)/(nextw*firstr+nexth));
     while (sw/newhj+over3) {
      nextw=ofv(imgArr, (j+over3)*4+1),nexth=ofv(imgArr, (j+over3)*4+2);
      newh=(newh/sw)*((sw*nexth)/(nextw*(newh/sw)+nexth));
      over3++;
     }
     j+=1+(over3-2);
     UIGraphicsBeginImageContext(CGSizeMake(sw,cury+newh));
     if(mixedimg){[mixedimg drawAtPoint:CGPointMake(0, 0)];}
     while(k<j+1){
      CGFloat neww=newh*[[imgArr objectAtIndex:k*4]size].width/[[imgArr objectAtIndex:k*4]size].height;
      [[imgArr objectAtIndex:k*4]drawInRect:(CGRectMake(curx,cury,neww,newh))];
      curx+=neww;k++;
     }
     cury+=newh;curx=0;
     mixedimg=UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
    }else{
     UIGraphicsBeginImageContext(CGSizeMake(sw,cury+sw*[[imgArr objectAtIndex:j*4]size].height/[[imgArr objectAtIndex:j*4]size].width));
     if(mixedimg) [mixedimg drawAtPoint:CGPointMake(0, 0)];
     CGFloat newh=sw*[[imgArr objectAtIndex:j*4]size].height/[[imgArr objectAtIndex:j*4]size].width;
     [[imgArr objectAtIndex:j*4]drawInRect:(CGRectMake(0,cury,sw,newh))];
     mixedimg=UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
     cury+=sw*[[imgArr objectAtIndex:j*4]size].height/[[imgArr objectAtIndex:j*4]size].width;
    }
   }
   [ai removeFromSuperview];
   self.image=mixedimg;
   [self abort];   
  }
 }
 else{
  [ai removeFromSuperview];
  if([[photourl objectAtIndex:0]hasSuffix:@"gif"]){
   CGImageSourceRef src=CGImageSourceCreateWithData((__bridge CFDataRef)rdata,NULL);
   NSDictionary *prop=[(__bridge NSDictionary*)CGImageSourceCopyProperties(src,NULL) objectForKey:(NSString*)kCGImagePropertyGIFDictionary];
   size_t count=CGImageSourceGetCount(src);
   NSMutableArray *GIFimages=[NSMutableArray array];   
   for(size_t i=0; i<count; i++){
    CGImageRef image=CGImageSourceCreateImageAtIndex(src,i,NULL);
    [GIFimages addObject:[UIImage imageWithCGImage:image]];
    CGImageRelease(image);
   }
   NSTimeInterval delay=[[prop objectForKey:(NSString*)kCGImagePropertyGIFDelayTime] doubleValue];
   if (!delay) delay=1.2;
   CFRelease(src);
   self.image=[UIImage animatedImageWithImages:GIFimages duration:delay];
  }else self.image=[UIImage imageWithData:data];
  [self abort];
 }
}

できた。
これでgif以外なら何枚でもタイリングしてself.imageにセットし、gifでも1枚ならImageIO使ってアニメーションするUIImageをself.imageにセットされる。gifを含んだ複数画像のpostはこいつ経由で1枚1枚親UIViewControllerにaddSubViewしていけばいけるんじゃないかな。本当かな。

早速くじかれた

tumblrクライアント本体の構成を
 UIViewController > UIView > UIView > UIImage
から
 UIViewController > UIViewController > UIViewController > UIImageView
に変更してautoresizingMask設定して確認。post移動するたびに再度setFrameすることに気付かず若干ハマったが、うまく動いてる。

ただ、gifはアニメーションに対応するためUIWebViewにぶち込んでたんだが、これがうまく回らん。autoresizingMaskの値をいろいろ試してみたがダメ。
UIImageViewにUIWebViewをaddSubViewしてるのがまずいのか。UIWebViewをsuperviewにaddSubViewする形に変えるか、いっそのことImageIO使ってアニメーションgif作るか。それでもgifを含んだ複数画像の場合はsuperviewに直接貼らんとmixはできんような気がする。ちょっと試す。

https://github.com/larcus94/LBGIFImage
http://stackoverflow.com/questions/8545154/is-it-possible-to-animate-an-uiimage

を参考に。

2012年7月4日水曜日

回転

もともとuniversal appとして作るつもりだったので、なんとなく体裁も整ってきたからおもむろにiPadのシミュレータで見てみた。ボタンとかは倍の大きさにしたほうがいいかなーとか思いながらふと回転させてみたけど回らない。いっちょ回してみるかーといろいろ試してみた。

最初はサイズとか位置とか回転検出して再計算?postのview全部?100以上あるけど一気に?出てくる都度?扇風機の羽の真ん中にiPhoneつけてゆっくり回されたら負荷どうなる?と考えてげんなりしてたけど、普通にautoresizingMaskなるプロパティいじればよいだけと知って安堵。早速試してみるも、うまく動かん。サイズが全く変わらんし、プロパティの値を触るたびに予想を裏切る動作をする。既にコードも複雑化し(自分にとっては)、検証して原因を特定するには要素が多すぎる(自分にとっては)ので、新しい回転用のプロジェクトを作って検証開始。

まずはrootのUIViewControllerにUIImageViewのサブクラスを貼って、その中でwebに落ちてる水原希子の画像URLをrequestして表示する状態から、UIImageViewのautoresizingMaskを2+16にしてテスト。回転に合わせて完璧に動いてる。ここからtumblrクライアント本体の構成に近づけていく。ていうかUIViewControllerのテンプレートからloadViewが消えてる。nib使わない場合はこれで初期化するんじゃなかったっけ?まあいいけど。

で、本体と同じように、UIImageViewをUIViewで包んでやる。しかも二重に。

root(UIViewController) > pvlayer(UIView) > postv(UIView) > photov(UIImageView)

これでやると、本体と同じ症状。サイズが変更されないし、右端による。困った。
入れ子がまずいのかと、一重にしても改善されず。
UIViewがまずいのかと、pvlayerをUIViewControllerにしてphotovをaddSubViewすると、動いた!
二重にしても、UIViewControllerなら動く。

root(UIViewController) > pvlayer(UIViewController) > postv(UIViewController) > photov(UIImageView)

UIViewだと子供をautoresizeしないのか。ネットを調べてみると、回転処理にはUIViewControllerがよい、という内容は見ても、これじゃないとダメ、というのは見つけられなかった。別のプロパティとかがあるのかもしらんが、とりあえずこれでいこう。無知は怖い。
まだ本体は改修していないが、多分いけるだろう。

2012年7月1日日曜日

消費メモリの検証

前回少し書いたが、parmanent vacationアルゴリズムの検証時に、1100件前後読み込んだ段階でcode=12のエラーが吐かれた。生成したpostのuiviewを配列に入れたままいつまでも使えるようにしていたので、いつかは限界を超えるのは素人の私でもうすうす気付きつつ見ないようにしてきた。でもやっぱり逃げられないようなので検証してみた。Instrumentsでallocationsの推移を見守るだけ、という検証というのもおこがましい作業だが、私にはこれで限界。

まず、配列にpostのuiviewをぶちこみ続ける現状の仕組みのままでの数値。

・500posts / Live Bytes:171.24MB / Living:141153
・1000posts / Live Bytes:325.78MB / Living:223672

このまま続けようかと思ったが1250件あたりで400MBを超えたので一旦停止。4Sでもメモリ512MBなのでこれはいくらなんでもまずい。

で、直近100件だけ残して、それより前のpostはNULLで殺してみた。配列の要素としては残す。

・500posts / Live Bytes:55.00MB / Living:69924
・1000posts / Live Bytes:48.86MB / Living:76970

歴然。ここまで予想通りに結果が出ると逆に怖くなる。postによってメモリの喰い方が違うみたいなので、Live Bytesはだいたい38~55MBあたりをうろちょろ。100件戻る奴はおらんやろー。個人的にはもうちょい少なくてもいいけど、一旦これでいく。これで文字通り原理上ほぼparmanentな仕組みになった。というより早くAPI側でoffsetの制限取っ払って欲しい。こんなトリッキーなことさせるほうがよっぽどサーバに負荷がかかる気がする。

2012年6月30日土曜日

since_idの決め方

相変わらず仕事がつまってて全然いじれてないけど、今日は出張でいつもよりは早く帰れたからsince_idでの永久掘削続き。たまに(場合によってはしょっちゅう)loadingにすげー時間がかかることがあり、最初はネットワークの調子が悪いのかapiのサーバがヘタってるのかなどと考えていたが、いろいろ検証してみるとどうやらsince_idの確定アルゴリズムで遅延が起こってる様子。なーんだ自分のせいか安心した。

since_idを確定できるのは、lastidがおかわりしたJSONの中に含まれ、且つlastidとJSONの最後のidが等しくない場合なので、それ以外のケースでは、
1.lastidよりおかわりしたJSONの最初のidが小さい場合
2.lastidよりおかわりしたJSONの最後のidが大きい場合
に切り分けて、1の場合はlastidとJSONの最初のidの差分をsince_idに+=し、2の場合は逆に-=して再度おかわりして確定できるまで繰り返していた。さらに1の場合だと+=した際にsince_idがlastidを超えるという馬鹿なケースが起こるので、その場合は前述のとおりid乖離平均にlimit+1をかけた数値を半分(それでも多ければ戻ってくるたびに3分の1、4分の1〜)していた(これを3とする)。

上記アルゴリズムで、たまに(場合によってはしょっちゅう)以下の2パターンの遅延が発生していた。
A.1を数十回繰り返してもなかなかJSONの最初のidがlastidを超えない
B.1と2を行ったり来たり、または1と3を行ったり来たりする
Aについては、まあ待ってれば確定するが、それでも分単位でloadingが終わらないのは精神衛生上よくないしサーバにも優しくない。Bはさらに致命的で、ほぼ無限ループに近い状態になる。
Aは+=する値を倍々してスピードを上げ、Bは行き過ぎた分戻すだけなのでスピードを下げておかわりする。コードは以下のとおり。
- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 loadflg=NO;
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 long long topid=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue];
 int skip=0;
 if(offset+limit>250){
  if(lastid>topid){
   if(adjflg==1){adj2=2;since_id+=(lastid-topid)*adj1++;}
   else if(adjflg==2){adj1=2;since_id+=(lastid-topid)/adj2++;}
   else since_id+=lastid-topid;
   adjflg=1;
   if(since_id>=lastid){adj1=2;adj2=2;since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1))/adj3++;}
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];
   return;
  }else if(lastid<=[[[bd valueForKeyPath:@"response.posts.id"]lastObject]longLongValue]){
   if(adjflg==2){adj2=2;since_id-=(topid-lastid)*adj1++;}
   else if(adjflg==1){adj1=2;since_id-=(topid-lastid)/adj2++;}
   else since_id-=topid-lastid;
   adjflg=2;
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];
   return;
  }else{
   adjflg=0,adj1=2,adj2=2,adj3=2;
   for(id idnum in [bd valueForKeyPath:@"response.posts.id"]){
    if(lastid<=[idnum longLongValue]) skip++;
    else break;
   }
  }
 }else if(lastid!=0&&lastid<=topid){
  offset++;offsinceval=[NSString stringWithFormat:@"%d",offset];[self reqJSON];return;
 }
 //type別処理は例によって割愛
 lastid=[[idArr lastObject]longLongValue];
 [self mkSinglePostView];
}

できた。1100postくらいまでは動いてたけどerror code=12が出たのでメモリ関連の処理を調べる。postのuiviewで古いやつは配列から消したほうがいいのかな。
ランは遅かったので7.22km@37:51だけ走った。ラーメンは今月まだ10杯。ペース半減。体重はそれほど落ちず。

2012年6月19日火曜日

parmanent vacation(改)

引き続きsince_idによる永久掘削を検証してたら、どうも動作がおかしかったので修正。

1.JSONをおかわりする際のsince_idを決めるのに、単にlimit分新しいidとlastidの差分をlastidから引くとむらが大きかったので結局id乖離平均にlimit+1(1足すのはsince_idは自分を含まないため)をかけた数値をlastidから引くようにした。
2.おかわりしたJSONのトップのidがlastidより小さい場合はpostの欠落を防ぐためにわざと重複させるようlastidとトップのidの差をsince_idに+=してたけど、since_idがlastidを超えたり、更には最新のpostidよりでかくなったりと傍若無人な感じになってたのでlastidを超える場合は一旦id乖離平均にlimit+1をかけた数値を半分(それでも多ければ戻ってくるたびに3分の1、4分の1〜)にしてlastidから引いてreqJSON。
3.lastidがおかわりしたJSONの最後のidより小さい場合がなぜか発生するので、その場合の対応を追加。lastidとおかわりしたJSONのトップのidの差分をsince_idに-=してreqJSONする。

これでひとまず1000postsまで検証したが、特に問題なく動いている。と思う。

- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 int skip=0;
 if(!offsinceflg){
  if(lastid>[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
   since_id+=(lastid-[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]);
   if(since_id>=lastid) since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1))/adj++;
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];return;
  }else if(lastid<=[[[bd valueForKeyPath:@"response.posts.id"]lastObject]longLongValue]){
   since_id-=([[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]-lastid);
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];return;
  }else{
   adj=2;
   for(id idnum in [bd valueForKeyPath:@"response.posts.id"]){
    if(lastid<=[idnum longLongValue]) skip++;
    else break;
   }
  }
 }else if(lastid!=0&&lastid<=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
  offset++;offsinceval=[NSString stringWithFormat:@"%d",offset];[self reqJSON];return;
 }
 //type 別の処理は割愛
 lastid=[[idArr lastObject]longLongValue];
 [self mkSinglePostView];
}

- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview]; 
 [self na];
 if(now+10>[contArr count]){
  if(offset+limit<=50){
   offsinceval=[NSString stringWithFormat:@"%d",offset+=limit];
   }else{ 
   offsinceflg=NO;
   //since_id=2*lastid-[[idArr objectAtIndex:[idArr count]-limit-2]longLongValue];
   since_id=lastid-(([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1)*(limit+1));
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];
  }
   [self reqJSON];
 }
}
できた。次はついにreblogとlikeやるか。ジェスチャーの埋め込みもやらねば。

夜は台風の中の神戸出張帰りにかねてより気になってたすっぽんラーメン太尊で超すっぽんラーメン+雑炊。雑炊が150円の割りにどっさり3人前くらい出てきたので焦ったが相当美味かったが体重が昨日より1.4kg増えた。

2012年6月17日日曜日

parmanent vacation

offsetが250を超えた場合の処理を完了。意外にあっさり動いた。
id間の差の平均がどうのこうのとややこしいことはせず、
最後のidとそこからlimit分新しいidとの差を最後のidから引いてsince_idにセットしてreqJSON。
帰ってきたidのトップが最後のidより古ければ最後のidとトップのidとの差をsince_idに足して再度reqJSON。
最後のidよりトップのidが新しくなるまで繰り返し、重複分をスキップしてパース。
アルゴリズムとして無駄なことはしていない、はず。

- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 if(offsinceflg&&lastid!=0&&lastid<=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
  NSLog(@"cho-fuku");
  offset++;offsinceval=[NSString stringWithFormat:@"%d",offset];[self reqJSON];return;
 }
 int skip=0;
 if(!offsinceflg){
  if(lastid>[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
   since_id+=lastid-[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue];
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];[self reqJSON];return;
  }else{
   for(id idnum in [bd valueForKeyPath:@"response.posts.id"]){
    if(lastid<=[idnum longLongValue]) skip++;
    else break;
   }
  }
 }
 for(int i=skip;i<[[bd valueForKeyPath:@"response.posts.type"]count];i++){
  [idArr addObject:[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:i]];
  [typeArr addObject:[[bd valueForKeyPath:@"response.posts.type"]objectAtIndex:i]];
  [bnameArr addObject:[[bd valueForKeyPath:@"response.posts.blog_name"]objectAtIndex:i]];   
  //type別の処理は割愛
 }
 lastid=[[idArr lastObject]longLongValue];
 [self mkSinglePostView];
}

- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview]; 
 [self na];
 if(now+10>[contArr count]){
  if(offset+limit<=250){
   offset+=limit;
   offsinceval=[NSString stringWithFormat:@"%d",offset];
   [self reqJSON];
  }
  else{ 
   offsinceflg=NO;
   since_id=2*[[idArr lastObject]longLongValue]-[[idArr objectAtIndex:[idArr count]-1-limit]longLongValue];
   offsinceval=[NSString stringWithFormat:@"%lld",since_id];
   [self reqJSON];
  }
 }
}
できた。これで永遠にdashboardの深淵へ潜っていけるはず。
ifのネストとか嫌いやけどとりあえずこれで様子を見ていけてそうならコードは綺麗にする。
では今から小一時間ランしてくる。

2012年6月15日金曜日

idの取り扱い

最近仕事がハードでぜんぜんxcodeできない。
とりあえず、idの重複は回避。
- (void)apiTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data{
 NSDictionary *bd=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
 if(lastid!=0&&lastid<=[[[bd valueForKeyPath:@"response.posts.id"]objectAtIndex:0]longLongValue]){
  offset++;[self reqJSON];return;
 }
 //以下略
}
あとはoffsetが250を超えた場合の処理。 まずは250個のidがそれぞれどれくらい離れてるかの平均をとって、それにlimitの件数をかけた値をlastidから引いてsince_idにセットしてreqJSONして、帰ってきたJSONの一番上のidがlastidより古ければ平均を足して再度reqJSONしてlastid(またはそれより新しいid)と重複したら重複した分だけ削ってパースしてpostのviewを作る感じでいけば、postの欠落は防げるかな。とりあえず平均を取るところまで。
- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview]; 
 [self na];
 if(now+10>[contArr count]){
  offset+=limit;
  if(offset<=250) [self reqJSON];
  else{ 
   if(iddiff==0) iddiff=([[idArr objectAtIndex:0]longLongValue]-[[idArr lastObject]longLongValue])/([idArr count]-1);
   NSLog(@"%lld",iddiff);
  }
 }
}
できた。
今日は丹後半島出張で、帰ってきて福島のみつ星製麺所で濃厚らーめん半玉増し白ごはん。濃い。今年100杯超えてるのでペースは押さえ気味(100杯目は姫路の丸十)。今月5杯目。しかも夜久々にランした。10km弱。半年ラーメン三昧だった分、絞るぞ。

2012年6月5日火曜日

single post

とりあえずsingle postのUIViewをサブクラスにしてみた。linkを開くためにUIWebViewを上位のviewに貼り付けるのにちょっとまごついたけど、self.superview.superviewにaddsubviewすることでrootのviewに無事貼れた。
夜は久々に芦原橋のふじいで中華そばと半チャーハン。美味い。前回は誰もおらんかったけど今日はすげー混んでた。

2012年6月4日月曜日

tasks

とりあえず現時点での積み残しをまとめておく。

・single postのUIViewをサブクラスに抜き出す
・閲覧中にdashboardにpostが追加された場合のoffset調整(ID重複の解消)
・offset250以上のdashboardの対応
・reblog,likeの実装
・スワイプでのpost移動の実装
・ダブルタップでのreblog,トリプルでのlikeの実装
・keyboardからjktでのオペレーション実装
・timelineスタイルのpostのUIViewサブクラスを作成
・setting画面の実装

山盛り。いっこいっこ潰していくしか無いなー。

2012年6月3日日曜日

type:answer

ついに最後のpost typeに到達。長かった。ものすごく冗長な感じがするが、仕方ない。

}else if([[typeArr objectAtIndex:i]isEqualToString:@"answer"]){
   UIView *ansBase=[[UIView alloc]init];
   UIImage *queimg=[UIImage imageNamed:@"question"];
   UIImageView *queimgv=[[UIImageView alloc]initWithImage:queimg];
   [queimgv setFrame:CGRectMake(10,0,queimg.size.width,queimg.size.height)];
   [ansBase addSubview:queimgv];
   UILabel *que=[[UILabel alloc]init];que.numberOfLines=0;
   que.text=[self deltag:[[contArr objectAtIndex:i]objectAtIndex:0]];que.font=sf(15);
   CGSize quesize=[que.text sizeWithFont:que.font constrainedToSize:CGSizeMake(sw-80,sh*3) lineBreakMode:UILineBreakModeTailTruncation];
   que.textColor=[UIColor blackColor];que.backgroundColor=[UIColor clearColor];que.frame=CGRectMake(70,0,quesize.width,quesize.height);
   [ansBase addSubview:que];
   UIImage *line=[UIImage imageNamed:@"gray"];
   UIImageView *linev=[[UIImageView alloc]initWithImage:line];linev.alpha=0.7;
   [linev setFrame:CGRectMake(10,(quesize.height>50?quesize.height:50)+10,sw-20,4)];
   [ansBase addSubview:linev];
   UIImage *ansimg=[UIImage imageNamed:@"answer"];
   UIImageView *ansimgv=[[UIImageView alloc]initWithImage:ansimg];
   [ansimgv setFrame:CGRectMake(10,linev.frame.origin.y+14,ansimg.size.width,ansimg.size.height)];
   [ansBase addSubview:ansimgv];
   UILabel *ans=[[UILabel alloc]init];ans.numberOfLines=0;
   ans.text=[self deltag:[[contArr objectAtIndex:i]objectAtIndex:1]];ans.font=sf(15);
   CGSize anssize=[ans.text sizeWithFont:ans.font constrainedToSize:CGSizeMake(sw-80,sh*3) lineBreakMode:UILineBreakModeTailTruncation];
   ans.textColor=[UIColor blackColor];ans.backgroundColor=[UIColor clearColor];
   ans.frame=CGRectMake(70,linev.frame.origin.y+14,anssize.width,anssize.height);
   [ansBase addSubview:ans];   
   //npy+=npsize.height+20;j++;
   UIScrollView *ansScr=[[UIScrollView alloc]initWithFrame:CGRectMake(0,30,sw,sh-50)];[postv addSubview:ansScr];
   [ansScr addSubview:ansBase];
   [ansScr setContentSize:CGSizeMake(sw-20,(quesize.height>50?quesize.height:50)+4+(anssize.height>50?anssize.height:50)+40)];
  }

できた。
あとはreblog,likeの処理、ID重複処理を終わらせて、timeline styleも作ってみるか。

type:video(続)

UIWebViewにvideoのエンベッド用コードを表示させた。Flash requiredなvideoはwidth、heightが埋め込まれてないので、そいつはnot availableとして対処。それ以外はクリックしたらムービーのプレーヤーが立ち上がる。

  }else if([[typeArr objectAtIndex:i]isEqualToString:@"video"]){
   NSError *error;
   NSRegularExpression *wpat=[NSRegularExpression regularExpressionWithPattern:@"width=['\"]?([0-9]+)" options:0 error:&error];
   NSRegularExpression *hpat=[NSRegularExpression regularExpressionWithPattern:@"height=['\"]?([0-9]+)" options:0 error:&error];
   int w,h;
   if(!error) {
    NSTextCheckingResult *wmatch=reg(wpat),*hmatch=reg(hpat);
    w=[[[contArr objectAtIndex:i]substringWithRange:[wmatch rangeAtIndex:1]]intValue],
    h=[[[contArr objectAtIndex:i]substringWithRange:[hmatch rangeAtIndex:1]]intValue];
   }
   if(!w||!h){
    UIImage *na=[UIImage imageNamed:@"na"];
    UIImageView *nav=[[UIImageView alloc]initWithImage:na];
    [nav setFrame:CGRectMake((sw-na.size.width)/2,(sh-na.size.height)/2,na.size.width,na.size.height)];
    [postv addSubview:nav];
   }else{
    UIWebView *video=[[UIWebView alloc]initWithFrame:CGRectMake((sw-w)/2,(sh-h)/2,w,h)];
    [video loadHTMLString:[contArr objectAtIndex:i] baseURL:nil];
    [postv addSubview:video];
   }
  }

できた。
昨日は職場の歓送迎会でラーメンは食べれず。先月末はさるぱぱ@河内永和で中華そば味付玉子ネギ。高井田系ではかなり上位の美味しさ。これで先月24杯、今年に入って96杯。やばいな。100杯いったらちょっと自粛するか。

2012年5月29日火曜日

type:video

audioはひとまずpost_urlに飛ばすようにした。特に問題なく動いている様子。videoはエンベッドのコードがpostによって違うようで、youtubeだったり別の何かだったり場合によってはFlashだったりするみたい。とりあえずUIWebView貼るためにwidthとheightを正規表現で抜くとこまで書いた。

#define reg(x) [x firstMatchInString:[contArr objectAtIndex:i] options:0 range:NSMakeRange(0, [[contArr objectAtIndex:i]length])]


}else if([[typeArr objectAtIndex:i]isEqualToString:@"video"]){
 NSError *error;
 NSRegularExpression *wpat=[NSRegularExpression regularExpressionWithPattern:@"width=['\"]?([0-9]+)" options:0 error:&error];
 NSRegularExpression *hpat=[NSRegularExpression regularExpressionWithPattern:@"height=['\"]?([0-9]+)" options:0 error:&error];
 if(error) NSLog(@"error.");
 else{
  NSTextCheckingResult *wmatch=reg(wpat),*hmatch=reg(hpat);
  NSLog(@"w:%@ / ",[[contArr objectAtIndex:i]substringWithRange:[wmatch rangeAtIndex:1]]);
  NSLog(@"h:%@",[[contArr objectAtIndex:i]substringWithRange:[hmatch rangeAtIndex:1]]);
 }
}

できた。perlとかと比べて正規表現の処理にエレガントさがない。あとはUIWebView貼るだけ。ようやくここまで来た。

夜は三く@新福島でかけ並ごはん。煮干系はなかなか外れがない。美味い。ちなみにボクサーの井岡さんとエンカウントしてシールもらった。このシール中津の弥七でも貼ってたしラーメン好きなんかな。

2012年5月27日日曜日

album_art

とりあえずaudioのジャケット画像を非同期で引っ張ってきて真ん中に表示させるためのUIImageViewのサブクラスを作った。あとはこの下にボタン置いてタッチしたらpost_urlをUIWebViewで開く感じで十分なようにも思う。Tumblrの中の人にはaudio_urlについて質問中。サポートチームに投げてなんかわかったら連絡するわーとのこと。

AlbumArt.m
- (id)initWithFrame:(CGRect)frame aaurl:(NSString *)cont
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
  if([cont isEqual:[NSNull null]]){
   self.image=[UIImage imageNamed:@"ina"];
   return self;
  }
  if(!ai) ai=[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  [ai setCenter:self.center];[self addSubview:ai];[ai startAnimating];
  data=[[NSMutableData alloc]initWithCapacity:0];
  NSURLRequest *req=[NSURLRequest 
         requestWithURL:[NSURL URLWithString:cont]
         cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
  conne=[[NSURLConnection alloc]initWithRequest:req delegate:self];
    }
    return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
 if([((NSHTTPURLResponse *)response) statusCode]!=200){
  data=[[NSMutableData alloc]initWithData:UIImagePNGRepresentation([UIImage imageNamed:@"ina"])];
  self.image=[UIImage imageWithData:data];
  [self abort];
 }
 else [data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)rdata{
 [data appendData:rdata];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
 [self abort];
 NSLog(@"error.");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
 [ai removeFromSuperview];
 UIImage *aa=[UIImage imageWithData:data];
 UIImageView *aav=[[UIImageView alloc]initWithImage:aa];
 UIImage *border=[UIImage imageNamed:@"gray"];
 UIImageView *borderv=[[UIImageView alloc]initWithImage:border];
 [aav setFrame:CGRectMake(3,3,204,204)];
 [borderv setFrame:CGRectMake(0,0,210,210)];
 [self addSubview:borderv];[self addSubview:aav];
 [self setFrame:CGRectMake((self.frame.size.width-210)/2,(self.frame.size.height-210)/2,210,210)];
}
-(void)abort{
 if(conne!=nil){[conne cancel];conne=nil;}
 if(data!=nil) data=nil;
}

できた。

勘違い

audio_urlにリクエスト投げて返ってきたのはpermission deniedだったみたい…。statusCode拾ったら403やった。最初から確認しとけばよかった。とりあえずtumblrのなかの人に聞いてみようかな。
昼は河童ラーメン本舗工場店で河童ラーメンとごはん小。予想してたよりは全然いけた。今度は黒河童いってみるか。

type:audio

tumblrのAPIから返ってくるaudioのJSONにaudio_urlっていうエレメントがあった。APIのドキュメントには一切記載がない。このへんtumblrの中の人ら超テキトーっぽいからなー。とりあえずそのurlにリクエスト投げてみたらデータは返ってきた。レスポンスのヘッダはこんな感じ。

2012-05-27 00:09:33.311 tm0001[1433:f803] {
"Cache-Control" = "no-cache, must-revalidate";
Connection = close;
"Content-Encoding" = gzip;
"Content-Length" = 780;
"Content-Type" = "text/html; charset=UTF-8";
Date = "Sat, 26 May 2012 15:09:33 GMT";
Expires = "Fri, 01 Jan 1990 00:00:00 GMT";
P3P = "CP=\"ALL ADM DEV PSAi COM OUR OTRo STP IND ONL\"";
Pragma = "no-cache";
Server = Apache;
Vary = "Accept-Encoding";
"X-Tumblr-Usec" = "D=27180";
}

gzip?htmlが圧縮されてんの?全然分からん。返ってくるデータも容量的に小さいし、とりあえずaudio関連のライブラリにぶち込んで試してみよう。って偉そうに書いてるけど今からゼロから調べるんやけどね。
昨晩は愛媛出張から帰ってきてそのまま光龍益@桜ノ宮で特製ラーメン。まー、普通。今月まだ19杯目。

2012年5月23日水曜日

chatにふきだし

ふきだし、とりあえずつけてみた。
}else if([[typeArr objectAtIndex:i]isEqualToString:@"chat"]){ //chat
 UIView *chatBase=[[UIView alloc]init];
 CGFloat npy=0;int j=1;
 for(id dialogue in [contArr objectAtIndex:i]){
  UILabel *np=[[UILabel alloc]init];np.numberOfLines=0;
  np.text=[NSString stringWithFormat:@"%@%@",[dialogue objectForKey:@"label"],[dialogue objectForKey:@"phrase"]];np.font=sf(15);
  CGSize npsize=[np.text sizeWithFont:np.font constrainedToSize:CGSizeMake(sw-60,sh-50) lineBreakMode:UILineBreakModeTailTruncation];
  np.textColor=[UIColor blackColor];np.backgroundColor=[UIColor clearColor];np.frame=CGRectMake(30,npy,npsize.width,npsize.height);
  [chatBase addSubview:np];
  npy+=npsize.height+20;j++;
  //if(j>[[contArr objectAtIndex:i]count]) break;
  UIImage *line=[UIImage imageNamed:@"gray"];
  UIImageView *linev=[[UIImageView alloc]initWithImage:line];linev.alpha=0.7;[linev setFrame:CGRectMake(25,npy-12,sw-50,4)];
  UIImage *fuki=[UIImage imageNamed:(j%2)?@"fuki":@"fuki2"];
  UIImageView *fukiv=[[UIImageView alloc]initWithImage:fuki];fukiv.alpha=0.7;
  [fukiv setFrame:CGRectMake((j%2)?5:sw-25,npy-12-30,20,30)];
  [chatBase addSubview:linev];[chatBase addSubview:fukiv];
 }
 UIScrollView *chatScr=[[UIScrollView alloc]initWithFrame:CGRectMake(0,30,sw,sh-50)];[postv addSubview:chatScr];
 [chatScr addSubview:chatBase];
 [chatScr setContentSize:CGSizeMake(sw-40, npy)];
}
できた。
これで各自が喋ってる感がちょっとはでたはず。
よるは天天,有@住之江公園に行くも何故かしまってたので急遽岸里から歩いてらーめんコーさんでチャーシューらーめんとライス。角煮級のチャーシューが5枚入ってる上にライスが激盛りで死ぬかと思った。味はあんまり覚えてない。

2012年5月22日火曜日

audioとvideo

audioはAPIから返ってくるのがswfをエンベッドするためのコードなので、そのままだと使えない。UAをiPhoneにしてaudioのポストを公式dashboardでどう表示してるか見てみるとjavascriptでmp3を再生してるみたい。mp3の命名規則はなんとなくわかるのでやろうと思えば無理やりHTMLのパースで抜けると思うが、あまり意味ないよね。とりあえずジャケット画像を出してボタン置いて押せばpostのurlをUIWebViewに貼ってaddSubViewかな。手抜きかな?videoはyoutubeのエンベッド用コードが返ってくるので単純に貼れば済む。その際のUIWebViewのframe.size計算のためにコードからwidthとheightを抜かんといかんな。正規表現でパパっと行けそうなサンプル見つけたので参考にさせて頂く。

Objective-Cで正規表現を使う その2

2012年5月21日月曜日

type:chat

typeがchatのポストのviewがとりあえずできた。あとは吹き出しみたいな奴もつけてみるか。いらんか。
}else if([[typeArr objectAtIndex:i]isEqualToString:@"chat"]){ //chat
 UIView *chatBase=[[UIView alloc]init];
 CGFloat npy=0;int j=1;
 for(id dialogue in [contArr objectAtIndex:i]){
  UILabel *np=[[UILabel alloc]init];np.numberOfLines=0;
  np.text=[NSString stringWithFormat:@"%@%@",[dialogue objectForKey:@"label"],[dialogue objectForKey:@"phrase"]];np.font=sf(15);
  CGSize npsize=[np.text sizeWithFont:np.font constrainedToSize:CGSizeMake(sw-40,sh-50) lineBreakMode:UILineBreakModeTailTruncation];
  np.textColor=[UIColor blackColor];np.backgroundColor=[UIColor whiteColor];np.frame=CGRectMake(20,npy,npsize.width,npsize.height);
  [chatBase addSubview:np];
  npy+=npsize.height+20;j++;
  if(j>[[contArr objectAtIndex:i]count]) break;
  UIImage *line=[UIImage imageNamed:@"gray"];
  UIImageView *linev=[[UIImageView alloc]initWithImage:line];linev.alpha=0.7;[linev setFrame:CGRectMake(20,npy-12,sw-40,5)];
  [chatBase addSubview:linev];   
 }
 UIScrollView *chatScr=[[UIScrollView alloc]initWithFrame:CGRectMake(0,30,sw,sh-50)];[postv addSubview:chatScr];
 [chatScr addSubview:chatBase];
 [chatScr setContentSize:CGSizeMake(sw-40, npy)];
}
できた。あとはaudio,videoか。これができればあとはtimelinestyleも作るか。楽しみは尽きない。gearも復活したことやし、のんびりやろう。
夜は中華そば無限@海老江で塩そばとごはん。これは美味い。閉まるの早いからなかなか来れんけど、今月限定の無限流高井田系食べてみたい。

2012年5月20日日曜日

UI微調整

textとquoteの見た目をちょっと変えた。統一感というか、単純によりよいデザインというか、そのへんを考えて。やっぱり何週間か前のコードでも、改めて見ると色々修正したくなるな。ひとまず下記の通り:
- (void)mkSinglePostView{
 for(int i=[pvArr count];i<[contArr count];i++){
  postv=[[UIView alloc]initWithFrame:self.view.bounds];
  if([[typeArr objectAtIndex:i]isEqualToString:@"text"]){ //text
   NSString *text=[self deltag:[contArr objectAtIndex:i]];
   UITextView *textv=[[UITextView alloc]initWithFrame:CGRectMake(10, 20, sw-20, sh-50)];
   [postv addSubview:textv];
   textv.text=text;
   for(int i=16;i<270;i++){
    textv.font=sf(i);
    if(textv.contentSize.height>sh-50){textv.font=sf(i-1);break;}
   }
   textv.textColor=[UIColor blackColor];textv.backgroundColor=[UIColor clearColor];textv.editable=NO;
   [postv addSubview:textv];
  }else if([[typeArr objectAtIndex:i]isEqualToString:@"photo"]){ //photo
   Photo *photov=[[Photo alloc]initWithFrame:self.view.bounds photourl:[contArr objectAtIndex:i]];
   [postv addSubview:photov];
   [photov loadPhoto];
  }else if([[typeArr objectAtIndex:i]isEqualToString:@"quote"]){ //quote
   UIImage *quoteimg=[UIImage imageNamed:@"quote"];
   UIImageView *quoteimgv=[[UIImageView alloc]initWithImage:quoteimg];
   [quoteimgv setFrame:CGRectMake(5,-10,50,50)];quoteimgv.alpha=0.7;
   [postv addSubview:quoteimgv];
   NSString *quote=[self deltag:[contArr objectAtIndex:i]];
   UITextView *quotev=[[UITextView alloc]initWithFrame:CGRectMake(25, 40, sw-30, sh-70)];
   [postv addSubview:quotev];
   //quotev.text=quote;quotev.font=[UIFont fontWithName:@"Helvetica" size:[quote length]>13?300/[quote length]*(3-[quote length]/150)+15:75];
   quotev.text=quote;
   for(int i=16;i<270;i++){
    quotev.font=sf(i);
    if(quotev.contentSize.height>sh-70){quotev.font=sf(i-1);break;}
   }
   quotev.textColor=[UIColor blackColor];quotev.backgroundColor=[UIColor clearColor];quotev.editable=NO;
   UIImage *line=[UIImage imageNamed:@"gray"];
   UIImageView *linev=[[UIImageView alloc]initWithImage:line];linev.alpha=0.5;[linev setFrame:CGRectMake(15,50,7,quotev.contentSize.height-20)];
   [postv addSubview:linev];   
  }else if([[typeArr objectAtIndex:i]isEqualToString:@"link"]){ //link
   Link *link=[[Link alloc]initWithFrame:CGRectMake(10,30,sw-20,sh-80) string:[[contArr objectAtIndex:i]objectAtIndex:0]];
   [postv addSubview:link];
   NSLog(@"%f",sh-link.origin.y);
   UIImage *openlinkimg=[UIImage imageNamed:@"openlink"],*openlinkimg_on=[UIImage imageNamed:@"openlink_on"];
   UIButton *open=[UIButton buttonWithType:UIButtonTypeCustom];//move.alpha=0.7;
   [open setImage:openlinkimg forState:UIControlStateNormal];[open setImage:openlinkimg_on forState:UIControlStateHighlighted];
   [open setFrame:CGRectMake(10,sh-link.origin.y-30,130,23)];
   [open addTarget:self action:@selector(openLink) forControlEvents:UIControlEventTouchUpInside];
   [postv addSubview:open];
  }

できた。appとして方向性が決まってなかったのを是正した感じかな。そんな大層なもんでもないか。
昼は中華そば鐘馗@東三国で中華そばと半ヤキメシ。美味しい。次はTKG試してみよう。

2012年5月17日木曜日

type:link

Linkのタイトルの下にボタンを置いてタッチすると必要最低限のコントローラを持ったUIWebView(を持ったUIViewController)をaddSubViewして用済みになればkillmeするクラスを作って貼った。特に問題なく稼働。

- (id)initWithURL:(NSString *)string{
 self=[super init];if(self) url=string;
 return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view.
 page=[[UIWebView alloc]initWithFrame:self.view.bounds];page.scalesPageToFit=YES;page.delegate=self;[self.view addSubview:page];
 toolbar=[[UIToolbar alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height-36, self.view.frame.size.width,36)];
 toolbar.tintColor=[UIColor blackColor];toolbar.translucent=YES;
 [self.view addSubview:toolbar];
 back=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(back)];
 forward=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFastForward target:self action:@selector(forward)];
 done=[[UIBarButtonItem alloc]initWithTitle:@"done" style:UIBarButtonItemStyleDone target:self action:@selector(killme)];
 NSArray *buttons=[[NSArray alloc]initWithObjects:done,back,forward,nil];
 [toolbar setItems:buttons animated:YES];
 [page loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}

- (void)webViewDidStartLoad:(UIWebView *)webView{
 if(!ai) ai=[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
 [ai setCenter:self.view.center];[self.view addSubview:ai];[ai startAnimating];
 [self update];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
 if(ai) [ai removeFromSuperview];
 [self update];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
 //nothing.
}
- (void)update{
 back.enabled=page.canGoBack;
 forward.enabled=page.canGoForward;
}

- (void)killme{[self.view removeFromSuperview];}
- (void)back{if(page.canGoBack)[page goBack];}
- (void)forward{if(page.canGoForward)[page goForward];}

できた。まあこのへんは参考にできる文書が山のようにあるので楽チン。残すはchat、audio、videoか。なんとかなるかな。
昼は3738@長居で特製中華そば。ガラガラ。思ってたよりは美味しかった。今月まだ10杯目。

CTFrameGetLineOrigins

CoreTextで描画した文字のframeの高さを取るだけで死ぬほど苦労した。CTFrameGetLineOriginsってのを使えばいいのはわかったけど参考事例が少なすぎる上に皆バラバラの記述で何が何やらわからん。やっとの思いでたどり着いて宣言プロパティにoriginの入ったcgpointを入れた。これでリンクテキストのしたにボタンを置けた。たったこれだけのことで何時間費やしたのか。またひとつ賢くなった。

#import "LinkedText.h"

@implementation LinkedText

@synthesize origin;

- (id)initWithFrame:(CGRect)frame string:(NSString *)string
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
  NSMutableDictionary *attrDic=[NSMutableDictionary dictionaryWithCapacity:0];
  CTFontRef ctFont=CTFontCreateWithName((__bridge CFStringRef)@"Helvetica",24, NULL);
  [attrDic setObject:(__bridge id)ctFont forKey:(NSString *) kCTFontAttributeName];
  [attrDic setObject:(__bridge id)[UIColor blueColor].CGColor forKey:(NSString *)kCTForegroundColorAttributeName];
  [attrDic setObject:[NSNumber numberWithInt:(int)kCTUnderlineStyleSingle] forKey:(NSString *)kCTUnderlineStyleAttributeName];
  NSMutableAttributedString *linkedText=[[NSMutableAttributedString alloc]initWithString:string attributes:attrDic];
  CGMutablePathRef path=CGPathCreateMutable();
  CTFramesetterRef framesetter= CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)linkedText);;
  CGPathAddRect(path, NULL, self.bounds);
  ctFrame=CTFramesetterCreateFrame(framesetter,CFRangeMake(0, [linkedText length]),path,NULL);
  CFArrayRef lines=CTFrameGetLines(ctFrame);
  CFIndex lastitem=CFArrayGetCount(lines)-1;
  CTFrameGetLineOrigins(ctFrame, CFRangeMake(lastitem,1), &origin);
  CGPathRelease(path);
  self.backgroundColor=[UIColor clearColor];
    }
    return self;
}

できた。originを呼び元のrootのviewcontrollerから参照してボタンを置いて、あとはUIWebViewでリンク先を開くだけ。これはacessTokenを取る時と同じ感じでいけるはず。本当か。

夜は職場の人とサシで御飯食べて更にそこから角力@玉造でコクしょうゆらーめん。美味いが、若干甘いかな。満腹。

2012年5月14日月曜日

NSMutableAttributedString

typeがlinkのpostはtitleを青字の下線で表示すべく色々調べたけど普通にやっても無理みたいでcore text?使わんといかんらしい。とりあえずいろんなサイトから切り貼りでUITextViewのサブクラス作ってみた。

- (id)initWithFrame:(CGRect)frame string:(NSString *)string
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
  NSMutableDictionary *attrDic=[NSMutableDictionary dictionaryWithCapacity:0];
  CTFontRef ctFont=CTFontCreateWithName((__bridge CFStringRef)@"Helvetica",21, NULL);
  [attrDic setObject:(__bridge id)ctFont forKey:(NSString *) kCTFontAttributeName];
  [attrDic setObject:(__bridge id)[UIColor blueColor].CGColor forKey:(NSString *)kCTForegroundColorAttributeName];
  [attrDic setObject:[NSNumber numberWithInt:(int)kCTUnderlineStyleSingle] forKey:(NSString *)kCTUnderlineStyleAttributeName];
  NSMutableAttributedString *linkedText=[[NSMutableAttributedString alloc]initWithString:string attributes:attrDic];
  CGMutablePathRef path=CGPathCreateMutable();
  CTFramesetterRef framesetter= CTFramesetterCreateWithAttributedString((__bridge CFMutableAttributedStringRef)linkedText);;
  CGPathAddRect(path, NULL, self.bounds);
  ctFrame=CTFramesetterCreateFrame(framesetter,CFRangeMake(0, [linkedText length]),path,NULL);
  CGPathRelease(path);
  //self.text=string;self.font=[UIFont fontWithName:@"Helvetica" size:21];self.textColor=[UIColor clearColor];
    }
    return self;
}
- (void)drawRect:(CGRect)rect
{
  CGContextRef context;
    context=UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, 0, CGRectGetHeight(self.bounds));
    CGContextScaleCTM(context, 1.0f, -1.0f);
    CTFrameDraw(ctFrame, context);
    CGContextRestoreGState(context);
}


UITextViewのサブクラスである必要はなさそうやけど一応。contentSize.height取るために透明色でtextセットしてみたけど、core textのものとは微妙に文字幅、行間が異なるみたいでなんかずれる。ちょっと調べる。
週末は麺や天啓でつけ麺。つけ麺自体はそこそこやけど、〆にスープ割りだけじゃなくて雑炊も選べて、これが実に美味い。今月まだ6杯目。ペースが遅い。

2012年5月7日月曜日

typeがtextのpostはtitleとbodyの境界に線を描画しようと思って色々調べたけど、uiviewのサブクラスを作ってdrawrectをオーバーライドするとかすげー大層な話になってるので1x1pxのpngをuiimageでサイズ変形してaddsubviewした。これで十分かな。あとはquoteのフォントサイズを15〜270のループで回してcontentSize.heightがwindowを超えるいっこ前を取るようにしてびしっとuitextview内に収まるように修正。だいぶ力技やな。contentSizeがaddsubviewする前には取得できないと知らずに若干ハマった。

#define sf(x) [UIFont fontWithName:@"Helvetica" size:x]

}else if([[typeArr objectAtIndex:i]isEqualToString:@"quote"]){ //quote
 UIImage *quoteimg=[UIImage imageNamed:@"quote"];
 UIImageView *quoteimgv=[[UIImageView alloc]initWithImage:quoteimg];
 [quoteimgv setFrame:CGRectMake(10,15,50,50)];quoteimgv.alpha=0.7;
 [postv addSubview:quoteimgv];
 NSString *quote=[self deltag:[contArr objectAtIndex:i]];
 UITextView *quotev=[[UITextView alloc]initWithFrame:CGRectMake(10,20,sw-20,sh-20)];
 [postv addSubview:quotev];
 quotev.text=quote;
 for(int i=16;i<270;i++){
  quotev.font=sf(i);
  if(quotev.contentSize.height>sh-20){quotev.font=sf(i-1);break;}
 }
 quotev.textColor=[UIColor blackColor];quotev.backgroundColor=[UIColor clearColor];quotev.editable=NO;
}
できた。

今日は振休だったので平日のみ16時までという普段絶対行けない弥七@中津で醤油ちゃーしゅー麺中盛。長蛇の列を覚悟して文庫2冊持ってったけど4人くらいしか並んでなくて拍子抜け。まあ、美味しいけど並ぶほどかと言われれば微妙かな。

2012年5月5日土曜日

LONG LONG TIME AGO

とりあえずlimit近くまでpostを表示させたらJSONをおかわりするロジックを入れた。これでとりあえず体裁は整った。postの重複とかを除けば傍目にはまとも?なtumblrクライアントの振る舞いはする。あとは重複やoffset上限250問題回避のためにidを取り扱おうにもtumblrのidの桁数が多すぎてlongでは扱えないからちょっと調べたらlong longなる型があるのね。これでいけそう。text,link,chat,audio,videoの扱いも決めれば、必要最低限は完成するな。
今日の昼はマッチョ@千日前で並野菜マシマシちょいカラ。初のマシマシ。死ぬかと思った。

2012年5月3日木曜日

uiwebview縮小限界値、非同期のエラー処理 など

明日から5連休!xcodeで遊ぶぞー。
とりあえず、gifを包んだUIWebViewはrectの4値を全部設定することで対応。
でもある一定の縮小率を超えるとコンテンツはそれ以上小さくならないみたい。(rectは普通に変形される)
setScalesPageToFitとは別のプロパティとかあるんかなー。
ただかなり小さくならないと影響ないのでとりあえずこれで進める。
ImageIOは調べてもgifの生成の記事がヒットするけど単にanimatedなgifを再生するだけの記事はどこにもない。
ヘッダ情報からアニメーションばらして再生成とか正気の沙汰じゃないしなー。
あと、UIWebViewの非同期通信は200のステータスが帰ってこなくてもエラー扱いにならないみたいで、自前で処理が必要みたいなので足してみた。

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
 if([((NSHTTPURLResponse *)response) statusCode]!=200){
  data=[[NSMutableData alloc]initWithData:UIImagePNGRepresentation([UIImage imageNamed:@"notfound"])];
  [self makeImage:data];
  [self abort];
 }
 else [data setLength:0];
}

ひとまずこれで動いてるみたい。

昨日は玉造の角力でとんこつ。独特なスープで最初は ん? って感じやけど最後の方はすべて飲み干した。うまい。次はコクしょうゆいってみる。

2012年5月1日火曜日

UIWebViewのリサイズ

gifを包んだUIWebView *gifvをUIImageView *ivにaddSubViewしてivのframeを変形してもgifvが合わせてリサイズされない。setAutoresizingMask設定しても中身はリサイズされてるけど元のrectの大きさはそのままのため余白が空く。ivの変形率からgifの最終的なframe計算してsetするしか無いのかな。なんか気持ち悪い。
昨日の昼は4月最後のラーメンを千林大宮のそらの星で。家系?うーん、、、濃い。やっぱり高井田系が一番すきかな。

2012年4月29日日曜日

非同期仮完了

とりあえず画像の読み込みを非同期にするため、UIImageViewのサブクラスを作ってNSURLConnection使って画像をロードする。@implementationの直下に裸で大域変数置いたらインスタンス同士で書き換えるみたいでエラー出まくりでしかも非同期やからdealloc済みにアクセスしてますよーとか配列にそんなに数入ってないですよーとか毎回違うエラーが出てハマった。ちゃんと@interfaceで宣言しとけば大丈夫みたい。基本か。そうだろうな。

Photo.m
#import "Photo.h"
#define nwf(x) [NSNumber numberWithFloat:x]
#define ofv(x,y) [[x objectAtIndex:y]floatValue]

@implementation Photo

- (id)initWithFrame:(CGRect)frame photourl:(NSMutableArray *)cont;
{
    self = [super initWithFrame:frame];
    if (self) {
  photourl=[[NSArray alloc]initWithArray:cont];
  imgnum=0;
  multiFlg=NO;
  if([photourl count]>1){
   multiFlg=YES;
   imgArr=[[NSMutableArray alloc]initWithCapacity:0];
  }
  UIScreen *uis=[UIScreen mainScreen];
  sw=[uis bounds].size.width,sh=[uis bounds].size.height,sr=sw/sh;
    }
    return self;
}

-(void)loadPhoto{
 [self abort];
 if(!ai) ai=[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
 [ai setCenter:self.center];[self addSubview:ai];[ai startAnimating];
 data=[[NSMutableData alloc]initWithCapacity:0];
 NSURLRequest *req=[NSURLRequest 
        requestWithURL:[NSURL URLWithString:[photourl objectAtIndex:imgnum]]
        cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
 conne=[[NSURLConnection alloc]initWithRequest:req delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
 [data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)nsdata{
 [data appendData:nsdata];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
 [self abort];
 NSLog(@"error.");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
 if(multiFlg){
  UIImage *img=[UIImage imageWithData:data];
  [imgArr addObject:img];
  [imgArr addObject:nwf(img.size.width)];[imgArr addObject:nwf(img.size.height)];[imgArr addObject:nwf(img.size.width/img.size.height)];
  if([imgArr count]/4<[photourl count]){
   imgnum++;[self loadPhoto];
  }
  else{ //tiling routine
   int curw=0,curh=0,k=0;
   for(int j=0;j<[imgArr count]/4;j++){
    if([[imgArr objectAtIndex:j*4+3]floatValue]<sr&&[imgArr count]/4>j+1){
     int over3=2;
     float firstw=ofv(imgArr, j*4+1),firsth=ofv(imgArr, j*4+2),firstr=firsth/firstw;
     float nextw=ofv(imgArr, (j+1)*4+1),nexth=ofv(imgArr, (j+1)*4+2);
     float newh=firstr*((sw*nexth)/(nextw*firstr+nexth));
     while (sw/newh<sr&&[imgArr count]/4>j+over3) {
      nextw=ofv(imgArr, (j+over3)*4+1),nexth=ofv(imgArr, (j+over3)*4+2);
      newh=(newh/sw)*((sw*nexth)/(nextw*(newh/sw)+nexth));
      over3++;
     }
     j+=1+(over3-2);
     UIGraphicsBeginImageContext(CGSizeMake(sw,curh+newh));
     if(mixedimg){[mixedimg drawAtPoint:CGPointMake(0, 0)];}
     while(k<j+1){
      float neww=newh*[[imgArr objectAtIndex:k*4]size].width/[[imgArr objectAtIndex:k*4]size].height;
      [[imgArr objectAtIndex:k*4]drawInRect:(CGRectMake(curw, curh,neww,newh))];
      curw+=neww;k++;
     }
     curh+=newh;curw=0;
     mixedimg=UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
    }else{
     UIGraphicsBeginImageContext(CGSizeMake(sw,curh+sw*[[imgArr objectAtIndex:j*4]size].height/[[imgArr objectAtIndex:j*4]size].width));
     if(mixedimg){[mixedimg drawAtPoint:CGPointMake(0, 0)];}
     float newh=sw*[[imgArr objectAtIndex:j*4]size].height/[[imgArr objectAtIndex:j*4]size].width;
     [[imgArr objectAtIndex:j*4]drawInRect:(CGRectMake(0, curh, sw, newh))];
     mixedimg=UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();
     curh+=sw*[[imgArr objectAtIndex:j*4]size].height/[[imgArr objectAtIndex:j*4]size].width;
    }
   }
   [ai removeFromSuperview];
   self.image=mixedimg;
   if(self.image.size.width/self.image.size.height<sw/sh)
    [self setFrame:CGRectMake((sw-sh*self.image.size.width/self.image.size.height)/2, 0, sh*self.image.size.width/self.image.size.height, sh)];
   else [self setFrame:CGRectMake(0, (sh-sw*self.image.size.height/self.image.size.width)/2, sw, sw*self.image.size.height/self.image.size.width)];
   [self abort];
  }
 }
 else{
  [ai removeFromSuperview];
  self.image=[UIImage imageWithData:data];
  if(self.image.size.width/self.image.size.height<sw/sh)
   [self setFrame:CGRectMake((sw-sh*self.image.size.width/self.image.size.height)/2, 0, sh*self.image.size.width/self.image.size.height, sh)];
  else [self setFrame:CGRectMake(0, (sh-sw*self.image.size.height/self.image.size.width)/2, sw, sw*self.image.size.height/self.image.size.width)];
  [self abort];
 }
}

できた。画像のURLが入った配列を引数にして初期化してやればちゃんと読む。複数でも大丈夫。あとはanimatedなgifをどう扱うか。imageIO? UIWebView?ちょっと調べる。

昨日の昼は十六番@長堀橋で十六番ラーメンセット。スープはかなり美味しいが、麺はどうだろう。尾道ラーメンって全部あんな感じなんかな。日清カップヌードル的な、って言ったら怒られるか。でもまた行きたい。
尾道ラーメン 十六番

2012年4月27日金曜日

バージョン管理

仕事が大変でなかなかxcodeで遊べない。
とりあえず画像を非同期取得する前に、バージョン管理の手法を勉強しとく。
このへんを参考?:
Xcode4でアプリのバージョン管理

昨晩は玉造の角力に行ったがなぜか閉まってた。前行った時も閉まってたしやめたんかな。で、急遽日本橋の小洞天へ。ワンタン麺が絶妙な塩分でうまい。今日の昼はまたまた清正で、今月20杯目。

2012年4月26日木曜日

非同期

メソッドの並列処理云々の前に、画像の読み込みを非同期にする必要があるな。
ここらへんを参考に:

[Objective-C][iPhone] 非同期で画像をロードするUIImageView
[iOS SDK]非同期通信で画像をロードする方法について
UIImageをサーバ経由で取得する方法

夜は難波の味仙で担仔麺。おでんみたいなかちかちな煮玉子が入っててなかなか新鮮。程よい辛さでうまかった。

2012年4月25日水曜日

xcode4.3.2

がapp storeからインストールできない。正常終了もできんし、なんか調子悪いなー。
とりあえずdevelopersのサイトからDLしてみる。
それが終わったら並列処理の勉強やな。

夜は麺家 静で中華そば煮玉子にライス。かなりうまい。次はにぼにぼいってみよう。

2012年4月24日火曜日

課題

UIや挙動なんかはなんとなく目処が立った。ちょこちょこ時間かければ完成するだろう。
あとやりたいけどちょっと調べないといけないことを書いとこう。

・GIFはUIWebViewで包む(これは多分やれそう)
・並列処理(パースしながらどうview作るか、view作りながらいつJSONとるか、pvArrをnow起点に前後10posts(?)であふれたやつをどう消すか→nilを代入?)
・JSONの重複回避、since_id算出ルーチン
・bluetoothキーボードでのオペレーション
・大量のfollowee、followerをどう選択させるか(table?)

うーん、結構残ってるな。。。まあ、ひとつひとつ潰していくしか無いな。今まで通り。

並列処理関連は、以下とか参考にしよう:

並列プログラミングガイド
NSOperationQueue スレッドと処理の関係
NSOperation 非並列実行モードと並列実行モードの使い分け

あと、詳解obj-c2.0も。

画面遷移

長押しで逆巻やlikeを出すようにして、デフォルトでは必要最低限のコントローラで遷移させる。ほぼ思惑通りの動きができた。満足。

 ctrlv=[[UIView alloc]initWithFrame:CGRectMake(0, 0, sw, sh)];[self.view addSubview:ctrlv];
 UIImage *nextimg=[UIImage imageNamed:@"next"];UIImage *nextonimg=[UIImage imageNamed:@"next_on"];
 bnext=[UIButton buttonWithType:UIButtonTypeCustom];bnext.alpha=0.7;
 [bnext setImage:nextimg forState:UIControlStateNormal];[bnext setImage:nextonimg forState:UIControlStateHighlighted];
 [bnext setFrame:CGRectMake(sw-nextimg.size.width-10, sh-nextimg.size.height-30, nextimg.size.width,nextimg.size.height)];
 [bnext addTarget:self action:@selector(npost) forControlEvents:UIControlEventTouchUpInside];
 prelong=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(dpl)];
 [bnext addGestureRecognizer:prelong];
 [ctrlv addSubview:bnext];
 UIImage *previmg=[UIImage imageNamed:@"prev"];UIImage *prevonimg=[UIImage imageNamed:@"prev_on"];
 bprev=[UIButton buttonWithType:UIButtonTypeCustom];bprev.alpha=0.7;
 [bprev setImage:previmg forState:UIControlStateNormal];[bprev setImage:prevonimg forState:UIControlStateHighlighted];
 [bprev setFrame:CGRectMake(sw-previmg.size.width-10, sh-previmg.size.height-80, previmg.size.width,previmg.size.height)];
 [bprev addTarget:self action:@selector(ppost) forControlEvents:UIControlEventTouchUpInside];

 UIImage *rblgimg=[UIImage imageNamed:@"reblog"];UIImage *rblgonimg=[UIImage imageNamed:@"reblog_on"];
 brblg=[UIButton buttonWithType:UIButtonTypeCustom];brblg.alpha=0.7;
 [brblg setImage:rblgimg forState:UIControlStateNormal];[brblg setImage:rblgonimg forState:UIControlStateHighlighted];
 [brblg setFrame:CGRectMake(sw-rblgimg.size.width-10-nextimg.size.width-10, sh-rblgimg.size.height-30, rblgimg.size.width,rblgimg.size.height)];
 [brblg addTarget:self action:@selector(rblg) forControlEvents:UIControlEventTouchUpInside];
 rllong=[[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(chngrl)];
 [brblg addGestureRecognizer:rllong];
 [ctrlv addSubview:brblg];
 UIImage *likeimg=[UIImage imageNamed:@"like"];UIImage *likeonimg=[UIImage imageNamed:@"like_on"];
 blike=[UIButton buttonWithType:UIButtonTypeCustom];blike.alpha=0.7;
 [blike setImage:likeimg forState:UIControlStateNormal];[blike setImage:likeonimg forState:UIControlStateHighlighted];
 [blike setFrame:CGRectMake(sw-likeimg.size.width-10-previmg.size.width-10, sh-likeimg.size.height-30, likeimg.size.width,likeimg.size.height)];
 [blike addTarget:self action:@selector(like) forControlEvents:UIControlEventTouchUpInside];
- (void)npost{
 if([pvlayer viewWithTag:now]==[pvArr lastObject]) return;
 [[pvlayer viewWithTag:now++] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 [bprev removeFromSuperview];
}
- (void)ppost{
 if(now==1){[bprev removeFromSuperview];return;}
 [[pvlayer viewWithTag:now--] removeFromSuperview];
 [pvlayer addSubview:[pvArr objectAtIndex:now-1]];
 if(now==1)[bprev removeFromSuperview];
}
- (void)dpl{
 if(now!=1) [ctrlv addSubview:bprev];
 else if([bprev isDescendantOfView:ctrlv])[bprev removeFromSuperview];
}
- (void)rblg{
 NSLog(@"reblog.");
}
- (void)like{
 NSLog(@"like.");
}
- (void)chngrl{
 if([brblg isDescendantOfView:ctrlv]){[brblg removeFromSuperview];[ctrlv addSubview:blike];[blike addGestureRecognizer:rllong];}
 else{[blike removeFromSuperview];[ctrlv addSubview:brblg];[brblg addGestureRecognizer:rllong];}
}

できた。でもコードがださいのよねー。素人まるだし。
reblog,likeなどの実装はまだ。でもだいぶ進んだなー。

2012年4月23日月曜日

UI

久々にfireworksをたちあげてインターフェイスの構築。なんとなくイメージは固まってきたかな。macとwindowsの行き来がめんどくさい。
昼は大阪大勝軒@日本橋でつけ麺。並400g中盛800g大盛1500gという量子力学的な麺量設定のため、なかなか中盛に手が出せない。でもうまい。

2012年4月22日日曜日

quote

photoはある程度目処が付いたので、quoteのviewを作成。とりあえずUITextViewを使う。文字が少ないと質素になるので、文字数とフォントサイズを反比例させた。ひねりの効いた短文などはなかなかインパクトがあってよい。

}else if([[typeArr objectAtIndex:i]isEqualToString:@"quote"]){ //quote
 NSString *text=cont;
 text=[text stringByReplacingOccurrencesOfString:@"<br/>" withString:@"\n"];NSRange r;
 while ((r=[text rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location!=NSNotFound)
 text=[text stringByReplacingCharactersInRange:r withString:@""];
 UITextView *tv=[[UITextView alloc]initWithFrame:CGRectMake(10, 20, sw-20, sh-20)];
 tv.text=text;tv.font=[UIFont fontWithName:@"Helvetica" size:[text length]>13?300/[text length]*(3-[text length]/150)+15:75];
 tv.textColor=[UIColor blackColor];tv.backgroundColor=[UIColor clearColor];
 tv.editable=NO;
 [postv addSubview:tv];
}

できた。あとは引用符の画像を付けてみる。

UILabel

松江に出張行ってきた。人生初島根。向こうではisemiya527でもやしラーメン、戻ってきてから鶴橋ののぼり屋で味付玉子入りラーメン。どちらもまずまず。
で、帰ってきてからblog_nameとcaptionの表示部分をちょこちょこ書いてみた。UILabel使ってこんな感じで。

UILabel *bname=[[UILabel alloc]init]; //blog_name
bname.text=[bnameArr objectAtIndex:i];bname.font=[UIFont fontWithName:@"Helvetica" size:10];
bname.textColor=[UIColor blackColor];bname.backgroundColor=[UIColor whiteColor];bname.alpha=0.5;
CGFloat bwidth=[bname.text sizeWithFont:bname.font constrainedToSize:CGSizeMake(sw,10) lineBreakMode:UILineBreakModeTailTruncation].width;
bname.frame=CGRectMake(0,0,bwidth,10);
[postv addSubview:bname];
if([capArr objectAtIndex:i]){ //caption
 NSString *capText=[capArr objectAtIndex:i];NSRange r;
 while ((r=[capText rangeOfString:@"<[^>]+>" options:NSRegularExpressionSearch]).location!=NSNotFound)
 capText=[[capText stringByReplacingCharactersInRange:r withString:@""] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
 UILabel *caption=[[UILabel alloc]init];
 caption.numberOfLines=0;
 caption.text=capText;caption.font=[UIFont fontWithName:@"Helvetica" size:10];
 caption.textColor=[UIColor blackColor];caption.backgroundColor=[UIColor whiteColor];caption.alpha=0.5;
 CGSize csize=[caption.text sizeWithFont:caption.font constrainedToSize:CGSizeMake(sw, 30) lineBreakMode:UILineBreakModeTailTruncation];
 caption.frame=CGRectMake(0,sh-csize.height,csize.width,csize.height);
 [postv addSubview:caption];
}

できた。
でもなんか無駄が多い気がする。もうちょいエレガントに書けんもんか。

2012年4月19日木曜日

viewWithTag

というのがあるのか。勉強になるなー。
[Objective-C]viewWithTagメソッドは入れ子になったビューにもアクセスできるみたい

ページ遷移するときremoveFromSuperviewしてからaddSubviewするか、逆でいくか。逆で行く方法で悩んでたけど、これでいけるんかな。あとで試す。

夜はメン太ジスタ@玉造でメン太そばとごはん小。無鉄砲とは大違いの素晴らしい炊き具合の米が塩っ気の多いスープ、味玉に合う。メン太そばの平麺ももうちょい固ければよいがうまい。満足。

大域変数2と無鉄砲

floatとかの変数はそのまま{}の外側に書けば普通に大域変数になるみたい。なーんだ。
とりあえず画像が複数の場合のタイリングを別メソッドにして書きなおした。あっさり動いて拍子抜け。

そんなことより今日は初めて無鉄砲@大国町(大阪本店)に行った。20人以上並んでて店に入るまで30分かかった。だから結構期待してたけど、うん。美味しくない。まずごはんはべちゃべちゃ。スープも、知らんかったとはいえ、普通の濃さであれはないよ。22杯/monthでラーメン食べてるから言わせてもらうけど、あれに並ぶ意味が分からん。自分的に食事を残すのは人生でやっちゃいけないことの上位に入るので(同じようなことを佐藤雅彦先生も書いていたが)、途中まで頑張ったけど、残した。あれで満腹になるのは人生損する気がした。普通に小十郎行っときゃよかった。

2012年4月16日月曜日

tiling routine

縦横比の異なる複数の画像を、なるべく効率的に隙間なく並べる、というか最終的にはアスペクト比を維持したまま画面からはみ出さないようリサイズするので、複数の画像を矩形に組み上げるルーチン、かな。をとりあえず動くようにしてみた。NSNumberが直接演算できないとか、配列の添字がobjectAtIndex:ってのに未だに慣れず、コードを綺麗に書けない。マクロもどうかなーってくらいの微妙な冗長さ。どうするのがいいのかな。

配列contArrにはtumblrAPIから返ってきたresponse.postsの中身が入ってるという前提で、以下のとおり:

1.photoの場合はtumblrAPIからoriginal_sizeのurlが配列で返ってくるので、高速列挙にぶちこんでサイズ、比率、自身のUIImageオブジェクトを配列に格納する。1postに画像が複数ある場合は当然複数入る。
2.画像を取り出して、画面の縦横比よりも縦長で且つまだ次の画像があれば、その二つが隙間なく画面の幅に収まるよう横に並べられる共通の高さを取得(newh)する。
3.それでもまだ画面の縦横比より縦長で且つまだ次の画像があれば、先のものと合わせて隙間なく画面の幅に収まるよう横に並べられる共通の高さを取得(newh)する。これは全部の画像をくっつけて縦長じゃなくなるまで続ける。”縦長”の定義は、画面の比率以外にも、”正方形より”縦長、”横2:縦1より”縦長などが考えられるが、現時点では画面比で。
4.既に同じように生成した画像があれば(配列に画像を残して横長になった場合は)、まずその画像をdrawAtPointしておく。
5.2~3で処理した画像をdrawInRectする。
6.複数画像を1つの画像に合体(getImage)する。
7.1postに画像が1つ、または画像が横長、または縦長でも配列最後の画像であれば、単に画面幅に合わせてリサイズし、既に同じように生成した画像があれば(配列に画像を残して横長になった場合は)、まずその画像をdrawAtPointしてからdrawInRectしてgetImageする。
8.最後に1post分の画像としてUIImageViewに貼ってアスペクト比を維持したまま画面からはみ出さないようリサイズ。

#define nwf(x) [NSNumber numberWithFloat:x]

- (void)makeTimeLine{
 UIScreen *uis=[UIScreen mainScreen];
 float sw=[uis bounds].size.width,sh=[uis bounds].size.height,sr=sw/sh;
 int i=0;
 for(id cont in contArr){
  if([cont isKindOfClass:[NSArray class]]){
   UIImage *mixedimg;
   NSMutableArray
   *imgSizeArr=[[NSMutableArray alloc]initWithCapacity:0],*imgRatioArr=[[NSMutableArray alloc]initWithCapacity:0],
   *imgSetArr=[[NSMutableArray alloc]initWithCapacity:0];
   for(id parts in cont){ //1.高速列挙にぶちこむ
    UIImage *img=[[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:parts]]];
    [imgSizeArr addObject:nwf(img.size.width)];[imgSizeArr addObject:nwf(img.size.height)];
    [imgRatioArr addObject:nwf(img.size.width/img.size.height)];
    [imgSetArr addObject:img];
   }
   int curw=0,curh=0,k=0;
   for(int j=0;j<[imgSetArr count];j++){
    if([[imgRatioArr objectAtIndex:j]floatValue]<sr&&[imgSetArr count]>j+1){ //2.画像の比率チェック
     int over3=4;
     float firstw=[[imgSizeArr objectAtIndex:j*2]floatValue],firsth=[[imgSizeArr objectAtIndex:j*2+1]floatValue],firstr=firsth/firstw;
     float nextw=[[imgSizeArr objectAtIndex:j*2+2]floatValue],nexth=[[imgSizeArr objectAtIndex:j*2+3]floatValue];
     float newh=firstr*((sw*nexth)/(nextw*firstr+nexth));
     while (sw/newh<sr&&[imgSetArr count]>j+over3/2) { //3.縦長の限りずっと
      nextw=[[imgSizeArr objectAtIndex:j*2+over3]floatValue],nexth=[[imgSizeArr objectAtIndex:j*2+over3+1]floatValue];
      newh=(newh/sw)*((sw*nexth)/(nextw*(newh/sw)+nexth));
      over3+=2;
     }
     j+=1+(over3/2-2);
     UIGraphicsBeginImageContext(CGSizeMake(sw,curh+newh));
     if(mixedimg){[mixedimg drawAtPoint:CGPointMake(0, 0)];} //4.既に合体画像がある場合
     while(k>j+1){ //5.複数画像を描画
      float neww=newh*[[imgSetArr objectAtIndex:k]size].width/[[imgSetArr objectAtIndex:k]size].height;
      [[imgSetArr objectAtIndex:k]drawInRect:(CGRectMake(curw, curh,neww,newh))];
      curw+=neww;k++;
     }
     curh+=newh;curw=0;
     mixedimg=UIGraphicsGetImageFromCurrentImageContext(); //6.複数画像を1つの画像に
     UIGraphicsEndImageContext();
     }else{ //7.画像が1つの場合は単に画面幅に合わせてリサイズ
      UIGraphicsBeginImageContext(CGSizeMake(sw,curh+sw*[[imgSetArr objectAtIndex:j]size].height/[[imgSetArr objectAtIndex:j]size].width));
      if(mixedimg){[mixedimg drawAtPoint:CGPointMake(0, 0)];}
      float newh=sw*[[imgSetArr objectAtIndex:j]size].height/[[imgSetArr objectAtIndex:j]size].width;
      [[imgSetArr objectAtIndex:j]drawInRect:(CGRectMake(0, curh, sw, newh))];
      mixedimg=UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      curh+=sw*[[imgSetArr objectAtIndex:j]size].height/[[imgSetArr objectAtIndex:j]size].width;
     }
    }
   UIImageView *imgset=[[UIImageView alloc]initWithImage:mixedimg]; //8.1post分の画像を生成
   if(mixedimg.size.width/mixedimg.size.height<sw/sh) [imgset setFrame:CGRectMake(0, 0, sh*mixedimg.size.width/mixedimg.size.height, sh)];
   else [imgset setFrame:CGRectMake(0, 0, sw, sw*mixedimg.size.height/mixedimg.size.width)];
   [self.view addSubview:imgset];
   }else{//NON-photo
    NSLog(@"%d / %@",i,cont);
   }
  i++;
 }
}

できた。
横に並べる場合の比率なんかはこれからテストする。
というか、複数画像の場合は別メソッドに抜き出して渡したほうがいいな。あとでやろう。

昨晩は日本橋の一蘭。うまく隙間時間に入れて待たずに食べれた。帰るときは結構な行列だった。今月10杯目。ペースが遅い。