Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 248 - UIViewの初期化ステップ

2016年11月30日:説明を加筆*1

Viewの階層はどこから始まるのか?

この問題をちゃんと理解していないと後で困ってしまうのでしっかり理解しなければならない。

新規にXcodeプロジェクトを作ると、「var view: UIView?」はスーパービュー(superview)として作られます(下記のイメージは新規プロジェクトのドキュメント・アウトラインに表示されているスーパービュー)。

f:id:yataiblue:20161115154020j:plain

このジェネリックなsuperview(View)は、subviewをいくつも持てます。

  • var subviews: [UIView]

そして、このsbviewは、Xcodeで視覚的に作る(オブジェクト・ライブラリからドラッグ&ドロップ)のが一般的ですが、次のコードを使って加えることもできます。

  • addSubview(aView: UIView)  // 上位のViewに送ってaViewを加えます。
  • removeFromSuperview()  // 消したいViewで実行して上位のViewから消されます。

ビューコントローラーのプロパティとしてviewを操作することができます。コントローラの中で、「var view: UIView」と宣言して使うことができますが、実際はコードのみの記述は不十分です。Main.storyboard上のジェネリックなviewとコードは繋がっていないからです。ジェネリックなオブジェクトとコードを繋ぐために「Ctrl + ドラッグ」して「@IBOutlet」付きのプロパティとして使用しなければなりません。

viewプロパティ、これを理解することが基本であり重要です。画面をローテーションすると、boundsは変化します。Xcodeで視覚的に、またコードで加えることもできます。MVCモデルのV(View)の起源となるのが、このプロパティです。

UIViewの初期化ステップ

プログラミングをするうえでイニシャライザの知識は必須です。しかしながら、「継承」というクラスの特徴の1つがイニシャライザーを複雑にしてしまっているんです(-> Swiftで遊ぼう! - 407 - Initializer イニシャライザ - Swiftで遊ぼう! on Hatena)。iOS開発では数々のフレームワークが絡み合い複雑になっているため、可能な限りイニシャライザーを使用しないことが推奨されています。この理由も後で説明します。

XcodeでカスタムUIViewクラスを作る場合、メニューのFile > New > File...を選んで、次のいずれかのファイルを選びます。

f:id:yataiblue:20161115172146j:plain

このファイルどちらを選んでも全く同じ結果が得られます。ちょっと手間が違うだけなんです。「Cocoa Touch Class」を選んで継承すべきクラスとファイル名を指定してやると、自動的にサブクラスが作成され、オーバーライドすべき便利なメソッドも用意されます。そういう訳で、こちらでコーディングするスタイルが一般的なんです。例えば、UIViewクラスを継承したSubViewを作ると次のようなコードが用意されます。

//
//  SubView.swift
//  CustomViewProject
//
//  Created by 
//  Copyright © 2016年 
//

import UIKit

class SubView: UIView {

    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance 
    // during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

}

コメントアウトしているdraw()を使うことでイニシャライザを使用しないで描画の初期設定ができるんです。コードを書く前にもう一つXcodeで設定しなければならないことがあります。新規プロジェクト*2を作成するとジェネリックなViewが設定されます。これを独自に作ったSubViewに置き換える必要があります。以下のようにします。

f:id:yataiblue:20161116144959j:plain

これでdraw()メソッドを実装する準備が整いました。コメントを外して次のようにコードしてみます。レイアウト設定は無視するためシュミレータはiPhoneSEにします。

override func draw(_ rect: CGRect) {
        
    let label = 
        UILabel(frame: CGRect(x: 100, 
                              y: 150, 
                          width: 150, 
                         height: 20))
        
    label.text = "SubView TEST";
    self.addSubview(label);
        
}

UILabelクラスもUIViewをスーパークラスに持つためこのベースビュー(superview)にコードを使って加えることができます。使用するメソッドはaddSubview()です。

これをランすると次のようになります*3

f:id:yataiblue:20161116171351j:plain

draw()メソッドに関する情報は次のページにもあります。

yataiblue.hatenablog.com

しかし、UIViewの初期設定は、draw()メソッドを使うだけではありません。次はSwift Fileを使ってコーディングをしてみます*4。メニューのFile > New > File...からSwift Fileを選択してファイル名を指定すると、その名前はプロジェクト・ナビゲータのファイル名になります。今回はテストとして「CustomView」にしたので「CustomView.swift」ファイルがプロジェクト・ナビゲータに並びます*5

f:id:yataiblue:20161116174012j:plain

このファイルを選んでコードを見ると...

//
//  CustomView.swift
//  CustomViewProject
//
//  Created by 
//  Copyright © 2016年 
//

import Foundation

何もないファイルです。デフォルトで用意されているメソッドもありません。APIもFoundationしか使えないので、iOSの描画に必要な機能は全く継承されていません。しかし、心配ありません。プログラミングの達人になってくると、この状態からCocoa Touch Classファイルを選択した状態を簡単に実現することができます。次のように自分でコードを加えるたけです。

//
//  CustomView.swift
//  CustomViewProject
//
//  Created by 
//  Copyright © 2016年 
//

import Foundation
import UIKit

class CustomView: UIView {

    override func draw(_ rect: CGRect) {
        
        let label = UILabel(frame: CGRect(x: 100, 
                                          y: 150, 
                                      width: 150, 
                                     height: 20))
        
        label.text = "SubView TEST";
        self.addSubview(label);
        
    }

    
}

class CustomView: UIViewと、UIViewを継承したところで、draw()メソッドは継承されているんです。当然、イニシャライザも自動的に継承されます*6

UIViewのイニシャライザは2つ存在します。

  • init(frame: CGRect) // これはコード内でUIViewを作るときですね
  • init(coder: NSCoder) // これは全く初耳でした。ストーリーボードのオブジェクトライブラリからUIViewを作った時だそうです。まだcoderの意味が分かっていないんです。いつか分かる時がくるかな?

UIViewをイニシャライザで作る時、2つのイニシャライザを実装する必要があります。draw()メソッドと同じ表示をする場合次のようにコーディングします。

class CustomView: UIView {
    
    var label = UILabel(frame: CGRect(x: 100, 
                                      y: 150, 
                                  width: 150, 
                                 height: 20))
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    func setup() {
        // 独自に組み込む初期化ステップ
        label.text = "CusView TEST";
        self.addSubview(label);
        
    }
    
}

Main.storyboardのSubViewを選択して、アイデンティティ・インスペクタの「Class」を「CustomView」に切り替えて、からiPhoneSEシュミレータを動かすと次のようになります。

f:id:yataiblue:20161116180606j:plain

何となく理解していたUIViewの初期化ステップが理解できました。イニシャライザで初期化ステップをさせる場合、かなり知識が必要になります。どういうイニシャライザが継承されるか知らないと使えないからです。また独自にイニシャライザをカスタマイズしてサブクラスを作ると、それを継承する場合注意が必要になります。どんどん複雑になるためバグが潜みやすくなると思います。できる限りイニシャライザを使用せず、初期化メソッドの利用が推奨される所以なんです。

ここまで。

*1:2016年11月15日:間違って理解していた内容を訂正、2015年12月6日:Swift 2向けに改訂

*2:Single View Applicationを選択した場合です。

*3:何度もいうけどレイアウト調整はしていないので、iPhone5sの画面に合うようにCGRect値を決めています。

*4:わざわざ別ファイルを用意しますが、SubViewのコードを全部消去しても同じことなんです。

*5:ファイルの順序は適当に動かせます。

*6:Swiftで遊ぼう! - 407 - Initializer イニシャライザ - Swiftで遊ぼう! on Hatenaの中で説明している「Automatic Initializer Inheritance」です。