長々と脱線したけど、ようやく今回で「または私は如何にして心配するのを止めて…」で公開したサンプルの説明も終わりっす。

 

 ↓これね、忘れた?

 

サンプル:

http://tetera.jp/xcc/book-sample/RPG.zip

 

 「CALayerで完璧」でのUIFont.preferredFontの説明から、「ダイナミックな文字」、「アンラップしてチン♪」、「ダイナミックなレイアウト」と脱線して、今ここなわけですが、まあ脱線中の解説で、numberOfLinesプロパティの意味とかもわかったよね?

 5は適当な数字だったわけだ。0でお任せが一番簡単かな。

 

 ↓イマココ

        ・・・
        //  セリフ部分(文字)
        let message = UILabel(frame:frame.insetBy(dx: 30, dy: 30))
        message.font = UIFont.preferredFont(
                forTextStyle:UIFontTextStyle.title1)
        message.numberOfLines = 5
        ・・・

 で、残すは、セリフ付き前景部分をタップに反応させる部分の説明ってことになります。

 

 

 

 それが次に示す部分で、タップを見張るオブジェクトUITapGestureRecognizerを作成して、そいつをbaseView(セリフ付き前景部分)に登録してます。

 

UITapGestureRecognizer

 

 これでbaseViewがタップされると、tapメソッドが呼び出されるようになる。

        ・・・
        //  セリフ付き前景
        let baseView = UIView(frame: ・・・
        ・・・
        //  タップに反応するようにする
        baseView.addGestureRecognizer(
                UITapGestureRecognizer(target: self, action: #selector(tap)))
        ・・・
    }
    //  場面の段階を1つ進め、それに合わせ、セリフや状況を更新する。
    func tap() {
        ・・・
    }

 タップを見張るオブジェクトの作成:

 UITapGestureRecognizer(target: self, action: #selector(tap))

 

 baseViewへの登録:

 baseView.addGestureRecognizer(・・・)

 

 てことなんですが、UITapGestureRecognizer(タップを見張るオブジェクト)の作成時の引数を見て、あれ、これって「ダイナミックな文字」でやった通知そっくりじゃね?と思った人もいるんじゃないでしょうか。

 基本、通知と同じで、タップ時に連携するオブジェクトと呼び出すメソッドを指定してます。

 ただし、こっちは連携の関係が、通知センタとオブジェクト間じゃなく、オブジェクトとオブジェクト間ってことになる。

 

 

 で、Appleでは、この2つの仕組みを区別する意味で、通知センタを使うやり方を「通知」、そうでないものを「ターゲットアクションデザインパターン」て呼んでます。

 通知センタを間に挟むと、やり取りする相手が不定になる点を区別してるんじゃないかと思われ。

 

 

 とにかくこれで、UITapGestureRecognizerオブジェクトはタップを検出するとself(ViewControllerね)のtapメソッドが呼び出すようになるわけです。

 で、このUITapGestureRecognizerオブジェクトを引数に指定して、UIView派生オブジェクトにaddGestureRecognizerメッセージを送ることで、そのUIView派生オブジェクトが表示してる画面に対するタップを見張るようになるんですな。

 

 baseView.addGestureRecognizer(・・・)  ← これ

 

 ジェスチャーを見張るオブジェクトは、このaddGestureRecognizerメッセージを使ってUIViewにどんどん追加できます。

 追加するオブジェクトはUIGestureRecognizerを派生したオブジェクトなら何んでもOK。

 タップを見張るUITapGestureRecognizerの他に、ドラッグやスワイプ、ピンチジェスチャ(Safariの画面ズーム時なんかに2本の指を縮めたり広げたりするやつね)を見張るオブジェクトなんかもあります。

 

 ちなみに、UIViewのようなiPhone側が提供するクラスには、Appleが決めた命名規則があって、add〜って名前は、引数のオブジェクトが追加登録できる場合に使われるみたいっす(addSubviewとか)。ここら辺はSwiftの決まりじゃないので守らなくてもエラーにはならないけど、わかりやすいので自分のクラス定義なんかでも積極的に使っていきましょう。

 

 作成時にaction:引数に指定できるメソッドは、今回のtapメソッドのような引数なしか、ジェスチャを検知したUIGestureRecognizer派生オブジェクトを受け取るという、2種類のいずれか。

 

  ↓こんな感じ

 

 func 好きな名前()

 

 func 好きな名前(_ gestureRecognizer:UIPanGestureRecognizer)

 注意)型の部分は、派生クラスの型にするのが一般的。UIPanGestureRecognizerはドラッグを検出するUIGestureRecognizer派生クラス。

 

 ドラッグなんかではドラッグ開始位置や現在の指の位置なんかを知るために、引数付きのメソッドにして、受け取ったUIGestureRecognizer派生オブジェクトから情報を取り出すことになります。

 今回はタップで無条件実行なので引数なしを指定した。

 メソッドを指定する#selector()の使い方がわからん人は「ダイナミックな文字」を読み直すように。

 それと、今回はUITapGestureRecognizerがself(ViewController)より先に消えることはないので、通知の時のように登録解除の必要はないです。

 今回のUITapGestureRecognizerオブジェクトが消えるのは、登録先のUIViewオブジェクト(セリフ付き前景部分)が消える時で、そのUIViewオブジェクトが消えるのはself.viewが消える時で、self.viewはViewControllerが消す消さないを決めてるので、ViewControllerが消える前にUITapGestureRecognizerが消えることはない。

 

 で、いよいよ残すはtapメソッドでの処理

    var imageView:UIImageView!      //  人物用
    var message:UILabel!            //  セリフ・説明用
    //  セリフ・説明
    let script = [・・・
    var scriptIndex = 0             //  現在の場面の段階
    ・・・
    override func viewDidLoad() {
        ・・・
        //  スタート
        self.message.text = script[scriptIndex]
        scriptIndex += 1
    }
    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
                })
            }
        }
    }

てわけだけど、ま〜「色々と脱線」の配列の説明で大体わかるよね。

 scriptIndexプロパティは、次に表示すべき文字列が、文字列配列(scriptプロパティ)の何番目の要素かを示してます。

 なので、tapメソッドでは、最初のifで、scriptの要素数とscriptIndexを比較、小さいならまだ表示してない文字列要素があるってことで、その文字列をscriptから取り出してメッセージ画面であるmessageのtextプロパティに設定してることになる。

 tapメソッド内では、scriptIndex、script共に、めんどくさいのでselfつけるのを省略してます。

 

 

 これで、scriptIndexが示す番号の文字列は表示済みになったので、scriptIndexの値を1つ大きくして、次の番号に更新。

    func tap() {
        if scriptIndex < script.count {
            message.text = script[scriptIndex]
            scriptIndex += 1  ←次の番号に更新
            ・・・
        }
    }

 で、2番目のifで、表示した文字列が最後の要素だったかを確認し、最後の要素だったら、イメージ画面(imageViewプロパティ)をアニメーションで表示させる。

    func tap() {
            ・・・
            ↓ 要素数と一致するなら、設定したのは
              最後の要素だったということ
            if scriptIndex == script.count {
                UIView.animate(withDuration: 2, animations: { 
                    self.imageView.alpha = 1
                })
            }
        }
    }

 この時に使ってるのがUIViewのalphaプロパティで、こいつはUIColorのalphaプロパティ同様、透明度を指定してます。UIViewの場合は画面の透明度となり、0.0(透明)〜1.0(完全に不透明)の間を指定できて、0.1、0.2、0.3、...といった具合に時間をかけて徐々に値を変更してやれば、今回のようにじわじわと画像が現れるとこになるわけですよ。

 

UIViewのアニメーション指定

 

 で、この「時間をかけて徐々に値を変更」を依頼してるのがUIViewクラスオブジェクトへのanimateメッセージと、そのanimations:引数に指定してる {・・・} 部分な訳ですよ。

 

 UIView.animate(withDuration: 2, animations: {・・・} )

 

 withDuration:引数の方は、何秒かけてアニメーションするかの指定です。

 でもってanimateメッセージでは、animations:引数に渡された処理で行われているUIViewのプロパティへの設定は、アニメーションでおこなうって約束になってるんですわ。この処理を記述してるのが {・・・} という部分。今回なら

 

  {

   self.imageView.alpha = 1

  }

 

てことで、これで、イメージ画面(imageViewプロパティ)のalphaプロパティの値を、現在の値から1に、2秒間かけて変化させてくださいってことになるんですな。

 タップ時のimageViewのalphaプロパティ値は、viewDidLoadメソッドでの設定なので

 

        imageView.alpha = 0(透明)

 

であり、これが2秒間かけて、だんだんと1(完全に不透明)に変化することで、今回のような画像がじわっと浮き出る効果が得られるわけっす。

 

クロージャ

 

 animations:引数に指定した {・・・}  てのはクロージャと呼ばれるもので、任意の処理を引数として渡す時に使います。

 クロージャは自分のメソッドではなく、独立した処理なので、自分のプロパティを省略形式で書くことは不可能。なのでselfが必須となります。

 

  {

   self.imageView.alpha = 1 ←self.は必須

  }

 

  で、一応こんな風にself.と書くことで、自分のプロパティを指定できるんだけど、これは暗黙に次のような処理が行われていることを知識として覚えておきましょう。

 

  クロージャ側の見えない定数α = self  ←暗黙の処理

  {

   self.imageView.alpha = 1  ←selfと書かれていることろはαと解釈される

  }

 

 ま〜、ここら辺の知識は、今はいいけど、アプリのきめ細かい制御をやり始めると必要になります。興味がある人は「オーナーシップ クロージャ swift」なんかで検索してみましょう。

 

アニメーション対応プロパティ

 UIViewのプロパティへの設定がアニメーションになると言ったけど、実際にアニメーションになるのは対応してるプロパティだけです。

 今まで出てきたのだと、alphaの他にframeやcenter、masksToBounds、backgroundColorなんかが対応してます。対応してるかどうかはクイックヘルプで調べることが可能。

 ちなみに複数の処理を書くことで同時にアニメーションさせることも可能。

 

サンプル:

http://tetera.jp/xcc/book-sample/animation.zip

 

 

 

 勘のいい人は気付いてると思うけど、こいつも結局UIViewが持つCALayerへの間接的な設定となってます。

 実際にアニメーションを管理するのはCAAnimationオブジェクトであり、アニメーションの起動はCALayerオブジェクトへのaddAnimationによる明示的なCAAnimationオブジェクトの追加、もしくはプロパティ値の変更による暗黙のアニメーションってことになります。

 CAAnimationについて詳しく知りたい人はAppleの日本語約ドキュメント集ページに置いてある

 

Core Animationプログラミングガイド

 

を読みましょう。

 

 とにかくこれで、「または私は如何にして心配するのを止めて…」で公開したサンプルの解説は終了。

 サンプルで使ってる配列scriptの要素を、文字列じゃなく、「アンラップしてチン♪」で紹介した辞書にすると、もうちょっと発展しそうなところで、また次回!

アー・ユー・エクスペリエンスト?目次

 

 1976年4月、マイクロコンピュータが中央集中制御する方式を採用した一眼レフがキヤノンから発売された。

 Canon AE-1

 50mm f1.4 レンズとケース付きで85000円。

 

 

 同年同月1日エイプリルフール、ジョブズとウォズニアック、二人のスティーブがApple Computer社を設立し、Apple Iを666.66ドルで販売している。

 

 ここでジョブズが言ってる、クラブとはホームブリュー・コンピュータ・クラブのこと。

 Wikiより:電子工学の愛好家や技術系ホビーストが集まって、コンピュータ機器をDIYで製作するための電子部品、電子回路、情報などを交換する非公式な場として始まった。

 

 クラブで流行ってるアルタイルってのは、Altair 8800と呼ばれるコンピュータをさす。前年1975年に、個人が所有可能なコンピュータ(パーソナルなコンピュータ、略してパソコン)の組み立てキットとして発売されていた。

 

 この時代、コンピュータはビルの1フロアを丸々独占するような大きさのメインフレームと呼ばれるものや、メインフレームよりはミニだけど、それでも大型冷蔵庫数台分のミニコンピュータと呼ばれるものがほとんどで、お値段うん億円からうん千万円、とても個人が所有できるものではなかった。

 そこに、5年前の1971年に産声をあげたマイクロプロセッサと呼ばれるLSIチップを利用して、たったの397ドルでミニコンピュータ(自称)を作れるよと登場したのがAltair 8800だった。

 

 だったんだけど、こいつは今の自作パソコンのような、部品をソケットに挿せば動くといったものではなく、配線図見ながら溶かしたスズとナマリの合金で電線と装置を繋いだり(ハンダ付け)しなくちゃならなくて、作るのがとっても大変。ていうか、素人には作れねーよ。

 それに目をつけたジョブズが、組み立てまで終わったコンピュータを売ったらいんじゃねとウォズに持ちかけて実際に会社を作って売ったのがApple Iだった。

 

 いずれのコンピュータも貧弱な性能だったが、そんなことはマニアにはどうでもよかった。自分だけのコンピュータを持てることが重要だったのだ。

 このアメリカの西海岸で起こった「コンピュータを個人所有しちゃおうぜ」という小さなさざ波は日本まで到達し、この夏8月にはNECがTK-80を88,500円で販売している。

 そして、このパーソナルコンピュータの心臓部として大活躍のマイクロプロセッサは、2年後、Canon AE-1の発展系であるCanon A-1 83,000円(ボディのみ)を、完全デジタル制御カメラとして完成させ、やがては炊飯器、ビデオカメラ、電話とあらゆるところに浸透していくことになる。

 

 現在50前後の初老のおっさん、おばさんが小学生だった時代の話。

 ちなみに、その頃の日本の小学生が知ってる電子関係グッズといえばTK-80ではなく電子ブロックだった。

 

 

 もはや戦後ではなく、大卒の初任給は9万円で、大島渚監督はわいせつ物頒布罪で愛のコリーダされ、犬神家の一族の池には逆さの足が伸びていた。

 一方のアメリカではロバートデニーロが流しのタクシー運ちゃんで、ジャックニコルソンはカッコーの巣から転げ落ち、キングコングが今はなき貿易センタービルによじ登っている。

 

 ロッキードは黒いピーナツを配って回り、日清製粉からは焼きそばUFO、どん兵衛が発売されていく中、ピンクレディがペッパー警部でデビューし、テレビでは、まんが日本昔ばなしの放映が開始され、金田伊功が大空魔竜ガイキングで金田光り、金田パースを炸裂させ、なんか週によって絵柄変わるんだけどと当時の小学生に制作スタッフの名前を覚えるというきっかけを提供していた。

 そんな1976年から始めよう。

 

流行っていた音楽

 イーグルス:ホテル・カリフォルニア

 ABBA:ダンシング・クイーン

 川橋啓史/斎藤こず恵:山口さんちのツトム君

 イルカ:なごり雪

 
 

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

 

 前回は力尽きてメンゴメンゴ。

 今回はマジにAuto Layoutだから。

 とか言いつつ、型変換の補足説明。

 いやホントちょっとだけっすから。たぶん。

 型変換には as? の他に as! ってのもあります。

 使い方は as? と同じで

 

 let text = script[0] as! String

 ↑何いってるか、わからない人は前回参照

 

てやるだけです。

 で、こっちを使うとどうなるかというと、受け取った定数・変数はオプショナル型ではなく、通常のString型となります。

 でも、それじゃnilが設定できないわけで、もし

 

 let text = script[1] as! String

 

なんてして、script[1]がString型に変換できない型だった場合はどうなるのかというと〜、アプリが死にます。

 デンジャラスなんで、ある程度覚悟して使いましょう。

 アンラップしないでいいのは便利なんだけどね。

 

サンプル:

http://tetera.jp/xcc/book-sample/unwrap.playground.zip

 

 で、ついでにいうと、型の方にも、nilが設定できるけど、アンラップしなくてもいい型があります。こいつはアンラップ済みのオプショナル型。

 

アンラップ済みのオプショナル型

 

 まず、復習を兼ねて変数宣言を整理すると、宣言時になんらかの値が必要なのがオプショナルじゃない型です。で、この場合nilは設定不可です。

 例えばInt型ならこんな感じ

 

 var x = 0   ← 値を設定してるので、型は書かなくても類推できる

 var y:Int = 1  ← 正式には、このように型も指定する

 x = nil    ← nilの設定は不可能

 

 これがオプショナルやアンラップ済みのオプショナル型なら、宣言時に値を書かなくてもよくなります。書かない場合はnilが入る。

 前回のオプショナル型なら

 

 var x:Int? = 0

 var y:Int?   ← 値を書かなくてもいい。その場合nilが設定される

 y = 1

 x = nil    ← nil設定も可能

 

 アンラップ済みのオプショナル型なら

 

 var x:Int! = 0

 var y:Int!

 y = 1

 x = nil    ← nil設定も可能

 

となる。

 こうしてみると、オプショナルとアンラップ済みのオプショナル型は、の違いだけじゃんてことになるんだけど、違うのは先に言ったように変数を使う場合。

 オプショナル型なら、使う前のアンラップ作業が必須で

 

 if y != nil {   ← 利用前にnilでないことを確認

  x = 2 * y! + 1 ← !をつけてアンラップして使う

 }

 

とかしないとダメなのが、アンラップ済みだと

 

 x = 2 * y + 1  ← Int型と同じ扱いができる

 

となります。とても楽。

 いちいちアンラップしなくてもいいのは楽なんだけど、もしyにnilが設定されている状態で同じことをやっちゃうとアプリが死にます。

 

 x = 2 * y + 1 ← yがnilの時に、これをやるとやばい

 

 なので、yがnilの可能性がある場合は、オプショナル型同様に

 

 if y != nil {   ← 利用前にnilでないことを確認

  x = 2 * y + 1 ← !は不要

 }

 

なんて書く必要もあるわけです、この点はオプショナル型と同じ。

 でも、書かなくてもコンパイラに怒られはしない。

 オプショナル型だと必須なnilでないかの確認作業を、アンラップ済みだと、プログラマ側で状況によってやるかやらないか選択できるんですな。

 そんな感じで、オプショナル型、アンラップ済みオプショナル型が使い分けられます。結構Appleのサンプルなんかで、出くわすと思うので覚えておきましょう。

 ちなみに正式には、オプショナル型、アンラップ済みオプショナル型は、それぞれ

 

 Optional<型> オプショナル型

 ImplicitlyUnwrappedOptional<型> アンラップ済みオプショナル型

 

と書きます。

 

 例)

 var x:Optional<Int>   ← var x:Int?の正式

 var y:ImplicitlyUnwrappedOptional<Int> ← var y:Int!の正式

 

ま、あんま使わないし見ないけど、変わりモンのソースコードを見た時に出てくるかも。

 

 それと、チェック付きアンラップ(オプショナルバインディングとか言うらしい)作業での

 

 var x:Int?

  ・・・

 if let x = x {

  ・・・ ← xを使った処理

 }

 

といった記述ですが、この場合、 { } (ブレースペア)の内側のxと外側のxは別物になります。

 

スコープ

 

 変数・定数のスコープ(範囲)と呼ばれるルールで、if letやfor、メソッド、関数の引数で宣言されたものは { } 内でしか認識されません。 { } 内で宣言されたものも同様。

 もし、その { } 外に同じ名前の変数・定数があったら、より近くで宣言されたものの方が優先となります。

 


 チェック付きアンラップ作業の場合、別の名前にするより、同じ名前にした方がプログラムが読みやすいんでよく使われるんですよ。やりたければ

 

 if let a = x, let b = y {

   ・・・ ← アンラップされたxとしてa、yとしてbを使う

 }

 

 てのもアリですが、いちいちaはxだったっけ、yだったっけと考えるより、同じ名前使った方がいいでしょ。

 

 このルールがあるおかげで、メソッドや関数を定義する時に、 引数や { } 内部で好き勝手な名前の変数・定数を定義できるわけです。

 

 

 

 ちなみに、クラスのプロパティとして定義されている名前と同名の変数・定数をメソッド内で使う場合は、優先されるのはメソッド内で宣言された変数・定数だけど、プロパティ側もselfをつけることでアクセスできたりもします。

 

class C {

    var str = "property"

    func x() {

        let str = "method"

        print(str)    ← methodと出力される

        print(self.str)  ← propertyと出力される

    }

}

 

サンプル:

http://tetera.jp/xcc/book-sample/scope.playground.zip

 

 ここら辺の仕組みを詳しく知りたい人は「変数 スコープ swift」あたりで検索かけて見ましょう。

 ということで寄り道終わり!

 

 こっからがAuto Layoutの話ね。

 まずは前々回のサンプルにAuto Layout機能を組み込んだサンプルをRunだ!

 

サンプル:

http://tetera.jp/xcc/book-sample/dynamictype+AL.zip

 

 設定側の変更で、ちゃんとUILabelの矩形が伸び縮みするざましょう。

 

 

 これがdynamic type対応アプリじゃい!

 

 じゃまず、今回のサンプルでUILabelの作成時に、全てのUILabelの矩形の左上位置を(0,0)、幅と高さを(200,60)に固定してる点から説明します。

 Auto Layoutを使うなら、ここでの矩形指定はあんまり意味を持ちません。

 なので、元のままでもいいんですが、Auto Layoutがきっちり効いてることを示すために全UILabelの矩形を同じにしました。

 なので前のサンプルで使った変数yは使いません。

    override func viewDidLoad() {
                ・・・
        var y = 50  ←いらない
        for textStyle in textStyles {
            let label = UILabel(frame:
                CGRect(x: 0, y: 0, width: 200, height: 60))
                ・・・
            label.numberOfLines = 0
                ・・・
        }

 ぶっちゃけ

 

 let label = UILabel()

 

でも動いたりします。サンプルでframe:指定しといてなんだけど、そっちの方が素直かもしれん。

 そのあとのnumberOfLinesプロパティは、前にも言ったけど表示のための行数を最大何行にするかなんですが、0を指定することで、UILabelの矩形幅で折り返した際に、全文字列を表示できるだけの行数って意味になります。

 もっとも、この指定はAuto Layoutを導入して、UILabelの矩形高さを調整するようにしないとあまり意味はない。高さが固定だと、結局はみ出した部分は表示されないからね。

 

 というわけで、下に追加してるのがAuto Layoutの導入処理。

 

Auto Layout

 

  Auto Layoutは、2つの画面矩形が、配置時に互いにどのように影響しあうかを指定しておく事で、実際の画面配置をiPhone側に任せるという仕組みっす。

 で、この2つの画面間の配置取り決めの事を制約と呼んで、NSLayoutConstraintクラスで表現するんですな。 

 

NSLayoutConstraint(制約)

 

 Auto Layoutの導入ってのは、このNSLayoutConstraintの作成に他なりません。実際、サンプルでもNSLayoutConstraintを複数作ってconstraintsという配列にまとめてます。

    override func viewDidLoad() {
                ・・・
        NotificationCenter.default.addObserver(self・・・

        //  Auto Layout機能追加
        var constraints = [NSLayoutConstraint]()
        var upperObject:Any = self.topLayoutGuide
        for label in self.labels {
       ・・・
            //  横の制約を作成しconstraintsに追加する
            let h_constraint = NSLayoutConstraint(
                item:label, attribute:.leading,
                relatedBy:.equal,
                toItem:self.view, attribute:.leading,
                multiplier:1, constant:50)
            constraints.append(h_constraint)

            //  縦の制約を作成しconstraintsに追加する
            let w_constraint = NSLayoutConstraint(
                item:label, attribute:.top,
       ・・・
            upperObject = label
        }
        ・・・

 じゃあ、この制約ってのは、具体的には何なのか?

 

   Y = aX + b

 

 この式が制約です。

 

親切本545ページ〜からを、今のSwift 3に対応させつつ抜き出し(親切本は秀和システムのサポートページからダウンロードするサンプルプロジェクト群の方だけSwift 3に対応させてる)


 まず、制約とは何かという事ですが、これは横なら横、縦なら縦の位置に関する2つの画面位置の関係を次の数式で表したものです。 

 

 

 先のソースコードでは、forループの中でおこなっているNSLayoutConstraintインスタンスの作成が、1つの制約の作成となっています。

 ・・・

 

NSLayoutConstraint

制約を表現する

 

 

 引数のitem:とattribute:が数式の左辺である位置Yを定義し、toItem:とattribute: が位置X、そしてmultiplier:とconstant:が、それぞれ数式のaとbを表し右辺となります。 relatedBy:には、この両辺を結ぶ「=、≦、≧」といった等記号や不等記号をNSLayoutRelation型 の値である、.equalや.lessThanOrEqualで指定します。 

 

NSLayoutRelation 

 等記号や不等記号を定義する。 

 

 item:やtoItem:には、UIViewといった位置情報を持つオブジェクトを指定します。そのオブジェ クトのどの部分の位置を利用するかを指定するのがattribute:です。 

 次のような値が、NSLayoutAttribute型として定義されています。

NSLayoutAttribute

 オブジェクトの部位を定義する。 

 

 

 このうちMarginと付くものは iOS 8から利用できる値で

 ・・・ 省略 ・・・

 

 .baselineや、.firstBaseline、.leading、.trailingといった値は、文字に対する位置調整で意味を持ちます。 

 

 

 左から右に文字を書く英語や日本語で.leadingと.left、.trailingと.rightに違いはありませんが、アラビア語など右から左に文字を書く言語では位置が逆転します。 .leadingMarginや.trailingMarginもそれぞれ.leftMargin、.rightMarginに対応します。 

 

 

 サンプルで作成している横位置用の制約は、左辺側のlebelの.leadingを、右辺の式(1 * self. viewの.leading + 50)をみたすように調整する制約となります。 

 

 

 縦位置の制約では、左辺側のlabelの.topを、右辺の式(1 * upperObjectの.bottom + 8)をみたすように調整する制約となります。 

 

 


 

 ここで出てきたupperObjectというのは、ループで設定してるUILabelの一つ上のUILabelを意味してます。

    override func viewDidLoad() {
       ・・・
        var upperObject:Any = self.topLayoutGuide
        for label in self.labels {
       ・・・

            //  縦の制約を作成しconstraintsに追加する
            let v_constraint = NSLayoutConstraint(
                item:label, attribute:.top,
                relatedBy:.equal,
                toItem:upperObject, attribute:.bottom,
                multiplier:1, constant:8)
            constraints.append(v_constraint)
       ・・・
            upperObject = label
        }
        ・・・

 

 

 ただし、最上位のUILabelの、その上にUILabelは存在しないので、最上位だけはupperObjectにUILabelではなく、UIViewControllerのtopLayoutGuideプロパティに設定されているオブジェクトを指定するようにしてる。

 このオブジェクトは、ステータスバーといった、自分のviewに覆いかぶさっている領域をはぶいた、最上位位置を示すようになってるオブジェクトっす。

 

 

 topLayoutGuideプロパティに設定されているのは、UILayoutSupportというプロトコルを採用したオブジェクトっぽいけど詳細は不明。いずれにせよupperObjectには、UILabelかこのオブジェクトを設定するのでupperObjectの型はAnyにしてます。

 

 var upperObject:Any = self.topLayoutGuide

 

 NSLayoutConstraint作成時のitem:やtoItem:引数の型はAnyなので、これで問題ない。

 幅の制約にはtoItem:にnilを指定したりもしてます。自分自身の幅を固定の幅にするだけなので相手がいないんですな。attribute:も.notAnAttributeとして、ただconstant:に渡した200を設定するようにrelatedBy:に.equalを指定してる。

    
            //  幅の制約を作成しconstraintsに追加する
            let w_constraint = NSLayoutConstraint(
                item:label, attribute:.width,
                relatedBy:.equal,
                toItem:nil, attribute:.notAnAttribute,
                multiplier:0, constant:200)
            constraints.append(w_constraint)

 こういった制約を配列に集め、NSLayoutConstraintクラスオブジェクトのactivateメソッドで有効にすることで、AutoLayoutが有効となります。

    override func viewDidLoad() {
                ・・・
        var constraints = [NSLayoutConstraint]()
                ・・・
        for label in self.labels {
                ・・・
            constraints.append(h_constraint)
                ・・・
            constraints.append(v_constraint)
                ・・・
            constraints.append(w_constraint)
                ・・・
        }
        //  制約群を有効にする
        NSLayoutConstraint.activate(constraints)
    }

 

 なんですが…

 重要な点として、UIViewの初期設定はautoresizingMaskプロパティの値をNSLayoutConstraintに自動翻訳するって設定になってるんですよ。

 覚えてるかなautoresizingMaskプロパティ

 Auto layoutが導入されるのは、iPhoneのシステムのバージョンが、6になってからなんだけど、それまでは画面配置の自動化はautoresizingMaskでやりくりしてたわけで、そういったautoresizingMask前提のアプリの画面配置が破綻しない措置なんだけど、そのせいでNSLayoutConstraintが無視されちゃうんですな。

 なので、この設定を解除する必要があります。

 そのためにやってるのがtranslatesAutoresizingMaskIntoConstraintsプロパティにfalseを設定する作業。

    override func viewDidLoad() {
                ・・・
        for label in self.labels {
            //  labelに対しAuto Layoutを有効にする。
            label.translatesAutoresizingMaskIntoConstraints = false
                ・・・
        }
                ・・・
    }

 これで、NSLayoutConstraint側が優先されることになる。

 ふう。

 

 めっちゃめちゃめんどくさい。

 

 NSLayoutConstraint使うのやめっか?

 と思う人が多いと思うけど、本来、NSLayoutConstraintをこうやってプログラミングで設定することは少ないです。

 普通は「重箱の隅をつつくようにネチネチと進めてみる」で言ったようにストーリーボードを使う。

 そこらへんも含め次回に続く。