UILabelが角丸にならない！- clipsToBoundsとは何か？-

# はじめに
UILabelを角丸にしたいのに角丸にならない！という経験をしたことある人、結構多いんじゃないでしょうか？

```swift:こうやって書いてるのに！
label.layer.cornerRadius = 10
```

この時以下のように、1行追加すると途端に角丸になります。

```swift:正解
label.layer.cornerRadius = 10
label.clipsToBounds = true
```

`clipsToBounds=true`とは一体何なんでしょうか？

# UILabelとUIButtonの構造の違い

混乱の原因となっているのは`UIButton`の挙動で、こっちは1行で角丸になります。

```swift:これで角丸になる
button.layer.cornerRadius = 10
```

ここで、`UILabel`と`UIButton`の構造をHierarchyを用いて観察してみます。

以下のように、`layer.cornerRadius`のみ設定した状態だと画像のようになります。

```swift
label.layer.cornerRadius = 10
button.layer.cornerRadius = 10
```

![スクリーンショット 2024-11-28 17.48.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/e5e8aa53-77ab-8f5a-006d-43e54a21e268.png)


やはり`UILabel`だけうまく角丸になりません。

ここで実行中に以下のボタンをクリックすることで構造を見ることができます。

![スクリーンショット 2024-11-28 16.52.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/e877698f-639e-c000-8799-fa1fa38be443.png)

![スクリーンショット 2024-11-28 16.53.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/6d98240e-d94e-39f4-73cc-223f37cff85b.png)

ここで大事なのは、**UIButtonのclipsToBoundsもfalseである**こと。それは以下のように確認されます。

![スクリーンショット 2024-11-28 17.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/2d236d44-585a-bd43-84f9-a318d229056b.png)

### そもそもclipsToBoundsは何をしている？

[公式ドキュメント](https://developer.apple.com/documentation/uikit/uiview/1622415-clipstobounds)では、

> A Boolean value that determines whether subviews are confined to the bounds of the view.

とあります。

ここでの`bounds`とはViewの**枠**と捉えてよいので、（frameとは座標系の差しかない）、**Viewの枠の外側に要素を描画するかどうか**ということになります。

外側にはみ出るとはどういう場合か、については、以下の記事が大変参考になります。

https://qiita.com/orimomo/items/7075fb911da5c9d2fba5

では、根本的な原因は何なのでしょうか。

### 構造が異なることが原因

ここで大事なのは、**UILabelとUIButtonの層の数が異なる**ということです。
`UIButton`はこのように3つの層からできており、一番後ろのView（UIButton）のSubviewとして`_UISystemBackgroundView`と`UILabel`があることが確認できます。

![スクリーンショット 2024-11-28 16.56.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/7976114c-e9a3-f52f-b589-f5ed3ad0a1da.png)

![スクリーンショット 2024-11-28 17.00.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/1ca9f1e3-437b-57ea-3d37-357cf4fe6119.png)

一方`UILabel`は一つしか層がありません。

![スクリーンショット 2024-11-28 17.01.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/b4b7cf7c-15d5-44a0-eb8f-1e22e9a6a979.png)

さて、ここで以下の2行を追加して、`UILabel`のboundsを描画してみましょう。

```swift
label.layer.borderColor = CGColor(red: 1, green: 0, blue: 0, alpha: 1)
label.layer.borderWidth = 2.0
```

すると以下のようになります。

![スクリーンショット 2024-11-28 17.19.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/b20d9088-bb8e-ff51-9294-db6a4d414411.png)

したがって、**boundsはしっかり角丸になっているが、背景の要素がboundsをはみ出て描画されている**ことが原因だとわかります。

# UIButtonが角丸になる原因の考察

`UIKit`のパーツは内部構造がブラックボックス化されているため完全な理由は分かりませんが、Hierarchyからヒントをもらうことは可能です。

最下層のViewをクリックすると、以下のような情報が出てきます。

### UILabel

![スクリーンショット 2024-11-28 17.28.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/d85a71e8-6211-0ed6-ccbb-228ac9f60968.png)

### UIButton

![スクリーンショット 2024-11-28 17.29.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/8f9a1d4d-2cd9-f6de-1453-04c9115cae5b.png)

`UIButton`はただの`CALayer`で実装されていますが、`UILabel`は特注の`_UILabelLayer`なるもので実装されていることがわかります。

ここで、`dump(button.layer)`などしてみる（printでも可）と、`button.layer`でアクセスされるのはこの`CALayer`であることがわかります。

![スクリーンショット 2024-11-28 17.38.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823873/0bc04cca-1aeb-b395-9d89-b7387389eb30.png)

したがって、

- **UIButton.layer.cornerRadius**は最下層のlayerを角丸にするため、boundsどおり角丸になる
- **UILabel.layer.cornerRaduis**では、**_UILabelLayer**で背景とboundsが別の層にあると考えられ、bounds通りに角丸にならない

と考えられます。

# UILabelを角丸にする方法

原理を理解したところで、角丸にする方法をおさらいしましょう。

### layerを角丸にする

```swift
label.layer.cornerRadius = 10
label.clipsToBounds = true
```

また、**layerをboundsの形に合わせる**という処理として、

```swift
label.layer.cornerRadius = 10
label.layer.masksToBounds = true
```

ともかけます。

# まとめ
`UIView`の内部構造が秘匿されているため完全に原因を究明できないのが残念ですが、Hierarchyを駆使すればここまで構造を把握することができます。
ここまで理解できたら、**clipsToBounds**と書くのも苦じゃないですね！