リブートキャンプ by Swift 目次

 

 前回説明したように、XcodeのSingle View Applicationテンプレートで用意されるアプリプロジェクトは、ViewController.swiftの加工を起点とするように設計されとります。オブジェクト指向言語であるSwiftのオーバーライド機能を利用して、元々の振る舞いを自分の処理に振り替えたりするんですわ。

 

 で、その実例が前回の、viewDidLoadメソッドの記述を数行の変更したプロジェクトというわけです。

 

 ↓viewDidLoadメソッドに追加記述したプロジェクトがこれ

http://tetera.jp/xcc/book-sample/test-hello.zip

 

 以下の3行の追加で元々の真っ白い画面に、文字列が表示されるようになってます。

 

 

 

 

 派生元であるUIViewControllerのviewDidLoadメソッドが、何をするものかというと、画面を表示する直前の最終画面調整っす。

 例の白い画面が用意されていて、表示される直前に呼び出されるメソッドなので、このタイミングで白い画面に、別の画面を貼り付けたりすることで、アプリが起動されて表示される画面のカスタマイズができるんですよ。

 

 プロジェクト作成直後のViewController.swiftでも、すでにviewDidLoadメソッドがオーバーライドされてるんですが内容は以下の通り。

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after 
    }

  ここで使われているsuperというキーワードは、メソッド内で自分自身にメッセージを送る時に使うキーワードの1つ。

 通常はselfというキーワードを使うか、送り先を省略することで自分自身を指定したことになるんだけど…

 

 

 そこにselfの代わりにsuperを使うと、送り先は自分自身に違いないが、呼び出されるメソッドは派生元であるUIViewController側のメソッドになるんですな。 

 

 

 結果、この1文を書くことで、派生元のUIViewControllerのviewDidLoadメソッドの処理を、そっくりそのまま真似ることができるわけだ。

 

 

 え、んじゃオーバーライドの意味ないじゃないすか、と思った人。

 大正解です。意味ないです。実質、派生元のUIViewController本来の作業をやってるだけなんで、用がなければ消しちゃってかまいません。

 このViewControllerクラスの定義でオーバーライドしてるviewDidLoadメソッドを、丸ごと消しても全然元気にアプリは真っ白い画面を表示します。

 

 じゃ、なんでこんな事してるかというと、さっき言ったように、たいていのアプリは、白い画面の上に自分たち独自の画面を表示するわけで、何もしないわけないんですよ。「オーバーライドすると思うんで、自分、準備しときました」という、Appleの親切心なわけですな。

 そういうわけで、viewDidLoadメソッドはプロジェクト作成直後からオーバーライドされてるし、中には、元の作業を再現するsuper.viewDidLoad()を書かれた上で「Do any additional setup…」つまり、なんか追加の設定作業ここに書けって感じのコメントがあるわけです。

 

 そして、実際にここに追加の画面を登録する処理を書き込んだのがこれ。

    override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel(frame: CGRect(x: 50, y: 100, width: 200, height: 44))
        label.text = "こんにちは世界"
        view.addSubview(label)
    }

 これが、白い画面の上に文字表示用の画面を貼り付ける作業になります。

 

 

 具体的には、画面を管理しているオブジェクトに、あなたの担当画面上に、子画面を貼り付けさせてくださいとメッセージする作業になる。

 

 画面を管理しているオブジェクトは、UIViewControllerオブジェクト(つまり自分のことでもある)が、プロパティとして持っているUIViewクラスのオブジェクト。

 

 

 で、そいつに、どうやって子画面を貼り付けるかというと、子画面側を管理する別のUIViewオブジェクトを用意して、UIView同士で親子関係を結べばいいんですな。

 それがプログラム中にも出ている

 

   親側UIViewオブジェクト.addSubview(子側UIViewオブジェクト)

 

というメッセージの送信。

 

 このメッセージで渡されたUIViewは、メッセージを送った先のUIViewの子供になります。で、画面上にも子供のなったUIViewが管理する画面が子画面として表示されることになる。

 

 

 

 そのためにUIViewは、自分の親画面上での矩形情報をframeプロパティとして保持します。

 

 

 

 

 今回は、シンプルに文字表示用の画面を、元からある画面に貼り付けただけなんでピンとこないかもしれないけど、大抵の場合は、子画面の中にまた子画面があるって感じになって、親画面が移動すると、子画面も一緒に移動するので、addSubviewで、どの画面と親子関係を結ぶかは画面レイアウトで重要な意味を持つことになります。

 

 

 

 

 

 ま、そこらへんは自分で経験積んでください。

 

 それはともかく、今回、子供にする文字列表示用画面を管理するオブジェクトはUILabelと言います。で、当然こいつはUIViewの派生クラスなんで、さっきのaddSubviewに渡すことができます。
 なんで?と思った人は前回から読み直すように。

 

 作るときには、上で言ったframeプロパティを設定するために、貼り付けたい位置や大きさを、矩形を表現するCGRectという構造体を作成して引数として渡すのが慣例。

 

 

 慣例通りするのが嫌なら、そのまま
 
  let label = UILabel()
 
とやって、後からframeプロパティを設定してもかまいません。
 
  label.frame = CGRect(x: 50, y: 100, width: 200, height: 44)
 
 いずれにせよ、frameプロパティはCGRectという構造体で指定します。
 で、この今回初登場の構造体て何?なんですが…
 ぶっちゃけ、プログラム始めたばっかなら、オブジェクトの一種、以上!
 てのでもいいんですが、一応、知識として知っておいた方がいいだろうから、ちょっと語ります。

 

構造体

 

 構造体は、クラスから作るオブジェクトとよく似ていて、structというキーワードで新しい型を定義し、その定義から作成するようになってます。

 

 

 プロパティやメソッドを定義することもできて、かなりクラスとまぎらわしいけど、大雑把にいうと、次の2点が異なる。
 

  1、派生できない。したがってメソッドのオーバーライドは不可能

  2、変数同様、値全体がコピーされる

 

 2の方は、クラスから作られるオブジェクトを設定した変数や定数がどういったものなのかを知らなければ、意味わからないでしょうな。

 

 実は、オブジェクトの変数や定数には、オブジェクトそのものではなく、オブジェクトを特定するための識別子が設定されます。

 オブジェクトのメアドが設定されるとでも考えればいい。メアドがわかればメッセージを送ることができるし、プロパティの値を問い合わせることもできる。

 

オブジェクトなら

    

    

 

 これに対し、構造体は構造体そのものが変数や定数に設定されます。

 

構造体なら

 

 

 この違いは、変数や定数のコピー時に影響を及ぼすわけで、次のような記述は、オブジェクトか構造体かで、実際の動作が異なることになるわけですよ。

 

   let pt = Point(x: 1, y: 2)

   let pt2 = pt

 

オブジェクトなら

   

 

構造体なら

    

 

 

 そのほか、次のようなコピー先のプロパティの値変更にも影響します。

 

   let pt = Point(x: 1, y: 2)

   let pt2 = pt

   pt2.x = 3

 

 まず、オブジェクトだと、定数として用意したpt2のプロパティxを変更することができる。変更不可なのは識別子の値であって、識別子が特定するオブジェクトではないから。

 この場合、識別子が特定するオブジェクトはptが特定するオブジェクトでもあるので、結果、pt2.xの変更はpt.xの変更にもなる。

 

オブジェクトなら

 

 

 これが構造体の場合、定数として用意したpt2のプロパティxを変更することはできない。変更したければpt2を変数として用意する必要がある。

 

  let pt = Point(x: 1, y: 2)

  var pt2 = pt            ← まず、変更可能にするには変数とする

  pt2.x = 3

 

 また、こうした場合、pt2.xの変更はpt.xには影響しない。

 

構造体なら

 

 

 クラスや構造体を定義する者は、このような特性の違いを考慮して、どちらにするか決定します。

 ま〜、いずれは、この手の知識も重要になってくるわけですよ。

 今は、そんなもんか程度でいいです。

 

 座標を表現するCGPointは派生の必要性はなさそうだし、値そのものがコピーされる方が扱いやすい。そういった点で構造体として用意されているんでしょう。

 Swiftが作られる前からObjective-Cで用意されてたから、名残っちゃ名残だけどObjective-Cでも、クラスじゃなく構造体になってるのは上記の理由と思われ。

 

 

ラノベ本の原型から抜粋(前回の続き)


 

 あとは、作ったUILabelオブジェクトのtextプロパティに、表示したい文字列を設定して、こいつをViewControllerが持つUIViewに、addSubviewで小画面として登録すれば、アプリの画面として文字列が表示される

 ViewControllerが持つUIViewはプロパティとして用意されている。

 自分(または派生元)が持っているプロパティは、そのことを明示するためにselfを使う場合もあるが、省略されている場合も多い。

 ViewControllerのviewDidLoadメソッドは、UIViewControllerが画面表示用オブジェクトを用意した直後に呼び出され、UIViewControllerがやるべきことをやった後で、画面文字列表示用オブジェクトであるUILabelを作成して追加するという処理を行っていることになる。

 

 

 この理解で正しいなら、このviewDidLoadメソッド内の処理を、同じようにtestプロジェクトのViewControllerでやれば、同じように画面に文字列が表示されるはずだった。

 試してみましょうと安堂が、サンプル側のviewDidLoadメソッドの処理をtestプロジェクトのviewDidLoadメソッドにコピー&ペーストする。

 そしてRun。

「こんにちは世界」

 シミュレータが立ち上がり、表示されたtestアプリの真っ白い画面に、くっきりと文字列が表示された。

 


 

 ↓viewDidLoadメソッドに追加記述したプロジェクトがこれ

 

http://tetera.jp/xcc/book-sample/test-hello.zip

 

 文字列表示用のUILabel以外にも、画像表示用のUIImageViewとか、いろいろなUIView派生クラスがあります。

 そこらへんを先駆者がアップしてるサンプルプロジェクトを見たり、Appleのデベロッパページに行って調べたりして、iPhoneアプリを作っていくわけですな。

 

 ↓ここね

 

https://developer.apple.com/jp/

 

 こんにちはデベロッパの世界。

 でわでわ。