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

 

 プロパティについては終わったので、viewDidLoadメソッドについて。

    override func viewDidLoad() {
        super.viewDidLoad()

        //  背景
        let backgroundImageView = UIImageView(frame:self.view.bounds)

 まずはbackgroundImageViewを作成する時に渡してるself.viewのboundsプロパティが何なのかから。self.viewが何かは「こんにちはデベロッパの世界」を参照。

 

 boundsプロパティはUIView自身の画面座標上での自分の矩形を表してます。

 frameプロパティは親側の画面座標上での位置なので、例えば矩形を、左上を親側の(x:100, y:50)の位置、縦横を200ポイントとするなら、frameプロパティのほうは(x:100, y:50, width:200, height:200)となりますが、boundsプロパティのほうは、左上を示す(x, y)座標が、親側の画面座標上での位置じゃなく、自分自身の座標上での位置なので(0, 0)となってることです。

 幅と高さは変わりません。

 

 

 

 

 今回の場合はself.viewのframe自体も、その親側の座標系で(0,0)を指定してるので、boundsとの違いはないんですが、上の図のように親側の左上からずれた位置に貼られている場合は、frameではなくboundsを使うのが正解ということになります。

 ま、こうすることでself.viewの矩形いっぱいの大きさで、backgroundImageViewを作ることができるわけですYO。

 

 続いて指定してるのが、親側UIViewの矩形が変更された時に、自分の矩形をどうするかの指定。

 それがautoresizingMaskプロパティの設定。

        ・・・
        let backgroundImageView = UIImageView(frame:self.view.bounds)
        backgroundImageView.autoresizingMask = [
                UIViewAutoresizing.flexibleWidth, 
                UIViewAutoresizing.flexibleHeight]

 これで、幅と高さについて親側の変形に合わせて調整するって指定になる。

 

 

 幅と高さ以外に上、下、左、右の4つをそれぞれ変形に合わせて調整する指定ができる。指定しなければ固定。

 初期状態は全てが固定になるので、その場合、親画面の左上からの位置、幅、高さは変わらない。

 

 

 今回のように幅と高さについて親側の変形に合わせて調整するって指定にしたのは、iPhoneの横置きに自動対応させるため。

 シミュレータだとHardware→Rotate LeftまたはRotate Rightメニューで画面が90度づつ回転します。

 

 

 画面を回転させた時に、self.viewの矩形自体はUIViewContollerが調整するんだけど、その子供UIViewに関しては各自で対応する必要があるんですな。

 autoresizingMaskプロパティはUIViewAutoresizingという構造体型で、事前に用意されたプロパティを指定する決まりになっとります。このプロパティは前回のクラスオブジェクトのプロパティと同じで

 

 型名.プロパティ名

 

で指定するようになってます。

 構造体の場合は、定義中でstaticってキーワードがついてる奴がこの手のプロパティです。

public struct UIViewAutoresizing : OptionSet {
	・・・
    public static var flexibleWidth: UIViewAutoresizing { get }

 まあ、もうUIViewAutoresizingは、なごり技なんですけどね…

 今はUIView画面位置の自動調整にAutoLayoutって別の機構を使います。実際、autoresizingMaskへの指定も、内部で、このAutoLayout機構に自動変換されてる。

 AutoLayoutは増え続けるiPhone/iPadの画面サイズに対応するために、iPhoneアプリのプログラマにとっては必須教養とも言える機構なんだけど、説明が長くなるので、ストーリーボード(こっちも、まだ話してない。ViewControllerオブジェクトが自動的に用意される仕組みなんですが、これも後で)の時に話します。

 

 ちなみに、このUIViewAutoresizing構造体の定義見て、あれ、OptionSetから派生してるんじゃん。構造体って派生できないんじゃなかったっけと思った人。

 鋭いです。

 

     ↓ どう言うことなのか、責任者はどこか?

    struct UIViewAutoresizing : OptionSet 

 

 実は、これは派生じゃなくてプロトコルの採用と言いまして、OptionSetは構造体でもなければクラスでもないんですな。

 OptionSetはプロトコルとして定義されてます。

 

プロトコル定義

 

 プロトコル定義は、「または私は如何にして心配するのを止めて…」で教えたXcodeのヘルプ機能を使って宣言を見てもらうとわかるけど、見た目は構造体やクラスの定義みたいに見えます。

 違いは、そこで宣言されたメソッドには、処理部分が用意されないってこと。処理部分はそのプロトコルを採用した側が用意する決まりです。

 今回ならUIViewAutoresizing側が用意する。

 オーバーライドしなければ派生元のメソッドが使えるというクラスの派生とは、この点が異なるわけです。

 このプロトコル定義ってのは結構重要で、いろいろな場面で出くわすことになるんですが、何でこんなものが必要なのかは、今度ViewControllerがプロトコルを採用する必要が出てきた時に話します。

 先送りって楽だ。

 興味が湧いた人は「iOS デリゲート プロトコル」あたりで検索してみましょう。

 

 UIViewAutoresizingに話を戻す。

 こいつはOptionSetプロトコルの採用によって、複数の値を一つにできるようになっていて、前回の配列の宣言時みたいに [ ] (ブラケットペア)で囲んで、設定したい値を , (カンマ)で区切って羅列することができます。

 

        backgroundImageView.autoresizingMask = [

                UIViewAutoresizing.flexibleWidth, 

                UIViewAutoresizing.flexibleHeight

        ]

 

 それと、autoresizingMaskに設定できるのはUIViewAutoresizing型って、わかってるので、UIViewAutoresizingを省略することも可能。ただしselfやsuperとは意味が違うので . (ドット)は省略できない。

 

        backgroundImageView.autoresizingMask = [

                .flexibleWidth, 

                .flexibleHeight

        ]

 

 でもって書き方も割と自由です。

 

        backgroundImageView.autoresizingMask = [

                .flexibleWidth,   .flexibleHeight]

 

 なんてしてもいい。自分(他人)が見やすいように字下げやスペースを調整しましょう。

 今更だけど、

    func tap() {
        if scriptIndex < script.count {
            message.text = script[scriptIndex]
            scriptIndex += 1
            if scriptIndex == script.count {
                UIView.animate(withDuration: 2, animations: { 
                    self.imageView.alpha = 1
                })
            }
        }
    }

なんて風に、メソッドの処理部 { } や if文の処理部 { } で字下げしてるのも、どっからどこまでが影響範囲か見やすくするためだけです。

 インデントを揃える、とかいいます。

 これは文法としてのルールじゃないで、望むなら

func tap() {if scriptIndex < script.count {
message.text = script[scriptIndex]
scriptIndex += 1
if scriptIndex == script.count {
UIView.animate(withDuration: 2, animations: { 
self.imageView.alpha = 1
})
}
}
}

なんて書いてもいいけど、友達なくします。自分を自分で嫌いになるかもよ。

 

 autoresizingMaskの後に指定してるcontentModeは、画像を矩形に合わせるかどうかの指定。

        ・・・
        backgroundImageView.autoresizingMask = [
                UIViewAutoresizing.flexibleWidth, 
                UIViewAutoresizing.flexibleHeight]
        backgroundImageView.contentMode = UIViewContentMode.scaleAspectFill

 元々の画像の縦横比を保ちつつ、矩形を埋めるのが.scaleAspectFill

 

 

.scaleAspectFill

 

 比率を無視して、矩形に合わせてしまう.scaleToFill

 

.scaleToFill

 

 比率を保ちつつ、矩形からはみ出さない最大の矩形で貼り付ける.scaleAspectFitとか色々あります。

 

.scaleAspectFit

 

 UIViewAutoresizingで説明したように、こいつもUIViewContentMode部分を省略可能です。

 

 続いて、セリフ付き前景画面では矩形を

 

   self.view.bounds.insetBy(dx: 10, dy: 10)

 

にしてます。insetByはCGRectのメソッドで、dx:、dy:がそれぞれ内側に何ポイント小さくするかの指定です。そしてその大きさの矩形が戻される。

 これでself.view.boundsの内側に10ポイントほど小さくなった矩形でbaseViewの矩形を設定してる。

 

 くだって、人物部分を作ってるところでやってるalphaプロパティの設定は、UIColorで説明したalphaと同じで不透明度の設定。

        ・・・
        imageView.image = UIImage(named: "girl")
        imageView.alpha = 0

 0にすることで、透明にしてます。そして出番が来たら不透明度を1にして表示してる。

 見える見えないはhidden(隠す:true、隠さない:false)ってプロパティもあるんだけど、じわっとフェードインさせたかったのでalphaプロパティを使ってます。

 そこらへんはアニメーション部分のところで改めて説明。

 

 セリフ部分(すりガラス)部分は、UIVisualEffectViewを使ってすりガラス風にしてる。でもってlayerプロパティで角丸矩形にしてるんですが、layerプロパティの型であるCALayerはかなり重要なクラスで画面描画根幹とも言えるクラス。

 その話は次回!