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

## はじめに

SwiftUI書いてて、
ーーーーーーーーーーーーーーーーーーーーーーー

**画像まじ変なことなるんやけどなんなん！！！！！**

ーーーーーーーーーーーーーーーーーーーーーーー　　
ってなりますよね、わかります。
今回はそんなあなた(わたし)のために、ちゃんと向き合って
**スケーリングマスターに！！なる！！**
って話らしいです。

## 早見表

SwiftUIのmodifierには、以下のものがあります。

- scaledToFill
- scaledToFit
- aspectRatio

**つこたことある！**
ってなると思いますが、まぁまぁそう焦らずに。
それぞれどんなものかって軽く見ていきます。
自分なりに落とし込む際には、良い感じの表現にしてください。

早見表的にこの記事が使われてほしいため、ここで表にしておきます。
最初は下を読んだ後に見てもらえると！
(aspectRatioの簡易画像は、パターンによっていろいろあるしややこしくなるため、割愛)

| | scaledToFill | scaledToFit | aspectRatio |
|:-:|:-:|:-:|:-:|
| アスペクト比 | 維持 | 維持 | 指定 <br> (指定無しだと維持) |
| スケーリング | 表示枠を覆い尽くす最小サイズ | 表示枠内に収まる最大サイズ | contentModeで指定 |
| クリッピングの指定 | 必要 | 不要 | contentMode=.fillだと必要 |
| 簡易画像 | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/a259aef2-2825-2543-c436-910144cfc092.png" width="300"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/3b6f1eec-8cd7-2bc3-4b69-e882c4aadd12.png" width="300"> | - |

以下、本題です〜！


### scaledToFill

アスペクト比：**維持**
比率を保って、表示枠を覆いつくす最小サイズにスケーリングする

#### コード

```swift
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()
    })
```

#### レイアウト

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/2609af11-8ca6-ec05-3b74-3e151b74c87d.png" width="300">

覆った上で、はみ出てます。

:::note warn
fill系は表示枠を覆い尽くすので、表示枠と画像比率が一致していない限り、縦横どちらかが表示枠からはみ出ます。が、デフォルトだとクリッピングされないので注意。
:::


ここで、

```swift
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()`を指定したら、

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/a259aef2-2825-2543-c436-910144cfc092.png" width="300">

表示領域でクリッピングされました。
プロダクトではみ出たまま使うことはそうそうないと思うので、大体はこちらの形になるかと。

### scaledToFit

アスペクト比：**維持**
比率を保って、表示枠内に収まる最大サイズにスケーリングする

#### コード
```swift
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()
    })
```

#### レイアウト

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/3b6f1eec-8cd7-2bc3-4b69-e882c4aadd12.png" width="300">

ちなみにmodifierの順番を間違えると・・・

```swift
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()
    })
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/49924c4a-7b59-9dd7-0a85-aeccb5275093.png" width="300">

ほらーーーーーーーーーーー

SwiftUIにとってmodifierの順番は命なんだって、いつも言ってるじゃないですか！(ガミガミ)

はい、じゃあ次。

### aspectRatio

アスペクト比：**指定**(デフォルトはnilで、比率維持になる)
`contentMode`(fill or fit)を指定する
上記のscaledToFill,scaledToFitのようにスケーリングする

まずは

#### contentMode: .fill

##### コード

```swift
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()指定**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/a259aef2-2825-2543-c436-910144cfc092.png" width="300">

**.clipped()指定せず**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/2609af11-8ca6-ec05-3b74-3e151b74c87d.png" width="300">


比率指定をしていないので、デフォルトのnilになる。
実質scaledToFillと同じ。
もちろん、クリッピングによる変化もscaledToFillと同じですね。

では次

#### contentMode: .fit

##### コード

```swift
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()
    })
```

##### レイアウト

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/3b6f1eec-8cd7-2bc3-4b69-e882c4aadd12.png" width="300">

こちらも比率指定をしていないので、デフォルトのnilになる。
やっぱり、実質scaledToFitと同じ。

では最後に

#### 比率指定したver

##### コード

```swift
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()
    })
```

##### レイアウト

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81434/7fc447b6-5db7-77a5-95ff-09d141ece6eb.png" width="300">

比率を1に設定したので、画像が正方形にリサイズされます。
の上で、表示領域にfitします。

  
  
とまぁ、いろいろ見てきたわけですが、

### じゃあどうするの？

ってところだと思うんです。

aspectRatioは比率指定できるけど、その比率に画像を縮めたり引き伸ばしたりしてしまう。
プロダクトで使用する画像をリサイズすることはそうそうないと思うので、基本はscaledToFit,scaledToFillで良いのではないか、と現時点では思っています。
こういうときは使えるで、等が出てきたら、その際はまた記事にでもしようと思います。

というのを踏まえまして、

### SwiftUIでの実装

SwiftUIでの実装はこんな感じになりますね。

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