30
24

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

複数回addSubviewを呼んだ時の挙動について

Last updated at Posted at 2015-07-18

概要

動的にUIViewに別のUIViewを追加したい場合には、addSubviewというメソッドが利用できます。

//view1にview2を追加したい場合
        var view1 = UIView(frame:CGRectMake(0, 0, 200, 200))
        view1.backgroundColor = UIColor.blueColor()
        var view2 = UIView(frame:CGRectMake(50, 50, 100, 100))
        view2.backgroundColor = UIColor.greenColor()
        view1.addSubview(view2)

Figure:1

では、view2view1以外のビューに対してaddSubviewを使って追加したらどうなるのでしょうか??

        //view1, view1Aにview2を追加したい場合
        var view1 = UIView(frame:CGRectMake(0, 0, 200, 200))
        view1.backgroundColor = UIColor.blueColor()
        var view1A = UIView(frame:CGRectMake(0, 300, 200, 200))
        view1A.backgroundColor = UIColor.redColor()
        var view2 = UIView(frame:CGRectMake(50, 50, 100, 100))
        view2.backgroundColor = UIColor.greenColor()
        view1.addSubview(view2)
        view1A.addSubview(view2)

スクリーンショット 2015-07-18 13.14.26.png

最後に追加したview1Aにのみ追加されるようです。
ということは、view1に追加されていたview2removeFromSuperviewによって削除されたのでしょうか?
view1にもview1Aにも追加されると思ってしまうケースも多いような気がします :sweat:

検証

Case1: 追加先を順々に変更してみる

見た目で挙動を確認するために次のようなコードを書いてみました。

        let averageHeight = CGRectGetHeight(UIScreen.mainScreen().bounds) / 11.0
        let averageWidth = CGRectGetWidth(UIScreen.mainScreen().bounds) / 2.0
        //5つviewを追加するためのviewを用意する
        var subview1 = UIView(frame: CGRectMake(averageHeight,
                                                averageHeight,
                                                averageHeight,
                                                averageHeight))
        subview1.backgroundColor = UIColor.blueColor()
        subview1.center = CGPointMake(averageWidth, subview1.center.y)
        var subview2 = UIView(frame: CGRectMake(averageHeight,
                                                averageHeight*3,
                                                averageHeight,
                                                averageHeight))
        subview2.backgroundColor = UIColor.greenColor()
        subview2.center = CGPointMake(averageWidth, subview2.center.y)
        var subview3 = UIView(frame: CGRectMake(averageHeight,
                                                averageHeight*5,
                                                averageHeight,
                                                averageHeight))
        subview3.backgroundColor = UIColor.redColor()
        subview3.center = CGPointMake(averageWidth, subview3.center.y)
        var subview4 = UIView(frame: CGRectMake(averageHeight,
                                                averageHeight*7,
                                                averageHeight,
                                                averageHeight))
        subview4.backgroundColor = UIColor.yellowColor()
        subview4.center = CGPointMake(averageWidth, subview4.center.y)
        var subview5 = UIView(frame: CGRectMake(averageHeight,
                                                averageHeight*9,
                                                averageHeight,
                                                averageHeight))
        subview5.backgroundColor = UIColor.blackColor()
        subview5.center = CGPointMake(averageWidth, subview5.center.y)

        //追加するビューを用意する
        targetView = UIView(frame: CGRectMake(averageHeight/4, averageHeight/4, averageHeight/2, averageHeight/2))
        targetView.layer.cornerRadius = 4.0
        targetView.backgroundColor = UIColor.purpleColor()
        NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: "moveTargetView:", userInfo: nil, repeats: true)
        viewList = [subview1, subview2, subview3, subview4, subview5]
        subview1.addSubview(targetView)

    func moveTargetView(timer:NSTimer) {
        //追加先ビューの変更
        currentIndex = (currentIndex >= viewList.count - 1) ? 0 : currentIndex + 1
        let view = viewList[currentIndex]
        view.addSubview(targetView)
    }

gif.gif

moveTargetView:がcallされる度にビューの位置が変わっていますね。
追加される先は1つのように見えます。

Case2: RetainCountの確認

以下の様なコードを書いて追加されたchildViewのRetainCountを計測してみました。

    UIView *parentView = [[UIView alloc] init];
    UIView *childView = [[UIView alloc] init];
    NSLog(@"Number of RetainCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)childView));
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    NSLog(@"Number of RetainCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)childView));

2015-07-18 13:00:03.168 test-objC[9989:328391] Number of RetainCount = 1
2015-07-18 13:00:03.170 test-objC[9989:328391] Number of RetainCount = 2

childViewのRetainCountはインスタンス生成 & parentViewへのaddSubviewの2回分しかないみたいです。

念のため、ParentViewsubviewsの数も確認してみました。

    UIView *parentView = [[UIView alloc] init];
    UIView *childView = [[UIView alloc] init];
    NSLog(@"Number of Subviews = %lu", (unsigned long)parentView.subviews.count);
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    [parentView addSubview:childView];
    NSLog(@"Number of Subviews = %lu", (unsigned long)parentView.subviews.count);

2015-07-18 13:03:16.407 test-objC[10052:330128] Number of Subviews = 0
2015-07-18 13:03:16.408 test-objC[10052:330128] Number of Subviews = 1

Case3: addSubviewをする先を変更した場合に[will|did]MoveToSuperviewを確認

例えば、view1にview2を追加した場合やview2をview1から削除した場合、view2に対して,
willMoveToSuperview(newSuperview: UIView?)didmoveToSuperView()が呼ばれます。
willMoveToSuperviewの第一引数がnilの場合は、superviewから削除された場合です。 確認するため、以下のようにView2`クラスのそれぞれのメソッドでログを吐くようにしてみました :bow:

        var view1 = UIView(frame:CGRectMake(0, 0, 200, 200))
        view1.backgroundColor = UIColor.blueColor()
        var view1A = UIView(frame:CGRectMake(0, 300, 200, 200))
        view1A.backgroundColor = UIColor.redColor()
        var view2 = View2(frame:CGRectMake(50, 50, 100, 100))
        view2.backgroundColor = UIColor.greenColor()
        view1.addSubview(view2)   //View2クラスのview2にview1を追加
        view1A.addSubview(view2)  //View2クラスのview2にview1Aを追加
    
class View2: UIView {
    override func willMoveToSuperview(newSuperview: UIView?) {
        let isExistSuperview = (newSuperview != nil) ? true : false
        println("willMoveToSuperview: \(isExistSuperview)")
    }
    override func didMoveToSuperview() {
        println("didMoveToSuperview")
    }
}

willMoveToSuperview: true
didMoveToSuperview
willMoveToSuperview: true
didMoveToSuperview

あれ。。。?! 呼ばれてないというかここは通らないよう...。
明示的にview1からview1Aに追加先を変更する前に、removeFromSuperviewを呼ぶと、やはりそれぞれ3回ずつ呼ばれました。

willMoveToSuperview: true
didMoveToSuperview
willMoveToSuperview: false
didMoveToSuperview
willMoveToSuperview: true
didMoveToSuperview

まとめ

  • 同じUIViewクラスのオブジェクトを別のUIViewクラスのオブジェクトに追加した場合、直前の追加先からは削除され、参照カウンタに変動はない
  • 暗黙的にremoveFromSuperviewが呼ばれているような挙動だが、willMoveToSuperviewなどは呼ばれない

ということでした :smiley:

30
24
4

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
30
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?