0
1

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 3 years have passed since last update.

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

Last updated at Posted at 2020-11-20

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

この記事で分かること

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

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

Usage

そしてそれは、


self.view.removeAllSubviews()

これ一発でできます。

Extension

これがその中身です。


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

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

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

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

定義:階

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

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

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

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

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

パターン1:1階

スクリーンショット 2020-11-20 17.05.59.png

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

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

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

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

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

パターン2:n 階

n階の図の適当な例です。
スクリーンショット 2020-11-20 16.52.32.png

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


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

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

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

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

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


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

ちょっと冗長です。

パターン3:n 階(arrangedSubviews込み)

スクリーンショット 2020-11-20 16.53.06.png

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

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

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


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

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

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?