Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What is going on with this article?
@k-kohey

【Swift】addSubview(_:)を子View毎に呼ぶのめんどくせえ~~~~~~~~【iOS】

More than 1 year has passed since last update.

めんどくせぇ~~~~~~~~~~~~~~~

暴れたくなる。

モチベーション

UIViewにおいて、子Viewを追加する際にaddSubview(_:)を使用するかと思う。
複雑なレイアウトになるとそれなりの数をaddSubviewをするかと思うし、階層構造が把握しにくくなる。
そして、addSubview(_:)をし忘れてAutoLayoutによる制約を付けたときにはランタイムエラーが起きる。
悲しい.

提案手法

追加するViewを一元的に管理し、一括で追加する。

for文を使って[hogehogeView, fugafugaView].forEach { addSubview($0) }とする方法も考えられるが、階層構造が複雑になったときに対応が難しくなる。
よって別の方法を模索する。

実装

AddSubviewAutomaticableで子Viewを持つことを確約させ、automaticAddSubviewsを用いて一括的にaddSubviewをする。
また、ここでaddSubviewをしようとしている対象が子Viewを持つときに対応出来ないので、withメソッドを追加した。

withもautomaticAddSubviewsのタイミングで走らせたいが良いアイデアが思いつかない😭

import UIKit

protocol AddSubviewAutomaticable {
    var childrenView: [UIView] { get }
    func automaticAddSubviews(_ parentView: UIView?)
}

extension AddSubviewAutomaticable where Self: UIViewController {
    func automaticAddSubviews(_ parentView: UIView? = nil) {
        childrenView.forEach { (parentView ?? view).addSubview($0) }
    }
}

extension AddSubviewAutomaticable where Self: UIView {
    func automaticAddSubviews(_ parentView: UIView? = nil) {
        childrenView.forEach { (parentView ?? self).addSubview($0) }
    }
}

extension AddSubviewAutomaticable where Self: UICollectionViewCell {
    func automaticAddSubviews(_ parentView: UIView? = nil) {
        childrenView.forEach { (parentView ?? contentView).addSubview($0) }
    }
}

extension AddSubviewAutomaticable where Self: UITableViewCell {
    func automaticAddSubviews(_ parentView: UIView? = nil) {
        childrenView.forEach { (parentView ?? contentView).addSubview($0) }
    }
}

protocol AddSubViewAutomaticableHelper {
    func with(children: UIView...) -> Self
}

extension AddSubViewAutomaticableHelper where Self: UIStackView {
    func with(children: UIView...) -> Self {
        children.forEach { addArrangedSubview($0) }
        return self
    }
}

extension AddSubViewAutomaticableHelper where Self: UIView {
    func with(children: UIView...) -> Self {
        children.forEach { addSubview($0) }
        return self
    }
}

extension UIView: AddSubViewAutomaticableHelper {}

使用例

import UIKit

final class SampleCollectionViewCell: UICollectionViewCell, AddSubviewAutomaticable {
    private let thumbnailImaeView: UIImageView()

    private let titleLabel = UILabel()

    private let containerView: UIView()

    private let effectiveView = UIVisualEffectView()

    private let notificationTimeLabel = UILabel()

    private let labelStackView = UIStackView()

    // こいつの子ViewはDynamicであるという想定
    private let friendsImageStackView = UIStackView()

    // contentViewにchildrenViewが追加される
    // contentViewは角丸かつ影があるという想定でcontainerView(雑)を挟んでいる。
    internal var childrenView: [UIView] {
        return [
            containerView.with(
                children:
                    thumbnailImaeView,
                    effectiveView,
                    labelStackView.with(
                        children:
                            titleLabel,
                            notificationTimeLabel
                    ),
                    friendsImageStackView
            )
        ]
    }

    override init(frame: CGRect) {
        super.init(frame: .zero)

        これで子Viewが追加されます: do {
            automaticAddSubviews()
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

おまけ

今回定義したメソッドと名前が衝突してしまうが,Thenを使えばより簡潔に書ける.

import UIKit
import Then

final class SampleCollectionViewCell: UICollectionViewCell, AddSubviewAutomaticable {
    // クロージャ内にてプロパティをセット.
    private let thumbnailImaeView: UIImageView().then { ... }

    private let titleLabel = UILabel().then { ... }

    private let containerView: UIView().then { ... }

    private let effectiveView = UIVisualEffectView().then { ... }

    private let notificationTimeLabel = UILabel().then { ... }

    private let labelStackView = UIStackView().then { ... }

    // こいつの子ViewはDynamicであるという想定
    private let friendsImageStackView = UIStackView()

    // contentViewにchildrenViewが追加される
    // contentViewは角丸かつ影があるという想定でcontainerView(雑)を挟んでいる。
    internal var childrenView: [UIView] {
        return [
            containerView.with(
                children:
                    thumbnailImaeView,
                    effectiveView,
                    labelStackView.with(
                        children:
                            titleLabel,
                            notificationTimeLabel
                    ),
                    friendsImageStackView
            )
        ]
    }

    override init(frame: CGRect) {
        super.init(frame: .zero)

        これで子Viewが追加されます: do {
            automaticAddSubviews()
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

まとめ

  • ProtocolExtensionを用いて、addSubviewするViewを階層構造を示しつつ、一元管理出来るようにした。
  • addSubviews(_ : [UIView])とか公式であっても良い気がするんだが、もしかしてこういった行為はAppleに禁じられている?
2
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
k-kohey
https://twitter.com/k_koheyi

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
2
Help us understand the problem. What is going on with this article?