こんにちは、@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の関係はこんな感じに表す事ができます。
点線は任意個・任意回を表します。
つまりこの場合は、ある一つの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階
まず、最も単純な場合を考えます。
この例の図は、とあるviewに3つのviewが乗っている状態を表します。それ以上下に続くことはないので、このviewは1階です。
この場合は調べれば普通に出てきますが
view.subviews.forEach { subview in
subview.removeFromSuperview()
}
というふうに、subviewそれぞれに対してremoveFromSuperview()
をかければできます。
パターン2:n 階
まず、このメソッドを考えます。
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込み)
通常では、パターン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
閲覧ありがとうございました。腹減りすぎて頭痛い。