UIScrollViewで、読みやすいコンテンツに対応する


概要

読みやすいコンテンツを実現するガイドReadable Content Guide で、スクロールビューを使う場合の対応方法の説明になります。

Readable Content Guideについて

iOSには、Readable Content Guideというレイアウトガイドがあります。

例えば、テキストなどを表示する場合、iPhoneの場合は画面幅いっぱいにテキストを表示でいいけれど、iPadなどの大きい画面だと、左画面端にちょっとだけテキストが寄ってしまう場合があるかと思います。(Left To Rightの場合)

Readable Content Guideを使うと、デバイスや向きに応じて読むための最適化された領域でコンテンツを表示することができます。

iPadの横向きが特に顕著で、Readable Content Guideの有無で左右の隙間が変わり、文章が中央に寄って読みやすくなることがわかります。
デバイスや向きごとの隙間の調整もReadable Content Guideがやってくれます。

Readable Content Guideなし Readable Content Guideあり

https://developer.apple.com/news/?id=nixcb564
Keep your content readable の項目も参考にしてください。

Readable Content Guideの実装方法

Interface Builder

Interface Builderで対応する場合の手順は以下です。

  • Readable Content Guideに対応させたいビューのSuperviewに対してFollow Readable Widthのチェックを入れる
  • Readable Content Guideに対応させたいビューの制約 leading, trailingを Safe AreaではなくSuperviewのmarginに対してequalに設定し、Relative to marginにチェックを入れる

leadingの制約はこんな感じになります。

Readable Content Guide + UIScrollView (本題)

上記の文章はiPadだとしても長すぎるので、縦スクロールできるようにUIScrollViewに内包します。
しかし、ここで問題が起こります。

画面いっぱいにUIScrollViewを表示してコンテンツを縦スクロールする場合、以下のような構成になるかと思います。

  • 表示するコンテンツ(今回の場合、UILabel)を、Content Layout Guideに制約を合わせる
  • Frame Layout Guideは、表示するコンテンツの横幅にする

しかし、この場合Frame Layout Guideの横幅は画面幅そのものなので、Readable Content Guideを参照したものではなく、画面幅いっぱいにコンテンツが表示されます。
中のコンテンツがUIScrollViewのReadable Content Guideを参照することができません。

対応する

UIScrollViewの中にまずはコンテンツを示すViewを置き、Content Layout Guideの4方向とFrame Layout Guideの両脇に制約を設定し、コンテンツViewの中に、スクロールしたいコンテンツ(今回の場合UILabel)を、コンテンツViewのReadable Content Guideに合わせればOKです。

Interface Builderで対応する

UIScrollView直下にContentViewを置き、Content Layout Guideの4方向とFrame Layout Guideの両脇に制約を設定し、Follow Readable Widthのチェックをして、中のUILabelはSuperviewのmarginを参照しています。

コードで対応する

UIScrollViewには、コンテンツを示すcontentLayoutGuide、スクロールの枠を示すframeLayoutGuide、そしてreadableContentGuideのガイドがあるので、まずはコンテンツを示すビューをcontentLayoutGuideの4方向とframeLayoutGuideの両脇に設定し、コンテンツビューの中に、スクロールしたいコンテンツ(今回の場合UILabel)をreadableContentGuideに合わせればOKです。

        let scrollView = UIScrollView()
        let contentView = UIView()

        scrollView.addSubview(contentView)
        contentView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor),
        ])

        let label = UILabel()
        label.text = text
        label.numberOfLines = 0
        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.topAnchor.constraint(equalTo: contentView.topAnchor),
            label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            label.leadingAnchor.constraint(equalTo: contentView.readableContentGuide.leadingAnchor),
            label.trailingAnchor.constraint(equalTo: contentView.readableContentGuide.trailingAnchor),
        ])

表示

こんな感じで表示できます。

最後に

自動でデバイスや画面の向きに合わせてスペースを空けてくれる便利なReadable Content Guideですが、SwiftUIにはiOS 14.4の時点では対応されていないっぽいので、SwiftUIの場合はSize Classesに応じたマージンをとる対応が必要そうです。