MacBookを手に入れてから1年以上が経ちましたが、iOSアプリを作ってみたいという願望がありながら、実行に移すことは出来ませんでした。その理由は色々ありますが、一番の理由は目的がなかったからだと思います。今年はこういうアプリを作ってみたいという目的もできましたので、今年こそは達成してみたいと思います。

まずは、iOSアプリを作るための統合開発環境(IDE)である、Xcodeの使い方を覚える必要があると思い、そのメモとして残したいと思います。タイトルは入門となっていますが、今後も随時更新して行く予定です。(多分)

Install & Setup

Macを持っていればXcodeはアップルのAppStoreで無料ダウンロードができます。アプリを作成するためには、インストール後、初めての画面で「Create a new Xcode project」で新しいプロジェクトを作成する必要があります。

プロジェクトの種類は色々ありますが、最初は「iOS」の「Single View Application」を選択してみましょう。プロジェクトの設定画面が現れますが、実際アプリを登録しない限りは適当な名前でも問題ありません。言語はSwiftを使います。

次はファイルの保存場所を選択します。Gitのリポジトリに生成したい場合は「Create Git repository on」を選択します。ここまでの設定が終われば、アプリを作る準備は完了です。

Story Board

ストーリーボードは画面を開発するためのファイルです。左側のフォルダボタンよりプロジェクトのファイル一覧を見ることができます。Main.storyboardファイルを選択すると、ストーリーボード上にアプリの画面を表すビューコントローラーが表示されます。ここにボタンや画像などを置いて、アプリを作成することができます。

画面の右側下にあるオブジェクトライブラリボタンより、ボタンやラベルなどを選択し、ドラッグ・アンド・ドロップでビューコントローラー上にオブジェクトを配置してみましょう。各オブジェクトの見た目などの設定は、右側のアトリビュートインスペクタで可能です。基本的にオブジェクトを選択すると表示されます。

IBOutlet

次はオブジェクトをSwiftソースに紐付けてみましょう。SwiftはJavaScriptやRubyなどの言語と似ていますので、Swiftについての詳細説明は割愛します。まずは、右側の二つのリングになっているボタンを押してみましょう。そうすると、ストーリーボードと、ビューコントローラーが同時に表示されます。

この状態でラベルなどのオブジェクトを選択し、controlキーを押した状態で、Swiftソース上にドラッグ・アンド・ドロップして、名前を入れてConnectを押します。ラベルの場合、下記のようなコードが追加されます。

@IBOutlet weak var label: UILabel!

このラベルに好きなテキストを表示させたい場合は、アトリビュートインスペクタで設定しても良いですが、super.viewDidLoad()の下に下記のコードを追加しても表示されます。実行(Command + R)するとシミュレータで確認することが出来ます。

label.text = "Hello World!"

IBAction

続いてボタンのクリックなどのアクションを設定してみましょう。アクションを設定するボタンなどのオブジェクトを選択して、controlキーを押した状態で、Swiftソース上にドラッグ・アンド・ドロップします。ここで「Connection」をActionに設定してください。

buttonTapなどの名前を入れて「Connect」を押すと下記のような関数が出力されます。この関数の中に、IBOutletで設定したlabel.text = "Hello World!"などのコードを入れると、ボタンが押された時に、イベントが発生することになります。

@IBAction func buttonTap(_ sender: AnyObject) { }

Sample Application

今までの知識を使って簡単なアプリを作ってみましょう。アプリの内容はとあるマンガのキャラクター名を教えてくれるアプリです。テキストボックスに英字で名前を入れると漢字のフルネームが出力される仕組みです。Swiftの説明は割愛します。

このアプリの作成に必要なオブジェクトは次の通りです。

  • Label(案内メッセージ)
  • Text Field(名前の入力欄)
  • Button(名前表示アクションボタン)
  • Label(漢字名を表示させるラベル)

次はボタンが押された時に実行されるプログラムです。オブジェクトのoutlet名は任意のものでも構いません。データは連想配列で管理をしています。

@IBAction func buttonTap(_ sender: AnyObject) {
  let rabbitHouse: [String: String] = [
    "chino": "香風智乃",
    "cocoa": "保登心愛",
    "rize": "天々座理世",
    "sharo": "桐間紗路",
    "chiya": "宇治松千夜"
  ]
  
  let engName = inputName.text!
  
  if let jpnName = rabbitHouse[engName] {
    // 値が取得できた場合
    viewName.text = jpnName
  } else {
    // 値が取得できない場合
    viewName.text = "Undefined"
  }
}

プログラムの流れは簡単に説明しますと次の通りです。

  • rabbitHouseという連想配列に英字名に対応する漢字名の情報を入れます。
  • テキストフィールドに入力された名前をキーに連想配列の値を取得します。
  • 値が取得できれば、漢字名を表示させるラベルに取得した値をセットします。
  • できなかった場合はUndefinedが表示されます。

Auto Layout

休みコーナーではありませんが、Auto Layoutを使って、ウェブページのように色々な画面サイズにおいて、同じ比率のレイアウトを保つ方法を紹介したいと思います。イメージがないので、理解するのが難しいかも知れませんが、ご了承ください。

目標としては、ステータス・バーを除いた縦サイズを100として、ヘッダー25、コンテンツ50、フッター25の割合でviewを配置します。更にコンテンツの中には、横サイズを100として、左コンテンツ25、右コンテンツ75の割合で配置します。

まずは、簡単に制約(Constraint)を使ってみましょう。例としてボタンをストーリーボード上の右下に配置してみてください。そして、下に見える4つのアイコンの四番目(Resolve Auto Layout Issues)をクリックして3番目(Add Missing Constraints)を選択してみてください。そうすると、ボタンが右下に固定される制約が自動的に追加されます。(違う可能性もあります)

もし、要素を中央寄せにしたい場合は、下に見えるアイコンの二番目(Align)をクリックして、一番下の(Horizontally/Vertically in Container)にチェックを入れます。制約をストーリーボード上に反映したい時は、制約を入れる際にUpdate FramesのItems of New Constraintsを選択するか、Resolve Auto Layout IssuesよりUpdate Constraintsをクリックします。

前置きが長くなりました。比率の制約を入れるためには、二つの要素を選択します。そして、下に見える4つのアイコンの三番目(Pin)をクリックして、Equal WidthsとEqual Heightsを扱う必要があります。

まずはヘッダーとして使うviewを配置します。そして、 Pinより0にする制約を追加します。Constrain to marginsもチェックを外します。制約が適用されたら、ヘッダーのviewSafe Areaを同時に選択します(Document Outlineより選択できます)。その状態でPinをクリックし、Equal Heightsにチェックを入れます。ヘッダーの高さがヘッダーと同じようになります。

そして、Document OutlineのConstraintsにView.heightが追加されていることを確認し、右側のアトリビュートインスペクタよりMultiplierを0.25に変更してみてください。そうするとヘッダーが全体画面の高さの25%になり、すべての画面サイズ(機種)において適用されます。このような要領でコンテンツとフッター、コンテンツも追加してみてください。詳細の説明は省略させて頂きます。

Image View

画像を設定し、表示する方法について説明します。画像の表示にはラベルやボタンと同じく、オブジェクトライブラリにあるImage Viewを使います。ストーリーボードに配備したら、IBOutletでオブジェクトとソースを紐づけてください。表示する画像はプロジェクトフォルダの中にある「Assets.xcassets」に置いてください。Finderからのドラッグ・アンド・ドロップで簡単に置くことができます。

これで画像を表示する準備は完了しました。下記のようにviewDidLoad()関数の中に画像を表示するコードを追加すると、アプリ起動時に画像が表示されます。ちなみに、画像を比率の保持や全体表示などは、アトリビュートインスペクタのViewにあるContent Modeより色々と設定が可能です。各設定の内容については省略させて頂きます。

@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
  super.viewDidLoad()
  imageView.image = UIImage(named: "画像名")
}

Touch Event

あくまで基本的な使い方のみを紹介するつもりでしたが、上記で説明しましたImage ViewはIBActionの設定ができないことを知ってしまい、Touch Event処理も説明することにしました。しかし、私も初心者ですので、説明するのはイベント設定方法のみとなります。まずはタッチ検知のためにUser Interaction Enabledtrueにし、タグを付与する必要があります。

override func viewDidLoad() {
  super.viewDidLoad()
  imageView.isUserInteractionEnabled = true
  imageView.tag = 1

上記の設定はアトリビュートインスペクタでも可能ですので、参考にしてください。続いてタッチイベント検知についてです。方法は主にtouchesBegantouchesEnded関数を使った2つがありますが、使い方は似ているので、touchesBeganを例とします。下記はタッチしたオブジェクトのタグを取得し、イベントを設定したいオブジェクトのタグの場合、処理をするコードとなります。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  for touch: UITouch in touches {
    let tag = touch.view!.tag
    switch tag {
    case 1:
      〜処理〜
    default:
      break
    }
  }
}

Action Segue

上記でタッチイベントについて説明したのはImage View、つまり表示した画像をクリックして画面の遷移をするためです。画面遷移のためにはストーリーボードで新しい画面を作成する必要があります。オブジェクトライブラリからView Controllerを選択してストーリーボードに置いてください。続いて、以下の手順で新しいView Controller用のSwiftファイルを作成します。

  1. command + n より新規ファイル画面を開きます
  2. iOSのCocoa Touch Classを選択して次に進みます
  3. クラス名はSubViewControllerにし、言語はSwiftを選択します
  4. 既存のView Controllerと同じ場所であることを確認し、作成します

Swiftファイルの作成が終わったら、ストーリーボード上で新しいView Controllerを選択し、アイデンティティインスペクタにあるCustom ClassのClassよりSubViewControllerを設定します。IBActionの設定が可能はボタンなどは、親のView Controllerにあるボタンを、controlキーを押した状態で新しいView Controllerに置くことでAction Segueの設定が簡単にできます。

しかし、Image Viewなどは手動でAction Segueの設定を行うしかありません。ストーリーボード上で新しいView Controllerを選択し、上の3つのボタンの中で左のボタンを右クリックします。Presenting SeguesのPresent Modallyの右にある+を、親のView Controllerにドラッグ・アンド・ドロップしてください。そうするとストーリーボードにiPhoneのホームボタンみたいなものが追加されます。

続いて、ホームボタンみたいなものをクリックし、アトリビュートインスペクタのIdentifierにtoSubViewControllerを設定します。これで画面遷移の準備は終わりました。後は、Swiftにコードを書くだけです。Touch Eventで紹介したコードのcase 1:の下に、下記のコードを追加してください。これでImage Viewのクリックからの画面遷移ができるようになりました。

performSegue(withIdentifier: "toSubViewController", sender: nil)

Prepare

遷移先の画面で新しい処理を行う場合は、単純に遷移先のView Controllerに処理を記述すれば良いですが、遷移元の画面から情報を受け取りたい場合もあります。その時は、画面遷移時に呼ばれるprepare関数を使います。遷移元画面でprepare関数を定義し、遷移先の画面を取得して変数などを操作することができます。下記はサンプルのコードとなります。

override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
  if (segue.identifier == "toSubViewController") {
    let subViewController: SubViewController = (segue.destination as? SubViewController)!
    subViewController.image = UIImage(named: "画像名")
  }
}

上記のコードではIdentifierがtoSubViewControllerの場合、segue.destinationas?でダウンキャストしてSubViewControllerを取得しています。続いて、取得したSubViewControllerのimageという定数もしくは変数に画像を設定しています。もちろんSubViewControllerにも同様の定数もしくは変数の宣言が必要にります。下記はサンプルのコードです。

var image: UIImage!

  override func viewDidLoad() {
    super.viewDidLoad()
    guard let viewImage = image else {
      return
    }
  }
}

補足ですが、下記のように定数や変数をprepare関数に渡す方法もあります。

let images: [String] = ["画像1", "画像2"]

func goToSubViewController() {
  performSegue(withIdentifier: "toSubViewController", sender: nil)
        self.performSegue(withIdentifier: "toSecondViewController", sender: images)
  }

override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
  if (segue.identifier == "toSubViewController") {
    let subViewController: SubViewController = (segue.destination as? SubViewController)!
    subViewController.images = sender as! [String]
  }
}

Unwind Segue

これで画面遷移はできるようになりましたが、まだ遷移元に戻ることはできません。遷移元に戻るには、下記のようなコードを遷移元のView Controllerに追加します。そして、ストーリーボード上で遷移先のView Controllerを選択し、controlキーを押した状態で、ボタンを上の3つのボタンの中で右のボタンまで引っ張ります。そして、下記の関数を選択すれば完了です。

@IBAction func returnViewController(segue:UIStoryboardSegue) {
}

但し、画面遷移時と同じくImage Viewで遷移元に戻るたい場合は手動でUnwind Segueの設定が行う必要があります。上記のコードを遷移元に追加した状態で、ストーリーボード上で遷移先のView Controllerを選択し、上の右のボタンを右クリックします。そして、上記の関数名が表示された箇所の右にある+を、遷移先のView Controllerにドラッグ・アンド・ドロップしてください。

そしてTouch Eventの時と同じ要領で遷移先のView ControllerからImage Viewに新しいタグを追加し、touchesBegan関数より下記のコードを実行するようにします。これで画像をクリックしても遷移元の画面に戻ることができます。

performSegue(withIdentifier: "returnViewController", sender: self)

Collection View

今までの復習も兼ねで、アルバムのように画像をリスト表示できるCollection Viewについて説明します。アプリの初期画面で表示したい場合は特に問題ありませんが、もしボタンなどでの画面遷移で配列のパラメータなどを送りたい場合は、手動でAction Segueを行う必要があります。Action Segueの設定方法は前項のAction Segueをご参考ください。

まずは、オブジェクトライブラリのCollection Viewを選択し、ストーリーボード上にドラッグ・アンド・ドロップしてください。画面全体にリストを表示したい場合は、Auto Layoutを使いましょう。ストーリーボードのCollection Viewを選択するとCellという要素が含まれています。Collection Viewではこれをテンプレートに、画像などをリスト表示することができます。

Cellを選択して、アトリビュートインスペクタのIdentifierにimageCellを設定してください。続いて、ストーリーボード上のCollection Viewを選択し、controlキーを押した状態で、上の3つのボタンの中で左のボタンまで引っ張ります。Outletsが表示されたら、dataSourceを選択します。dataSourceの時と同じ手順で、delegateも選択してください。

Outletsの選択後はCollection Viewを制御するViewControllerを開いて、下記のようにクラス名にプロトコルを追加します。

class ListViewController: UIViewController, UICollectionViewDataSource

プロトコルUICollectionViewDataSourceを使う場合は、collectionView関数を定義する必要があります。定義しない場合、警告が発生しますので、Fixボタンより自動で定義することもできます。以下はCollection Viewに赤いCellを5個表示するコードです。

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 5;
}
    
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath)
    cell.backgroundColor = UIColor.red
        
    return cell
}

シミュレーターで5個のセルが表示されたら、次は画像を表示してみましょう。画像を表示するためにはセルを操作するSwiftファイルが必要です。新規でSwiftファイルを作成したら、Collection ViewのCellを選択し、Custom Classに新規で作成したSwiftファイル名を設定します。後は、CellにImage Viewを置いて、新しいSwiftファイルにIBOutletで紐付けば完了です。

紐付き後はCollection ViewのViewControllerに戻り、collectionViewに画像を設定するコードを追記します。そのためには以下のようにas!を使って、Cellの定数をを新しいSwiftファイルの型にキャストする必要があります。これで新しいSwiftファイルに紐付いたImage Viewの名前を使って、画像を設定することができるようになりました。

var rabbitHouse: [String]!

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCell", for: indexPath) as! ListCollectionViewCell
        let columnIndex = indexPath.row
        cell.cellImage.image = UIImage(named: rabbitHouse[columnIndex])
        
        return cell
}

では、最後にセルのサイズと画面の回転によってセルの表示を変える方法について説明します。セルのサイズを設定するには、下記のようにプロトコルを更に追加する必要があります。ちなみに、セルとセルの間のマージンはストーリーボード上でCollection Viewを選択し、右側のユティリティにあるサイズインスペクタのMin Spacingより設定が可能です。

class ListViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout

続いてセルのサイズを設定する関数も定義します。画面が回転するたびに違うサイズを設定したいので、合わせて画面の回転を検知する関数も定義する必要があります。以下は画面が縦の場合、セルのサイズを画面の横サイズの1/2に、画面が横の場合、セルのサイズを画面の横サイズの1/4にするコードとなります。- 2はMin Spacingで設定したマージンの数値となります。

@IBOutlet weak var varCollectionView: UICollectionView!

func collectionView(_ collectionView: UICollectionView,
                    layout collectionViewLayout: UICollectionViewLayout,
                    sizeForItemAt indexPath: IndexPath) -> CGSize {
    var width = collectionView.frame.size.width / 2 - 2
    var height = width
    // 画面の横サイズが縦サイズより大きい場合
    if collectionView.frame.size.width > collectionView.frame.size.height {
        width = collectionView.frame.size.width / 4 - 2
        height = width
    }
    return CGSize(width: width, height: height)
}
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        
    super.viewWillTransition(to: size, with: coordinator)
        
    // 画面が回転したらセルのサイズを再計算する
    varCollectionView.reloadData()
}