読者です 読者をやめる 読者になる 読者になる

さらなる高みへ〜iOSのMERYでなめらかなスクロールを実現するためにやった4つのこと

こんにちは。iOSを主に担当していますアプリエンジニアのkazutoyoです。

MERYのアプリチームでは、チューニングを「さらなる高みへシリーズ」と名づけて、日々アプリの改善をしています。

今回はその中で行ったUITableViewやUICollectionViewのスクロール周りを滑らかにする改善についてやったことをご紹介したいと思います。

1. CALayerで角を丸くしている部分のパフォーマンスが悪い

item timeline

このようなカード型のViewが並んでいるCollectionViewがあったのですが、画像の角を丸くするのにCALayerで cornerRadius をつけているところのパフォーマンスがあまり良くないようでした。

これを次のようにCoreGraphicsでUIImageをmaskして角を丸めるようにしてやりました。

let imageRect = CGRectMake(0, 0, image.size.width, image.size.height)
UIGraphicsBeginImageContextWithOptions(image.size, false, 0)

// 上の角だけまるくする
let maskPath = UIBezierPath(roundedRect: imageRect, byRoundingCorners: [.TopLeft, .TopRight], cornerRadii: CGSizeMake(4.0, 4.0))

maskPath.addClip()
image.drawInRect(imageRect)

let roundedTopCornerImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

画像の加工も若干重い処理なのでパフォーマンスはそこまでよくないのですが、CALayerで角を丸めているよりもパフォーマンスが改善し、スクロールがカクつくことが少なくなりました。

2. 実際の表示に必要なサイズ以上の画像をUIImageViewにセットしている

ネットワーク上から読み込んだ画像など、実際の表示に必要なサイズより大きな画像の場合があります。

そのような画像をグリッドのCollectionViewなどで大量に表示される場合、多くのメモリが使われてしまう場合があります。

image grid

このようなときは画像をリサイズしてやってからUIImageViewにセットしてやるようにしました。

func resizeImage(image: UIImage, maxWidth: CGFloat) -> UIImage {
    // UIImageをリサイズする
    let sourceImage = CIImage(CGImage: image.CGImage!)
    let imageRect = sourceImage.extent
    let newSize = CGSizeMake(maxWidth, maxWidth / imageRect.size.width * imageRect.size.height)
    let scale = CGPointMake(newSize.width / imageRect.size.width, newSize.height / imageRect.size.height)
    
    var resizedImage = sourceImage.imageByApplyingTransform(CGAffineTransformMakeScale(scale.x, scale.y))
    resizedImage = resizedImage.imageByCroppingToRect(CGRectMake(0, 0, newSize.width, newSize.height))
    let context =  CIContext(options: [kCIContextUseSoftwareRenderer: false])
    
    return UIImage(CGImage: context.createCGImage(resizedImage, fromRect: resizedImage.extent))
}

3. 必要以外の場面でUIViewのアルファブレンドを使っていた

透過を持つViewの描画はパフォーマンスがあまりよくありません。

必要のない場面ではViewのDrawing設定のOpaqueの項目はチェックを入れておきましょう。

実際のアプリでどのViewがアルファブレンドを持っているか確認する方法として

  • iOS SimulatorのDebugメニューから「Color Blended Layers」を選択
  • InstrumentsのCore AnimationのDebug Optionから「Color Blended Layers」を選択

があります。

こちらを有効にすると次の画像のようにアルファブレンドをもつViewが赤く塗られます。

このオプションをつかってアプリ全体をチェックしてみると意外と必要のないアルファブレンドが見つかるのではないでしょうか。(MERYでは最初ほとんどが赤くなっていました)

4. UILabelを使わずにViewに直接テキストをdrawする

Instrumentsでいろいろ調べていると、UILabelが多く使われているCellのパフォーマンスがあまり良くないことがわかりました。

そこでNSStringのdrawInRectを使ってUILabelを使わないでテキストを描画するようにしました。

let font: UIFont = UIFont.systemFontOfSize(14)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 8

let attributes = [
    NSFontAttributeName: font,
    NSForegroundColorAttributeName: UIColor.grayColor(),
    NSParagraphStyleAttributeName: paragraphStyle
]

str.drawInRect(self.frame, withAttributes: attributes)

UILabelを使うより扱いにくくなるのですが、テキストを多く使うようなCellがある場合に効果があると思います。

まとめ

以上の改善により、MERY内のUITableViewやUICollectionViewを使っているところがサクサク動くようになりました。

smooth_scroll.gif

上のCollectionViewのスクロールではFPSが57~59あたりで動いています。(iPhone6で測定)

MERYのアプリではUITableViewやUICollectionViewといった多くのViewを一度に表示するような画面が多く使われており、この周りをチューニングするだけでアプリ全体の使い心地が大きく変りました。

細かいところではあるのですが、これらのことを気をつけることでより良いアプリになると思います。

MERYではエンジニアを募集しています!興味ある人はご応募ください!

© peroli, Inc.