最初に、前回の配列宣言時の書き方の追加説明。

 

  ↓これね

 

  var labels = [UILabel]()

 

 配列の宣言は「色々と脱線」で [ ] (ブラケットペア)で囲んだ中に、複数の要素(あの時は文字列だったけど、今回はUILabelってことになる)を , (カンマ)で区切って並べることで作るって言ったので、上の記述を

 

 [UILabel] ← UILabelクラスオブジェクトが1つ入った配列   

 [UILabel]() ← で、その後ろに()ってなにこれ?

 

って思った人もいるんじゃないかと思われ。

 クラスオブジェクト?な人は「色々と脱線」を読みましょう。

 

 で、実はこれもSwiftの略記法の一つで、正式なUILabelの配列宣言は

 

  var labels = Array<UILabel>()

 

と書くんですよ。

 これで、UILabel型の要素を持つ空の配列の作成になるんですよ。varなので変更可能ね。

 

Array

 

 で、ここで現れたArray構造体こそが配列の正体なんですが、こいつは自分が取り扱う要素の型に合わせて自身の型が変化するという特性を持ってて、(Array+取り扱う要素の型)で一つの型となります。

 int型のArrayとか、UILabel型のArrayとか、取り扱う要素によって色々な型のArrayができるわけですよ。

 こういった構造体をジェネリック構造体というんですが、その特性ゆえに、作成時には型指定(今回のArrayなら配列要素の型)が必須で、ジェネリックじゃない構造体のように

 

 Array()

 

なんて風には作れません。それで、後ろに続く < > (アングルブラケットペア)で囲んだ文字列で要素の型を表記することになってます。

 

 

 

 例えば、Intの配列は

 

 Array<Int>()

 

で、作成となるわけです。でもって、その略記法が

 

 [Int]()

 

であり、もし要素の型がUILabelなら

 

 [UILabel]()

 

となるわけですね。

 ちなみに () を外した部分が、ジェネリックであるArrayの型で、引数定義なんかで型を書く時は、Array<Int>とか、[Int]といった風に書きます。

 なので、ここで出てきたUILabelは、配列の要素としてUILabelクラスオブジェクトを登録してるんじゃなく、要素の型を示してるだけです。

 ついでにいうと「色々と脱線」で説明した配列宣言の書き方

    let script = [
        "雨の日はだるい…\n今日は学校休んじゃおっかな〜",
        "誰だ"
    ]

 は

    var script = Array<String>()
    script.append("雨の日はだるい…\n今日は学校休んじゃおっかな〜")
    script.append("誰だ")

と書いたのとほぼ同じ(letでは要素の追加ができないのでvarになる点を除いて)意味になる。

  [ ] (ブラケットペア)で囲んだ中に , (カンマ)で区切って要素を並べることで、配列宣言、要素追加までを一気にやっちゃうわけですな。

 この時、要素の型は並べた要素から勝手に判断します。

 そいうわけで

    let script = ["太郎",1,2.0]

 なーんて書くとコンパイラから「ごめん、何いってるか解らない」って注意されたりもします。文字列の配列なのか、Intの配列なのか、Doubleの配列なのかはっきりしろよと…

 こんな時は

    let script:[Any] = ["太郎",1,2.0]

 と書いて、問題を解決する。

 Anyというのは、型のワイルドカード。「何かの型」という反則的な型です。これで「欲しいのは色々な型の要素が入る配列じゃあ」という意味になる。

 まあこれも略記で正式には

    let script:Array<Any> = ["太郎",1,2.0]

 ですな。これで

    var script = Array<Any>()
    script.append("太郎")
    script.append(1)
    script.append(2.0)

 と書いたことになる。

 

 こんな感じで便利なAnyなんだけど、可能なら避けましょう。

 これ指定すると、配列の要素は文字列(String型)だったり、Int型、Double型だったりと一定の型じゃなくなるので、数値を取り出したつもりが、文字列でしたとか、混乱が目に見えてるのでな。

 

 でも使わざるを得ない時もあるんですよ。

 その時は配列の要素の型は全てAnyなので、そのままでは配列から取り出してもAny型としてしか使えません。

 例えば先例のscriptの場合、インディックス0の要素はString型なんだけど

 

 let text = script[0]

 

としてもtextはAny型のままです。

 このままUILabelのtextプロパティに設定しようとしても型が合わないって怒られます。

 そのためAny型からString型に変換する必要があるんだけど、「ifの話」で紹介したInt型からDouble型への変換みたいなことはできません。

 

  Double(sign)

 

てやつね。こんな感じで

 

 let text = String(script[0])

 

とやっても怒られます。あれは渡された値を使った、新しい変数、定数の作成という意味なので、String側にAny型の値から、新しいStringを作る機能がない限り使えないんですよ。で、実際、そんなものは無いと。

 こういった場合、script[0]の型を、可能ならString型として扱ってくださいという意味で as? を使い

 

 let text = script[0] as? String

 

と書いたりします。こうするとtextには、script[0]がString型として扱えるなら、その値が、そうでないなら無効な値という意味のnilが設定されます。

 

 script[0]なら

 let text = script[0] as? String ← textには"太郎"が設定される

 

 script[1]なら

 let text = script[1] as? String ← textにはnilが設定される

 

 なので、script[1]の場合はIntを使って

 

 let value = script[1] as? Int ← valueには1が設定される

 

ですね。あくまで型をどう扱うかの指定なんで as? Double なんてすると、valueにはnilが入ります。

 

 let value = script[1] as? Double ← valueにはnilが設定される

 

 script[1]の値をDouble型として使いたければ、一旦Intとして取り出して、その上でDouble()を使って変換します。

 

 let value = script[1] as? Int 

 let dvalue = Double(value!) ← !をつける点に注目

 

 Double()を使って変換する時、valueの後ろに ! (エクスクラメーション)を付けているのは、valueがInt型ではなくnilの指定が可能なInt?型だからです。

 こういった型名の後ろにがついた型をオプショナル型っていいます。各型をnil設定可能にしたものです。Int型の定数・変数にはnilは設定できません。

 そのためnilの可能性のあるas? IntではInt型ではなくInt?型となります。

 

オプショナル型

 

 で、このオプショナル型の変数・定数は、そのままでは使えず、使う時は「この変数・定数はnilでは無い事を保証します」という意思表示が必要なんですな。

 それが後ろの!の意味。

 

 

 これをオプショナル型をアンラップするって言います。

 もし間違ってnilが設定されている変数や定数をアンラップすると、アプリは実行時エラーとなって終了するんで気をつけましょう。今回ならscript[0]を指定した時そうなります。

 

 let value = script[0] as? Int  ← nilが設定される

 let dvalue = Double(value!)  ← 実行時エラーとなってアプリ死亡

 

 結構デンジャラスです。

 そういったミスを防ぐためには事前にnilかどうかのチェックをするといいでしょう。

 

 let value = script[0] as? Int 

 if value != nil {

    let dvalue = Double(value!)

    ・・・ ←dvalueを使った処理

 }

 

 でもって、こういうオプショナル型をアンラップする局面は結構あるんで、もうちょと便利な記述法が用意されてます。

 

 if let value = script[0] as? Int {

   let dvalue = Double(value) ← valueはInt型なので ! はいらない

    ・・・ ←dvalueを使った処理

 }

 

 この場合、script[0] のas? Intの結果がnilでない場合だけ { } (ブレースペア)内が実行されることになり、valueもオプショナル型のInt?型ではなくInt型になります。

 つまりscript[0]の時は何もおこらず、script[1]の時はdvalueについての処理が実行されるわけです。

 Appleのサンプル見ると結構、この記述法に出くわすので覚えておきましょう。

 あと

 

 guard let value = script[0] as? Int else {

  return

 }

 let dvalue = Double(value) ← valueはInt型なので ! はいらない

 ・・・ ←dvalueを使った処理

 

なんて記述もあります。

 

guard文

 

 この場合、value = script[0] as? Int がnilなら else { } 内が実行される。

 こいつはnilなら処理を切り上げたい場合に使われることが多いので、大概 else { } の中はreturnとなります。こっちも結構使われる。

 色々なオプショナル型をif letでアンラップしていくと

 

 if let a = … {

   ・・・ ← aを使った処理

   if let b = … {

     ・・・ ← a,bを使った処理

     if let c = … {

      ・・・ ← a,b,cを使った処理

     }

   }

 }

 

てな感じになって、インデントが深くなって嫌だっていう要望から用意されました。

 まあ , let で区切って

 

 if let a = …, let b = …, let c = … {

   ・・・ ← a,b,cを使った処理

 }

 

なんて書き方もあるんで、一気にアンラップできるなら、そこまで深くはならないけどね。

 ということで配列の宣言の補足説明終わり。

 

 あと、もう一つ補足説明。

 addObserverのselector:引数に指定するメソッドは

 

 func メソッド名(_ notification:Notification) {・・・}

 

にしないといけないって言いましたが、ありゃ不正確です。

 引数なしのメソッドも指定できます。

 

 func メソッド名() {・・・}

 

 「引数なし、もしくは引数にNotification型を1つ受け取る」この、どちらかを指定できます。

 調べ方は何度も言うけど「または私は如何にして心配するのを止めて…」で紹介したクイックヘルプね。

 

 

 引数で受け取るNotificationには、指定したイベントが発生した際の詳細な情報が入ってるんで、そういった情報が欲しければ、引数ありのメソッドを使えばいい。

 ちなみに親切本では、categoryDidChangeメソッド内で受け取ったNotificationを使って

if let category = notification.userInfo?[UIContentSizeCategoryNewValueKey] as? String {
	println(" ユーザーが選んだ分類は \(category) です。") 
}

とやってます。

 何これ?ってなると思うんで、順を追って説明。

 まずNotificationオブジェクトのuserInfoプロパティが何かから。

 こいつはクイックヘルプで調べると

 

 [AnyHashable : Any]?

 

となってます。?はオプショナル型って意味なのは先に説明した通り。じゃあ

 

 [AnyHashable : Any]

 

は何か?

 さっきの説明聞いた人は、配列?あれ、でも:(コロン)を挟んで2つ型を書いてる、何これ?

 になると思うんだよね。で、これは連想配列と呼ばれるもので、要素へのアクセスに [ ] を使うのは配列と同じなんですが、要素を指定するのにインディックスではなく、任意の値(普通文字列を使う)を使うようになってます。任意のキーワードで要素を指定できるので連想配列。

 

配列:

 

連想配列:

 

連想型配列(Dictionary)

 

 Appleではこいつを辞書と呼んでいて、Dictionaryというジェネリックな構造体で定義してます。ジェネリックというように要素指定子の型, 要素の型とも指定することになってて、正式な型表記は

 

 Dictionary<要素指定子の型, 要素の型>

 

となります。で、これの省略形が

 

 [要素指定子の型 : 要素の型]

 

となる。 , じゃなく : を挟む点に注意ね。

 多分、宣言時の略系で書くときに、要素の区切りの , と紛らわしくなるから : を使ったのに合わせたんじゃないかと思ってる。辞書宣言の略系は

    let script:[String:Any] = ["name":"太郎", "level":1, "life":2.0]

なんて風に書きます。scriptの型をわざわざ :[String:Any] と記述してる理由は先のArrayと同じ理由。

 配列と違って0から順に入るわけじゃないので、こんな感じで要素指定子と要素のペアで記述する必要がある。要素指定子の"name"、"level"、"life"はプログラマが自由に決めていい。

 letが使えない点を除いて、実質、次の記述の略系です。

    var script = Dictionary<String,Any>()
    script["name"] = "太郎"
    script["level"] = 1
    script["life"] = 2.0

 というわけで、今回のuserInfoプロパティの

 

 [AnyHashable : Any]

 

なら

 

 Dictionary<AnyHashable , Any>

 

てことです。

 Any型はさっき説明した通り。

 AnyHashableは何かと言うと、特定の計算で整数にできる型ならなんでもいいという型。

 この、要素指定子に特定の計算で整数にできる型、というのはDictionaryの条件でもあります。もうちょっと突っ込んだ言い方をすると「Hashableプロトコルを採用している型」です。そこらへんは各自で調査。

 で、String型はその条件に適応するように定義されてるので、Dictionaryの要素指定子の型として指定できるわけで、先の例では

 

 [String:Any]

 

としました。

 

 とにかく、userInfoプロパティでは、[AnyHashable : Any]に?がついてるので、userInfoから要素を取り出す時はアンラップの必要があるんですよ。

 でもって取り出す要素がみつからない場合はnilとなるし、要素が見つかっても、Anyなので、それが求める型として扱えなければやっぱりnilになる。そこらへんを素直に

 

 if let userInfo = notification.userInfo {

   if let category = userInfo[要素指定子] as? String {

    ・・・ ← categoryを使った処理

   }

 }

 

と書いてもいいけど、一気に

 

 if let category = notification.userInfo?[要素指定子] as? String {

   ・・・ ← categoryを使った処理

 }

 

と一気に書き下す書き方もできるんですな。

 左から順にアンラップや型変換が行われ、成り立たなかった時点でfalseとなって { } 内は実行されません。アンラップチェーンといいます。

 これが親切本のサンプルのcategoryDidChangeメソッドでやってることです。

 何が起こるか興味がある人は、前回のサンプルで同じように書いて実験してみましょう。

 

 てことで、ようやく本題のAuto Layoutに入ろうと思ったけど疲れたので次回!

 

追記:

 iBook見ててApple発行のこんなの発見しました。

 

Swiftによるアプリケーション開発:入門編

 

 おおう。ちゃんと日本語です。無料です。Apple日本法人、頑張ってます。

 

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

 

 Dynamic Type対応アプリとは何なのかぁ〜。

 簡単に言えば、ユーザーが設定で「文字ちょい大きめ、ちょい小さめ」ってやると、それに対応して表示文字の大きさを変更してくれるアプリのことっす。

 

 ↓実機だと設定アプリの画面表示と明るさ→文字サイズを変更で変更

 

 

 ↓シミュレータだと設定アプリの一般→アクセシビリティ→Large Textで変更

 

 

 ちなみに、シミュレータの初期設定は英語だけど、実機と同じやり方で日本語に切り替えることもできる。

 

 ↓シミュレータでもやり方は同じ

 

 

 

 

 まあ、とにかく、アプリをDynamic Typeに対応させるためには、Dynamic Type用に用意されてるスタイル(ヘッドライン用とか、キャプション用とか)をUILabelのfontプロパティに指定する必要があるんですよ。

class ViewController: UIViewController {
  ・・・
    //  各UILabelに設定する文字スタイルを配列で持つ
    let textStyles = [
        UIFontTextStyle.headline,
        UIFontTextStyle.subheadline,
        UIFontTextStyle.body,
        UIFontTextStyle.footnote,
        UIFontTextStyle.caption1,
        UIFontTextStyle.caption2
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //  各UILabelのy座標だけ変化させる
        var y:CGFloat = 40
        for textStyle in self.textStyles {  
            let label = UILabel(frame:
                CGRect(x: 100, y: y, width: 200, height: 60))
            label.font = UIFont.preferredFont(forTextStyle: textStyle)

 サンプル:

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

 

 fontプロパティに指定するのはUIFontオブジェクトっす。

 

UIFont

 

 こいつは文字の形状(明朝体とかゴシック体とかが、ヒラギノ、Osakaといった名前で分類されてる)や、大きさを表現したオブジェクトで、Dynamic Type用には、UIFontのクラスオブジェクトにpreferredFontメッセージを送って取り出したものを使います。

 

 UIFont.preferredFont(forTextStyle: 指定するスタイル)

 

 引数forTextStyle:にはUIFontTextStyleで定義されている、ヘッドラインとか、キャプション用のスタイルを指定するですが、サンプルではtextStylesというUIFontTextStyleの配列をプロパティとして用意し、これをviewDidLoadメソッドでforループを使って取り出し使っています。

 

        for textStyle in self.textStyles {
      ・・・
        }

 こいつは配列用のforループで、こう書くことでself.textStylesの要素を順にtextStyleに取り出すようになります。

        for index in 0..<self.textStyles.count {
             let textStyle = self.textStyles[index]
      ・・・
        }

と書いても、同じことができるんですが、先の書き方の方がスッキリするので使っています。ちなみにループ範囲の指定に使ってる「..<」は未満の指定です。

 これで0からtextStyles配列の要素数未満の間となります。配列のインディックスは0から始まるんで、末尾の要素のインディックスは要素数-1にしないとダメだからです。

 

 0...3       0、1、2、3のループとなる

 0..<3       0、1、2のループとなる

 

 とにかく、こうすると、起動時にユーザー指定に応じた大きさの文字が表示されるわけですな。

 

 

 ただし、これをやっただけでは初回起動時にしか対応しません。

 ホームボタンで一旦アプリを中断して設定アプリで文字サイズを設定し直しても、それには対応しないんですよ。

 これに対応するには通知を使います。

 

通知

 

 こいつは文字どおり、通知を受けとる機能です。

 アプリには、起動時に用意される通知センタってオブジェクトが居て、こいつに「これこれのイベント発生時に通知を受けたい」と登録すると、指定したイベント発生時に通知がもらえるようになってるんですよ。

 

 

 そいつに、今回なら「ユーザーが設定アプリで文字サイズを変更」というイベント発生を通知してもらうようにします。

 

 通知は、指定したオブジェクトの指定したメソッドを、通知センタから呼び出してもらうことで実現します。

 そのため登録時に、通知を受け取るオブジェクトと呼び出してもらうメソッドの指定が必要なんですが…

 このメソッドは、名前はなんでもいいんだけど、引数が決まってて

 

 func メソッド名(_ notification:Notification) {・・・}

 

というメソッドにする必要があります。

 引数のところの _ (アンダースコア)は何かというと、実を言うと本来、メソッドや関数の引数の定義は

 

 

と書くのではなく

 

 

と書くのが正式で、関数を呼び出したりメッセージを送ったりする側は、このラベル名を書くことになっているんですな。

 例)

   定義:

   func example(arg a:Int) {・・・}

 

   呼び出し・送信側:

   example(arg:1)

 

 じゃ、今までのは何だったのかというと、引数の略記法でして、ラベル名を書かないことで、ラベル名と引数名は同じとみなすことになるんですよ。

 

 

 例)

   定義:

   func example(a:Int) {・・・} ラベル名も、仮引数名も同じという意味

 

   呼び出し・送信側:

   example(a:1)

 

 というわけで、今回の引数

 

 (_ notification:Notification)

 

 

 ラベル名 _

 仮引数名 notification

 型 Notification

 

という意味になります。

 で _ は特別で、呼び出し側はラベル名を付けないという指定になるんですよ。

 例)

   定義:

   func example(a:Int) {・・・} ラベル名は付けないという意味

 

   呼び出し・送信側:

   example(1)

 

 で、このメソッドを通知センタオブジェクトに登録するわけです。

    override func viewDidLoad() {
        super.viewDidLoad()
            ・・・
        NotificationCenter.default.addObserver(self, 
            selector:#selector(ViewController.categoryDidChange(_:)), 
            name:NSNotification.Name.UIContentSizeCategoryDidChange, 
            object:nil)
    }

    func categoryDidChange(_ notification:Notification) {
            ・・・
    }

 通知センタオブジェクトはNotificationCenterクラスオブジェクトのdefaultプロパティに設定されてるので、そいつに登録用のaddObserverメッセージを送ってます。

 最初の引数が、通知を受け取るオブジェクトで、今回ならViewController自身なのでself、その次のselector:には呼び出してもらうメソッドの指定。

 ここで使ってる#selectorてのは、コンパイラ司令というやつで、Swift言語で書かれたプログラムをiPhoneが理解できるものに変換するツール(コンパイラって言います)への命令です。これで ( ) 内に書かれたメソッドを引数として渡せる形にして渡せという命令になってます。

 

    #selector(メソッドの指定)

 

 で、この書き方は次のようにメソッドの定義に基づいて書くようになってて

 

 

 引数はラベル名に : を付けて表現します。型は書きません。

 

 

 で、その次のname:は、イベントの指定。

 今回なら「ユーザーが設定アプリで文字サイズを変更」というイベントで、こいつはNSNotificationに定義されてます。

 

 NSNotification.Name.UIContentSizeCategoryDidChange

 

 で、最後のobject:は、このイベントを発生させたオブジェクトを指定するためのもので、nullだと、どんなオブジェクトでも指定したイベントを発生させれば通知しろってことになります。

 ここに特定のオブジェクトを指定した場合、そのオブジェクト以外が指定したイベントを発生させても通知は来なくなります。

 今回は、どんなオブジェクトでもOKなんでnullとしてます。

 

 これで、ようやく、設定アプリで文字の大きさが変更されると、指定したメソッドが呼び出されるようになるんですな。

 んでもって、呼び出された時に、再度フォントを指定してやると、大きさが変わるって仕組みです。

 そのためにサンプルでは、スタイルとUILabelを配列で用意してます。

class ViewController: UIViewController {
    var labels = [UILabel]()
  ・・・
    let textStyles = [
  ・・・
    override func viewDidLoad() {
         ・・・
        for textStyle in self.textStyles {
             ・・・ 
            self.labels.append(label)
             ・・・ 
    func categoryDidChange(_ notification:Notification) {
        for (index, label) in self.labels.enumerated() {
            label.font = UIFont.preferredFont(
                forTextStyle: textStyles[index])
        }
    }

 最初に登場する

 

  var labels = [UILabel]()

 

てのは、空のUILabel配列の宣言。

 この配列にappendメッセージを送って、作成したUILabelを追加してます。

 

  self.labels.append(label)

 

 でもって、categoryDidChangeメソッドでは、このlabelsからインディックスとUILabelを一組として取り出してUILabelのfontプロパティを再設定してる。

 ここで出てくる

 

        for (index, label) in self.labels.enumerated() {

 

というループは、配列にenumeratedメッセージを送ることで、インディックスとUILabelを一組として順に取り出すもんで、やってることは

        var index = 0
        for label in self.labels {
            ・・・
            index += 1
        }

 と同じです。

 (index, label)はタプル(tuple:組)と呼ばれるもので、Swiftでは、こんな風に ( ) で囲み、 , (カンマ)で区切ることで複数の値を、一組にして受け渡せるようになってます。

 もちろん送る方、受ける方でタプルを使うことを定義する必要があるし、タプル内の値の順はその定義に従います。enumeratedメッセージを使ったforループでは(インディックス, 要素)の順です。

 

 ちなみに、この通知登録はViewControllerが消える時には、解除しないといけません。なのでdeinitで解除してます。

 deinitは自分が消えて無くなる前に呼び出される特殊なメソッドで、通知解除のタイミングとして丁度いい。通知センタに引数に自分自身を指定し、removeObserverメッセージを送ることで、自分を受け手とした登録が全て解除になります。

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

 もっとも、次回のバージョンからはこいつは必要なく…(気になる人はWWDCビデオみましょう)

 

 とにかくこれでDynamic Type対応アプリになったんだけど、見ての通り、UILabelの初期設定だと1行表示なんですよ。

 表示できない文字後半部分は「…」で省略されます。

 

 

 こいつを、複数行表示にしたい場合はUILabelのnumberOfLinesプロパティに、最大行数を指定します。

 

            label.numberOfLines = 4

 

 そうすることで、その指定行までは改行で残りを表示しようとしてくれます。

 


 まあ、それでもUILabelの矩形(背景色を臼灰色にしてるのでわかると思う)を超えることはできないんですがね。

 そんなの嫌じゃい。

 文字はいつでも全部表示させるようにするんじゃいい。

 

 ↓こんな感じでな

 

 

 

 そんな人はUILabelの矩形を調整することになります。

 で、その場合に使うと便利なのがAutoLayout機能。

 というところで、以下次回。

 

 

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

 

 前にUIViewが画面を管理するって言ったっけ?

 すまん、ありゃ嘘だ。

 まあ、嘘じゃないにしろ正確じゃない。

 正確には、画面表示に関してはUIViewオブジェクトが所有するCALayerオブジェクトが受け持ってます。

 それが、前回出てきたUIViewのlayerプロパティに設定されているオブジェクト。

 UIViewはいわば総合商社(ゼネコン)みたいなもんすね。

 発注を受けて下請けに丸投げしたりします。

 

 

 例えば、以前説明した、背景色の指定であるUIViewのbackgroundColorプロパティへのUIColor設定は、UIView内部で、このCALayerのbackgroundColorプロパティを設定してたりします。

 

 

 こんなまだるっこしい構成にしてるのは、一つは機能の分業化です。

 CALayerは画面表示に専念し、ユーザーからの画面タップには無関心。そっちはUIViewが担当することになってます。

 これが分業。

 もう一つはMac、iPhone開発時の共通機能の共有のため。

 画面表示機能はMacでもiPhoneでも考え方・扱い方は同じ(RGBで色を指定とか)なので、この機能を同じCALayerというクラスで提供してもらえると、アプリを作るプログラマは、1つのクラスを知ることでMacでもiPhoneでも画面表示プログラムを書けるようになる。

 覚えることが減って助かるんですよ。

 ただし、画面タップ、マウスクリックという機能はiPhone、Macで異なる部分なんで、そこは、それぞれのプラットフォーム(iPhone、Mac)別々のクラスを用意する。それがUIViewというわけです。ちなみにMacアプリで画面管理するオブジェクトはNSViewと言います。

 

 

 内部で複数のオブジェクトを構成して分業してるけど、外部とのやりとりは基本的に1つの元締めオブジェクト(UIView・NSView)が対応する。

 コンポーネントというプログラミング手法です。

 ま、そんなわけで実際の画面表示はCALayerが請け負ってるので、直接CALayerにナシをつけた方が細かな指示ができたりもするんですよ。

 UIView側に依頼窓口が用意されてないやつとかもある。

 それがlayerプロパティのconerRadiusやmasksToBoundsといったプロパティの設定。

    override func viewDidLoad() {
		・・・
        let back = UIVisualEffectView(frame:frame.insetBy(dx: 20, dy: 20))
        back.layer.cornerRadius = 16
        back.layer.masksToBounds = true
		・・・

 conerRadiusの方は矩形の四隅に丸みをつける指定です。角の丸みを半径で指定する。

 

 

 他に枠線の幅を指定(borderWidth)して枠線を表示したり、枠線の色(borderColor)を指定したりもできる。

 

サンプル:

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

 

        let sampleView = UIView(frame: CGRect(x: 100, ・・・
        self.view.addSubview(sampleView)
        sampleView.layer.borderWidth = 1
        sampleView.layer.cornerRadius = 8
        sampleView.layer.borderColor = UIColor(
            hue: 0.6, saturation: 0.2, brightness: 1, alpha: 1).cgColor
//        sampleView.layer.shadowOpacity = 0.5
//        sampleView.backgroundColor = .white

 

 

 borderColorプロパティに設定してるのが、UIColorじゃなくCGColorだってことにも注意が必要っす。ここら辺は以前教えたクイックヘルプで調べましょう。

 とにかくCALayerの色関係にはUIColorは指定できない。CGColorを直接作るのは手間なんで、一旦UIColorを作って、そのプロパティからCGColorを取り出して指定します。

 こんな感じで、変数や定数に入れなくても、オブジェクト作成のUIColor(hue:…)の後ろに .(ドット)とプロパティ名とすることで、作ったUIColorオブジェクトにアクセスできます。

 

 

 作ったUIColorオブジェクトは、変数や定数に入れてないんで、これ以後は使えなくなるだけど、CGColorを取り出したら用済みなんで問題ない。

 それと今回はRGBじゃなくHSB指定でUIColorを作ってます。hueが色相(0.0〜1.0 0.0が赤で0.33..で緑、0.66...で青と変わり1.0で赤に戻る)。

 

 saturationが彩度(0.0〜1.0 1.0で一番鮮やか), brightnessが明度(0.0〜1.0 1.0で一番明るい)。

 

 枠線の他に、shadowOpacityプロパティの値を、0以外にすると影が表示されたりもします。

 

 

 サンプルでコメントにしている

 

   // sampleView.layer.shadowOpacity = 0.5

 

の // 部分を消して有効にして試すといいでしょう。その後で、その次の行のコメント部

 

   // sampleView.backgroundColor = .white

 

を有効にして試すとこんな感じになります。違いがわかるかな?

 

 

 CALayerにどんな指定ができるか、詳しくはこの前教えたクイックヘルプを使って調べてみてください。

 

 それと、この角丸矩形にしてるUIVisualEffectViewは、画面にすりガラス効果を持たせるUIView派生オブジェクトです。

 effectプロパティにUIBlurEffectを指定することですりガラス効果を指定できる。

        let back = UIVisualEffectView(
                frame:frame.insetBy(dx: 20, dy: 20))
                ・・・
        back.effect = UIBlurEffect(style: UIBlurEffectStyle.light)

 自分の後ろにある画像を、すりガラス加工するので、後ろがただの白地だと効果はありません。

 

 

 で、このすりガラス処理した画像は矩形で表示されるので、このままだと角丸矩形にならないんですよ。そのために

        let back = UIVisualEffectView(
                frame:frame.insetBy(dx: 20, dy: 20))
        back.layer.cornerRadius = 16
        back.layer.masksToBounds = true
        back.effect = UIBlurEffect(style: UIBlurEffectStyle.light)

として、画像が角丸矩形をはみ出さないようにしてます。

UIView側で指定するなら

 

        back.clipToBounds = true

 

とします。どっちで指定してもいいです。

 その後のセリフ部分(文字)のUILabelに対する

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

はダイナミックタイプ用文字の指定。

 設定アプリの「画面表示と明るさ」項目にある「文字サイズを変更」で、文字の大きさを変更すると、それに合わせて文字の大きさが変わるようになります。

 

 

 ただし、ちゃんと対応してないので、起動した時だけです。

 なのでダイナミックタイプフル対応とは言えない。

 ここら辺の話は次回!