はじめに
SwiftUI書いてて、
ーーーーーーーーーーーーーーーーーーーーーーー
画像まじ変なことなるんやけどなんなん!!!!!
ーーーーーーーーーーーーーーーーーーーーーーー
ってなりますよね、わかります。
今回はそんなあなた(わたし)のために、ちゃんと向き合って
スケーリングマスターに!!なる!!
って話らしいです。
早見表
SwiftUIのmodifierには、以下のものがあります。
- scaledToFill
- scaledToFit
- aspectRatio
つこたことある!
ってなると思いますが、まぁまぁそう焦らずに。
それぞれどんなものかって軽く見ていきます。
自分なりに落とし込む際には、良い感じの表現にしてください。
早見表的にこの記事が使われてほしいため、ここで表にしておきます。
最初は下を読んだ後に見てもらえると!
(aspectRatioの簡易画像は、パターンによっていろいろあるしややこしくなるため、割愛)
scaledToFill | scaledToFit | aspectRatio | |
---|---|---|---|
アスペクト比 | 維持 | 維持 | 指定 (指定無しだと維持) |
スケーリング | 表示枠を覆い尽くす最小サイズ | 表示枠内に収まる最大サイズ | contentModeで指定 |
クリッピングの指定 | 必要 | 不要 | contentMode=.fillだと必要 |
簡易画像 | ![]() |
![]() |
- |
以下、本題です〜!
scaledToFill
アスペクト比:維持
比率を保って、表示枠を覆いつくす最小サイズにスケーリングする
コード
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.scaledToFill()
.frame(width: 300, height: 300)
.background(Color.green)
},
placeholder: {
ProgressView()
})
レイアウト

覆った上で、はみ出てます。
fill系は表示枠を覆い尽くすので、表示枠と画像比率が一致していない限り、縦横どちらかが表示枠からはみ出ます。が、デフォルトだとクリッピングされないので注意。
ここで、
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.scaledToFill()
.frame(width: 300, height: 300)
.clipped() // これを加えることで・・・!
.background(Color.green)
},
placeholder: {
ProgressView()
})
こうして、clipped()
を指定したら、

表示領域でクリッピングされました。
プロダクトではみ出たまま使うことはそうそうないと思うので、大体はこちらの形になるかと。
scaledToFit
アスペクト比:維持
比率を保って、表示枠内に収まる最大サイズにスケーリングする
コード
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
.background(Color.green)
},
placeholder: {
ProgressView()
})
レイアウト

ちなみにmodifierの順番を間違えると・・・
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.frame(width: 300, height: 300)
.scaledToFit() // frameの後にスケーリング指定に変更
.background(Color.green)
},
placeholder: {
ProgressView()
})

ほらーーーーーーーーーーー
SwiftUIにとってmodifierの順番は命なんだって、いつも言ってるじゃないですか!(ガミガミ)
はい、じゃあ次。
aspectRatio
アスペクト比:指定(デフォルトはnilで、比率維持になる)
contentMode
(fill or fit)を指定する
上記のscaledToFill,scaledToFitのようにスケーリングする
まずは
contentMode: .fill
コード
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 300)
.clipped() // クリッピング
.background(Color.green)
},
placeholder: {
ProgressView()
})
レイアウト
.clipped()指定

.clipped()指定せず

比率指定をしていないので、デフォルトのnilになる。
実質scaledToFillと同じ。
もちろん、クリッピングによる変化もscaledToFillと同じですね。
では次
contentMode: .fit
コード
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 300, height: 300)
.background(Color.green)
},
placeholder: {
ProgressView()
})
レイアウト

こちらも比率指定をしていないので、デフォルトのnilになる。
やっぱり、実質scaledToFitと同じ。
では最後に
比率指定したver
コード
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable()
.aspectRatio(1, contentMode: .fit)
.frame(width: 300, height: 300)
.background(Color.green)
},
placeholder: {
ProgressView()
})
レイアウト

比率を1に設定したので、画像が正方形にリサイズされます。
の上で、表示領域にfitします。
とまぁ、いろいろ見てきたわけですが、
じゃあどうするの?
ってところだと思うんです。
aspectRatioは比率指定できるけど、その比率に画像を縮めたり引き伸ばしたりしてしまう。
プロダクトで使用する画像をリサイズすることはそうそうないと思うので、基本はscaledToFit,scaledToFillで良いのではないか、と現時点では思っています。
こういうときは使えるで、等が出てきたら、その際はまた記事にでもしようと思います。
というのを踏まえまして、
SwiftUIでの実装
SwiftUIでの実装はこんな感じになりますね。
AsyncImage(
url: .init(string: "https://placehold.jp/600x120.png")!,
content: { image in
image
.resizable() // リサイズ可能に
.scaledToFit() // どのようにはめこむか
.frame(width: 300, height: 300) // 表示領域
.clipped() // はみ出た部分をクリッピング
},
placeholder: {
ProgressView()
})
使っては、変な形になっては調べる、みたいなことを繰り返していたので、きちんと整理してみた、のお話でした。
まとめ
プロダクト開発と画像表示は切っても切り離せない関係なので、この辺りは普遍的に使える技術かなと思います。迷った時にはこれを見て、さくっと実装してもらえれば!
cssではobject-fit
などがあり、他の言語でも似たようなことはあるはずなので、概念を落とし込んでおけば流用可能ですね〜すばらっ!
ああ、憧れのスケーリングマスターになりたい話でした!