自前のUITableViewCellを作ろうとしてハマった記録。
そもそも何がしたかったか
AVPlayerLayerをのせたcustom UITableViewCellを作りたかった。UITableViewCell一つ一つにAVPlayerのUIViewが表示されている感じ。既存アプリだとVineのイメージが近い。以下の画像のように動画をUITableViewCellに直接貼りたかった。
何がまずかったか
UITableViewCellのxibを作りレイアウトを構成して、初期化の中でAVPlayerLayerを生成した。
- (id)initWithIndexPath:(NSIndexPath*)indexPath{
self = [super init];
if( self ) {
NSURL *movieURL = @"動画ファイルの場所"
AVPlayer *player = [[AVPlayer alloc] initWithURL:movieURL];
AVPlayerLayer* layer = (AVPlayerLayer*)playerView.layer;
layer.videoGravity = AVLayerVideoGravityResizeAspect;
layer.player = videoPlayer;
label.text = [NSString stringWithFormat:@"Movie %d",indexPath.row];
}
return self;
}
xibで構成したのはUILabelとAVPlayerViewの2つ。AVPlayerViewが何かっていうと詳しくはここ
これでUILabel、動画ともにうまく表示される。
だが、しばらくスクロールしていると、AVPlayerViewが消える。
正確にいうと、見えなくなる。原因ははっきりとしていない。何度やっても10回くらいスクロールを行うと消える。UILabelの方は消えない。
よくわからなかったが調べていくうちに以下の3つの仮説をたてた。
- Cellは再利用されるためCell固有のプロパティなどはControllerの方で持っておいた方がよい
- dequeueができずにinitする回数が多かったのでメモリ不足
- UILabelは大丈夫でAVPlayerViewが消えるのでAVPlayerViewの問題
3つめに関してはAVPlayerViewを使わざるを得ないので、これを変更することはできない。1つめと2つめ解決するためにAVPlayerはController側で持つことにした。
- (id)initWithIndexPath:(NSIndexPath*)indexPath andVideoPlayer:(AVPlayer *)videoPlayer{
self = [super init];
if( self ) {
AVPlayerLayer* layer = (AVPlayerLayer*)playerView.layer;
layer.videoGravity = AVLayerVideoGravityResizeAspect;
layer.player = videoPlayer;
label.text = [NSString stringWithFormat:@"Movie %d",indexPath.row];
}
return self;
}
だいぶスッキリした。だがこれでも消える。さっきまでとほぼ同じ回数のスクロールで消える。根本的な解決になってないようだ。
とここでInterface Builder使っているのがまずいんじゃないかと勝手に想像。毎回コード側でaddSubViewとかしたら完璧になるのではと思った。
毎回addSubViewしてみた
- (id)initWithIndexPath:(NSIndexPath*)indexPath andVideoPlayer:(AVPlayer *)videoPlayer{
self = [super init];
if( self ) {
AVPlayerLayer* layer = (AVPlayerLayer*)playerView.layer;
layer.videoGravity = AVLayerVideoGravityResizeAspect;
layer.player = videoPlayer;
// 一回一回addSubViewを行う
[self.contentView addSubview:playerView];
label.text = [NSString stringWithFormat:@"Movie %d",indexPath.row];
}
return self;
}
なんと!!消えない!!何回スクロールしても消えない!!
これは使えると思った。
甘かった。
カクカク動く。ものすごい動きが悪くなる。どこがボトルネックなんだろうとコメントアウトしたりして探ってみたら、このaddSubViewの処理はめちゃめちゃ重たいみたいだ。ここを消すだけでぬるぬる動く。うーんどうしたものか。
別スレッドで動かしてみたら動きは改善されるんじゃなかろうか。
GCDを使う
- (id)initWithIndexPath:(NSIndexPath*)indexPath andVideoPlayer:(AVPlayer *)videoPlayer{
self = [super init];
if( self ) {
AVPlayerLayer* layer = (AVPlayerLayer*)playerView.layer;
layer.videoGravity = AVLayerVideoGravityResizeAspect;
layer.player = videoPlayer;
label.text = [NSString stringWithFormat:@"Movie %d",indexPath.row];
dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t q_main = dispatch_get_main_queue();
dispatch_async(q_global, ^{
dispatch_async(q_main, ^{
[self.contentView addSubview:playerView];
});
});
}
return self;
}
このコードが良いのかはわからないけれど、重たいとおもわれる処理を非同期で実行されるようにした。祈った。
だめだった。何も変わらない。
何も変わらないということがあるとは想像してなかった。
せめて何か変わって欲しかった。
結局どうしたか
結局毎回addSubViewをするパターンでいくことにした。UITableViewControllerでぬるぬる動かすことはできないので、Swipe動作とコード側でのスクロールで一つ一つのCellを断続的に動かすことにした。これはこれであんまり見ないUIだし、悪くないと思って満足している。
ただAVPlayerViewが消えてしまった原因は未だによくわからないし、解決できなかったので、誰か何かご存知の方がいたら教えてください。お願いします。
Vineが裏で何を動かしているのかわからないけど、Vineにできるなら何かしら方法はきっとあるのだろう。
追記
AVPlayerViewはアプリを一度バックグラウンドにしてからもう一度表に出すと勝手にまた表示されるようになっていた。その時に何が起きているのか知ることができれば、それを定期的に実行すれば消えたとしても再表示できるのではないかとも考えた。結局、フォアグランドになったときにアプリのなんのコードがUITableViewControllerやUITableViewCellに対して呼ばれているのかわからずじまいだったのだが。。。