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の制限取っ払って欲しい。こんなトリッキーなことさせるほうがよっぽどサーバに負荷がかかる気がする。