LoginSignup
12
5

More than 1 year has passed since last update.

SwiftUIのViewThatFitsを使ってレスポンシブWebデザインっぽいレイアウトを実装してみる

Posted at

Webの世界では多くのWebページがレスポンシブWebデザインで実装されています。
例えばQiitaのホームページも下記のようにブラウザの横幅に応じてデザインが変わります。
スクリーンショット 2022-06-19 13.09.55.png
こちらのデザインは、横幅が小さいときは縦に並び、長いときは横に並ぶようになっています。

iOS SwiftUIの世界でもWWDC22で発表されたViewThatFitsを使ってWeb同様のレスポンシブデザインを実装できるようになったので試してみました。

ViewThatFitsとは

ViewThatFitsはWWDC22で新たに発表されたSwiftUIのレイアウトです。
Appleの例では以下のようなViewが紹介されています。
ViewThatFits-1@2x.png
この3つのViewは全て同じことを表現しているのですが、横幅がそれぞれ異なります。
紹介されているコードは以下になります。

struct UploadProgressView: View {
    @State var uploadProgress: Double

    var body: some View {
        ViewThatFits(in: .horizontal) {
            HStack {
                Text("\(uploadProgress.formatted(.percent))")
                ProgressView(value: uploadProgress)
                    .frame(width: 100)
            }
            ProgressView(value: uploadProgress)
                .frame(width: 100)
            Text("\(uploadProgress.formatted(.percent))")
        }
    }
}
VStack {
    UploadProgressView(uploadProgress: 0.75)
        .frame(maxWidth: 200)
    UploadProgressView(uploadProgress: 0.75)
        .frame(maxWidth: 100)
    UploadProgressView(uploadProgress: 0.75)
        .frame(maxWidth: 50)
}

2つ目のコードでmaxWidthをそれぞれ200、100、50で指定しています。
それぞれのwidthに応じて、ViewThatFits内で定義されたViewの中から最適なものが選ばれ、表示されているのが分かります。

このようにSwiftUI内でif文のような条件分岐を書くことなく、レイアウトを定義できるのがViewThatFitsとなります。

Qiitaのホームページのようなデザインを実装してみる

冒頭で紹介したQiitaのホームページのようなデザインをViewThatFitsを使って実装してみます。
実際に実装したものがこちらです。
16.48.07.png
スクリーンショット 2022-06-19 16.48.23.png
端末の向きがPortraitかLandscapeかでレイアウトが違うのが分かります。
ViewThatFitsを使って横幅が狭いときは1つ目のデザインでレイアウトされ、広いときは2つ目のデザインでレイアウトされるように実装しています。
実際のコードは以下のようになります。

struct ContentView: View {
    var body: some View {
        VStack {
            ProductView(productType: .qiita)
            ProductView(productType: .qiitaTeam)
        }
        .padding()
    }
}

ContentViewでは2つのProductViewをVStack内に配置しています。
ここでは、端末の横幅などを気にすることなく、配置しているだけです。

ProductViewは以下のようになります。

struct ProductView: View {
    let productType: ProductType
    var body: some View {
        GeometryReader { geometry in
            ViewThatFits(in: .horizontal) {
                // 画像とテキストが横に並ぶレイアウト
                HStack(spacing: 20) {
                    Image(productType.imageName)
                        .resizable()
                        .scaledToFill()
                        .frame(width: geometry.size.width / 2, height: geometry.size.width / 4)
                    VStack(alignment: .leading) {
                        Text(productType.title)
                            .font(.title)
                        Text(productType.caption)
                    }
                }
                .frame(minWidth: 400) // このレイアウトが使われる閾値

                // 画像とテキストが縦に並ぶレイアウト
                VStack(spacing: 20) {
                    Image(productType.imageName)
                        .resizable()
                        .scaledToFill()
                        .frame(width: geometry.size.width, height: geometry.size.width / 2)
                    VStack(alignment: .leading) {
                        Text(productType.title)
                            .font(.title)
                        Text(productType.caption)
                    }
                }
            }
        }
    }
}
enum ProductType {
    case qiita
    case qiitaTeam

    var imageName: String {
        switch self {
        case .qiita: return "Qiita"
        case .qiitaTeam: return "QiitaTeam"
        }
    }

    var title: String {
        switch self {
        case .qiita: return "Qiita"
        case .qiitaTeam: return "Qiita Team"
        }
    }

    var caption: String {
        switch self {
        case .qiita: return "Qiita は、エンジニアに関する知識を記録・共有するためのサービスです。"
        case .qiitaTeam: return "Qiita Team は、チームを強くするための 社内向け情報共有サービスです。"
        }
    }
}

ProductViewでは、ViewThatFitsの中に端末の横幅ごとのレイアウトを実装しています。
1つ目が横幅400以上のときに使われるレイアウトで、2つ目がそれ以下のときに使われるレイアウトです。
また、今回はGeometryReaderを使っているので、横幅400でレイアウトは分かれますが、それ以外の横幅でも横幅に応じたレイアウトがされるように実装しています。

今回は横幅に応じてレイアウトを変えたのでViewThatFits(in: .horizontal)と定義しています。
例ではiPhoneの向きだけを紹介しましたが、iPadへの対応も同様に可能です。

まとめ

WWDC22で紹介されたViewThatFitsを実際の例を用いて紹介しました。
今まで面倒だった端末の向き対応やiPad対応がやりやすくなると思います。
実際に使えるのはiOS16以降ですが、SwiftUIがどんどん強化されていき、元々のUIKitを使わなくても柔軟にデザインできるSwiftUIに期待が高まります。

12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5