Webの世界では多くのWebページがレスポンシブWebデザインで実装されています。
例えばQiitaのホームページも下記のようにブラウザの横幅に応じてデザインが変わります。
こちらのデザインは、横幅が小さいときは縦に並び、長いときは横に並ぶようになっています。
iOS SwiftUIの世界でもWWDC22で発表されたViewThatFits
を使ってWeb同様のレスポンシブデザインを実装できるようになったので試してみました。
ViewThatFitsとは
ViewThatFitsはWWDC22で新たに発表されたSwiftUIのレイアウトです。
Appleの例では以下のようなViewが紹介されています。
この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
を使って実装してみます。
実際に実装したものがこちらです。
端末の向きが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に期待が高まります。