19
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftUIのscaledToFill,scaledToFit,aspectRatioと向き合ってスケーリングマスターになる

Last updated at Posted at 2023-06-06

はじめに

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などがあり、他の言語でも似たようなことはあるはずなので、概念を落とし込んでおけば流用可能ですね〜すばらっ!
ああ、憧れのスケーリングマスターになりたい話でした!

19
7
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
19
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?