全てのSubviewsを削除する方法【Swift 5】

こんにちは、@Zhalen です。読み方は「ツァーレン」です。

#この記事で分かること

>
「SubviewのSubviewのSubviewの... 」を全て消す
>

というめんどくさそうな処理を短いエクステンションで記述できます。

#Usage

そしてそれは、

```swift

self.view.removeAllSubviews()
```

これ一発でできます。

#Extension

これがその中身です。

```swift

extension UIView {
    func removeAllSubviews() {
        if type(of: self) == UIStackView.self {
            (self as! UIStackView).arrangedSubviews.forEach { (arrangedSubview) in arrangedSubview.removeAllSubviewsForEach() }
        }
        self.subviews.forEach { (subview) in subview.removeAllSubviewsForEach() }
    }
    func removeAllSubviewsForEach() {
        if type(of: self) == UIStackView.self {
            if (self as! UIStackView).arrangedSubviews.isEmpty {
                self.removeFromSuperview()
            } else {
                (self as! UIStackView).arrangedSubviews.forEach { (arrangedSubview) in arrangedSubview.removeAllSubviewsForEach() }
            }
        }
        if self.subviews.isEmpty {
            self.removeFromSuperview()
        } else {
            self.subviews.forEach { (subview) in subview.removeAllSubviewsForEach()
            }
        }
    }
}

```

#Mechanism

メカニズムは、こうです。


まずそれぞれのViewを丸、viewの上下関係を左から右の棒で表すと、一般的なviewの関係はこんな感じに表す事ができます。

![スクリーンショット 2020-11-20 16.58.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540507/6134aa92-ca3f-6449-c404-f7ba0e971c1d.png)

点線は任意個・任意回を表します。

つまりこの場合は、ある一つのviewに任意個のsubviewが乗っていて、さらにその関係が任意回繰り返されているという事です。

これをパターンごとに見ていき、最終的な結論(上記)に到達します。

###定義：階

大したことではないですが、ややこしさを防止するため単語を定義します。直感的には、viewの下に何重のviewが乗っているかということになります。

view単体のみで、何もsubviewが乗っていない状態を「０階のview」と定義します。
viewに（任意個の）subviewが乗っていて、それぞれが０階のviewの時、そのviewを「１階のview」と呼びます。

viewに（任意個の）subviewが乗っていて、その中に一階のviewが存在する時、そのviewを「２階のview」と呼びます。

一般に、
viewに（任意個の）subviewが乗っていて、その中に n-1階のviewが存在する時、そのviewを「n階のview」と呼びます。


・・・いや、逆にややこしいな（笑）
直感的には、図でいう左から右にかけて何重あるかということになります。

###パターン１：１階

![スクリーンショット 2020-11-20 17.05.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540507/7afae037-7b27-76e7-6527-65cdf05c41d1.png)

まず、最も単純な場合を考えます。

この例の図は、とあるviewに３つのviewが乗っている状態を表します。それ以上下に続くことはないので、このviewは１階です。

この場合は調べれば普通に出てきますが

```swift
view.subviews.forEach { subview in
    subview.removeFromSuperview()
}
```

というふうに、subviewそれぞれに対して```removeFromSuperview()```をかければできます。


###パターン２：n 階

n階の図の適当な例です。
![スクリーンショット 2020-11-20 16.52.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540507/41e34bdc-762b-f64c-bd6c-9a694491f9ab.png)


まず、このメソッドを考えます。

```swift

extension UIView {
    func removeAllSubviews() {
        guard !self.subviews.isEmpty else { self.removeFromSuperview() }
        self.subviews.forEach { (subview) in subview.removeAllSubviews() }
    }
}
```

このメソッドを実行すれば、
```もしsubviewsが存在すればそのそれぞれに対して同じ操作を繰り返す```
```もしsubviewsが存在しなければ、自分を消す```
という事になるので一番下の階層から順順に消していく事ができます。

従って、subviewsを全て消すことはできますが、<b>自分も一緒に消えてしまいます。</b>

ならば、一階層下のsubviewそれぞれに、このメソッドを実行させれば良いではないかとなる（```もし自分自身だったら消さない```だと、まず一番最初に自分自身をどこかに保存して残さなければならない。引数に指定すると、```view.removeAllSubviews(view)```と言ったように滑稽になる）

そうすると、こうなります。

```swift

extension UIView {
    func removeAllSubviews() {
        self.subviews.forEach { (subview) in subview.removeAllSubviewsForEach() }
    }
    func removeAllSubviewsForEach() {
        guard !self.subviews.isEmpty else { self.removeFromSuperview() }
        self.subviews.forEach { (subview) in subview.removeAllSubviewsForEach()
    }
}

```

ちょっと冗長です。


###パターン３：n 階（arrangedSubviews込み）

![スクリーンショット 2020-11-20 16.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540507/7caeb8bb-1a3f-6561-158e-1d7a52cc7844.png)

通常では、パターン２のメソッドで十分ですがもし、UIStackViewを使っていればそうはいきません。なぜならUIStackViewには```subviews```ではなく```arrangedSubviews```<b>も</b>指定しなければならず、その分の分岐をする必要があるからです。

例の図では、太い点線を```addArrangedSubview```によって追加されたものとの関係としました。

パターン２の場合に付け加えて分岐すれば良いのですから

```swift

extension UIView {
    func removeAllSubviews() {
        if type(of: self) == UIStackView.self {
            (self as! UIStackView).arrangedSubviews.forEach { (arrangedSubview) in arrangedSubview.removeAllSubviewsForEach() }
        }
        self.subviews.forEach { (subview) in subview.removeAllSubviewsForEach() }
    }
    func removeAllSubviewsForEach() {
        if type(of: self) == UIStackView.self {
            if (self as! UIStackView).arrangedSubviews.isEmpty {
                self.removeFromSuperview()
            } else {
                (self as! UIStackView).arrangedSubviews.forEach { (arrangedSubview) in arrangedSubview.removeAllSubviewsForEach() }
            }
        }
        if self.subviews.isEmpty {
            self.removeFromSuperview()
        } else {
            self.subviews.forEach { (subview) in subview.removeAllSubviewsForEach()
            }
        }
    }
}
```


となります。```guard```の部分を```if```として、```もしUIStackViewだったら、arrangedSubviewsそれぞれに対してremoveAllSubviewsForEachをかける```という分岐を追加しました。これで完成です

#Written by @Zhalen
閲覧ありがとうございました。腹減りすぎて頭痛い。