Androidはワンツーパンチ 三歩進んで二歩下がる

プログラミングやどうでもいい話

UITableView + Static Cellsでアプリ内設定画面を作成するサンプル(XCode9, Swift4, StoryBoard使用)

アプリ内の設定画面のサンプルを作りました。
ステップ・バイ・ステップで作成方法を書いていきます。

※ 当初、記事を書いていたらチュートリアルっぽくなってきたので
チュートリアルとタイトルに付けていたのですが、
記事公開直後に私は特に設定画面のスタンダードに詳しくないから自信がない
と思ってしまったということと、
アプリのバージョンをSettings.bundleあたりでなくアプリ内の設定画面で表示するのは
イマイチな例だったかもしれないなーと思いつつあるんだけど
書いてしまったしな〜〜〜という状況になってしまったので、タイトルはサンプルにしました。
申し訳ありませんが、こんな例があるんだなーぐらいに受け止めていただければ幸いです。
グダグダとすみません(´-﹏-`;)

開発環境

環境はXcode9.2、Swift4.0を使用しています。

サンプルの仕様

このようなシンプルなものを作ります。

f:id:sakura_bird1:20180307211415p:plain:w200

  • 「名前」はクリックすると入力画面に遷移し、入力された値をUserDefaultsクラスを使ってアプリ内に保存します。
  • 「アプリバージョン」はアプリ内でバージョンを取得して表示します。
  • 「ライセンス」はUITableViewのセクション内の行数を2行にするためのダミーです。

サンプルの実装

新しくプロジェクトを作成します。
テンプレート選択の箇所は、iOS「Single View App」を選択します。

不要なクラスなどの削除

デフォルトで作成される「ViewController.swift」を右クリック > Deleteを選択して削除します。

f:id:sakura_bird1:20180307214707p:plain:w200


Main.storyboardを開いてデフォルトで作成された「View Controller Scene」をクリック > deleteキーで削除します。

f:id:sakura_bird1:20180307215019p:plain:w200

設定画面を設置する

Main.storyboardにObject Libraryから「Table View Controller」を設置します。
右側のAttributes Inspectorの中の「Is Initial View Controller」にチェックしておきます。

f:id:sakura_bird1:20180307215808p:plain:w200


対応するクラスのファイルを作成します。
File > New > File… > Cocoa Touch Classを選択してNextボタンを押します。

次のダイアログで、Classを「SettingsTableViewController」、Subclass ofを「UITableViewController」にします。
Nextボタン > Createボタンを押し、作成します。

f:id:sakura_bird1:20180307220946p:plain:w200


Main.storyboardを開き、左側のDocument Outlineから「Table View Controller」を選択します。
右側のIdentity InspectorのCustom ClassのClassを「SettingsTableViewController」にします。

f:id:sakura_bird1:20180307222555p:plain:w200


左側のDocument Outlineから「Table View Controller」を選択して
Xcodeのメニュー > Editor > Embed In > Navigation Controller を選択します。

f:id:sakura_bird1:20180307222048p:plain:w200

Table View sectionを設定する

左側のDocument Outlineから「Table View」を選択します。
右側のAttributes Inspectorの項目を次のように変えます。

Content Static Cells
Sections 2
Style Grouped

f:id:sakura_bird1:20180307223934p:plain:w300


左側のDocument OutlineからTable Viewの下の「Section-1」を選択します。
右側のAttributes InspectorのTable View Sectionの項目を次のように変更します。

Rows 1
Header 設定

同様に、「Section-2」を次のように変更します。

Rows 2
Header その他

f:id:sakura_bird1:20180307225408p:plain:w200

Table View cellを設定する

左側のDocument Outlineから1つめの「Table View Cell」を選択します。
右側のAttributes InspectorのTable View Cellの項目を次のように変えます。

Style Right Detail

f:id:sakura_bird1:20180307230757p:plain:w200

セルの内側のContent View の内側にある「Title」を選択して
Attributes InspectorのLabelのTextを「Title」→「名前」に変更します。

f:id:sakura_bird1:20180307231645p:plain:w200

同様に「Detail」のTextを削除し、Colorはライトグレーを選択します。

f:id:sakura_bird1:20180307232236p:plain:w200


同じように「その他」セクションを設定します。

f:id:sakura_bird1:20180307234218p:plain:w300

セルの項目をソースコードに接続する

UITableViewでもStatic Cellsで作ったものは、通常の画面のように
直接セルの中のフィールドからソースコードにドラッグ&ドロップして
IBOutletの定義が出来ます。

Assistant Editorをクリックしてソースコードを開きます。
先程作成した「SettingsTableViewController」となっているのを確認してください。

f:id:sakura_bird1:20180308003151p:plain:w200


名前のDetailをDocument Outlineから選択し、
コントロールキーを押しながらソースコードにドラッグします。
classの下あたりでドロップすると、ダイアログが表示されます。

f:id:sakura_bird1:20180308023932p:plain:w300

Nameに「nameLabel」と入力してConnectを押します。

f:id:sakura_bird1:20180308024025p:plain:w200

コードが挿入されます。

同様にアプリバージョンのDetailも「versionLabel」と入力してConnectします。

  @IBOutlet weak var nameLabel: UILabel!
  @IBOutlet weak var versionLabel: UILabel!

ソースコードの編集

セクションとセルの数を設定

Standard EditorでSettingsTableViewControllerを開きます。
まず次の2つの関数を変更します。その下にコメント行がたくさんありますが削除してしまって大丈夫です。

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 0
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return 0
    }

次のように修正します。

    // MARK: - Table view data source

  override func numberOfSections(in tableView: UITableView) -> Int {
    // セクションの数を返します
    return 2
  }

  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // それぞれのセクション毎に何行のセルがあるかを返します
    switch section {
    case 0: // 「設定」のセクション
      return 1
    case 1: // 「その他」のセクション
      return 2
    default: // ここが実行されることはないはず
      return 0
    }
  }

このようにセクションとセルの数を書かなければいけないところが手間ですね。

ここで画面左上のBuild and Runボタン(▶)を押して、一度実行してみます。
セルの右側には何も表示されませんが、ちゃんと表示されました。

f:id:sakura_bird1:20180308021842p:plain:w200

アプリバージョンの表示

アプリバージョンを表示します。
viewDidLoad関数を次のように変更します。

  override func viewDidLoad() {
    super.viewDidLoad()

    // アプリのバージョン
    if let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
      versionLabel.text = version
    }
  }

▶ボタンを押して実行してみます。
セルの右側にバージョンが表示されました。
バージョンにつきましては「CFBundleShortVersionString」で調べてみて下さい。

f:id:sakura_bird1:20180308024647p:plain:w200

名前の表示

UserDefaultsの情報を画面にセットする

先程の関数のsuper.viewDidLoad()の下に次の行を書き加えて下さい。

    // UserDefaultsの情報を画面にセットする
    if let name = UserDefaults.standard.value(forKey: "name") as? String {
      nameLabel.text = name
    }

アプリ内の保存領域から「name」というキーとセットになっている文字があれば画面にセットしています。
アプリ内の保存領域については「UserDefaults」で調べてみて下さい。

ただ、今は実行しても何も表示されません。
nameというキーで値を保存したことがないからです。

次に名前の入力画面を作成して保存、遷移元の画面も更新する処理を書いていきます。

名前の入力画面を作成

Main.storyboardを開いてObject Libraryから「Table View Controller」を設置します。

f:id:sakura_bird1:20180308030634p:plain:w200



対応するクラスのファイルを作成します。
File > New > File… > Cocoa Touch Classを選択してNextボタンを押します。

次のダイアログで、Classを「SettingsNameTableViewController」、Subclass ofを「UITableViewController」にします。
Nextボタン > Createボタンを押し、作成します。

Main.storyboardを開き、左側のDocument Outlineから「Table View Controller」を選択します。
右側のIdentity InspectorのCustom ClassのClassをクリックして
「SettingsNameTableViewController」を選択します。

f:id:sakura_bird1:20180308031455p:plain:w300

名前の入力画面へのセグエを追加

左側のDocument Outlineから
「Settings Table View Controller Scene」を選択し、「Table View」の下の
「設定」の下の「Table View Cell」を選択します。

そこから先程設置した画面の「Table View」までコントロールキーを押しながら
ドラッグします。

セグエの種類を選択するポップアップが表示されます。
「Show」を選択して下さい。

f:id:sakura_bird1:20180308033533p:plain:w300

ここで▶ボタンを押して実行してみます。
名前のセルをクリックすると、次の画面が表示されます。
まだ何もない行が表示されるだけです。

名前の入力画面の設定

では、入力欄を定義していきます。
設定画面で行ったのと同じように、
左側のDocument Outlineから「Table View」を選択します。
右側のAttributes Inspectorの項目を次のように変えます。

Content Static Cells
Sections 1
Style Grouped

f:id:sakura_bird1:20180308034352p:plain:w300

セクションも編集します。

左側のDocument OutlineからTable Viewの下の「Table View Section」を選択します。
右側のAttributes InspectorのTable View Sectionの項目を次のように変更します。

Rows 1
Header 名前の編集

f:id:sakura_bird1:20180308165340p:plain:w300


左側のDocument Outlineから「Content View」を選択します。
Object Libraryから「Text Field」を選択し、ドラッグ・アンド・ドロップで
Content View内に設置します。

f:id:sakura_bird1:20180308170234p:plain:w300


設置したText Fieldを選択し、Constraintsを加えていきます。
次の画像のように、上下左右に16と入力してAdd Constraintsをクリックします。

f:id:sakura_bird1:20180308172328p:plain:w250


入力欄の属性を4点変更します。
Attribute Inspectorを開いて次のように編集しましょう。

  • Placeholderに「名前を入力してください」と入力します。
  • デフォルトだと、入力欄の周りに囲み枠が付いているのでBorder Styleを一番左にして非表示にします。
  • Clear Buttonを「Is Always visible」にします。
  • 「Text Input Traits」の中の「Return Key」を「Done」にします。

f:id:sakura_bird1:20180308220624p:plain:w150

セルの項目をソースコードに接続する

設定画面と同様にAssistant Editorをクリックしてソースコードを開きます。
先程作成した「SettingsNameTableViewController」となっているのを確認してください。

名前のText FieldをDocument Outlineから選択し、
コントロールキーを押しながらソースコードにドラッグします。
classの下あたりでドロップすると、ダイアログが表示されます。

Nameに「nameTextField」と入力してConnectを押します。

f:id:sakura_bird1:20180308213848p:plain:w300

コードが挿入されます。

  @IBOutlet weak var nameTextField: UITextField!

ここで実行してみますと、まだ名前の入力画面には何も表示されません。
ソースコードを編集しセクションとセルの数を記述することで表示されます。

セクションとセルの数を設定

Standard EditorでSettingsNameTableViewControllerを開きます。
設定画面と同じように修正します。この関数の後ろのコメント行も削除して大丈夫です。

    // MARK: - Table view data source

  override func numberOfSections(in tableView: UITableView) -> Int {
    // セクションの数を返します
    return 1
  }

  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // それぞれのセクション毎に何行のセルがあるかを返します
    return 1
  }

シンプルなサンプルなのでたった1つしかセクションとセルがありません。
▶ボタンを押して実行してみると入力欄が表示されるようになりました。
入力欄をクリックすると入力も出来ます。
まだ入力されたものをプログラム内で受取り保存する仕組みがないので、
前の画面に戻ったら消えてしまいます。

f:id:sakura_bird1:20180308215314p:plain:w200

UITextFieldDelegateの実装

SettingsTableViewControllerクラスにUITextFieldDelegateプロトコルを実装します。
UITableViewControllerの後ろにUITextFieldDelegateを追加してください。

class SettingsTableViewController: UITableViewController, UITextFieldDelegate {

次にviewDidLoadの「super.viewDidLoad()」の後に次のコードを追加してください。

 nameTextField.delegate = self

さらにソースコードの最後にデリゲートメソッドを追加します。
下記のコードを追加してください。

  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    // keyboardを隠す
    textField.resignFirstResponder()

    return true
  }

このメソッドはリターンキーが押されたというイベントが発生した時に実行されます。
他にも色々なイベントを受け取れます。UITextFieldDelegateで調べてみて下さい。
入力中に表示されていたキーボードを隠す処理を入れました。

実行するとリターンキーを押した時にキーボードが隠れれば成功です。

入力した値の保存と表示

設定画面にUserDefaultsというアプリ内の保存領域の情報を画面に表示する処理を書きました。

    if let name = UserDefaults.standard.value(forKey: "name") as? String {
      nameLabel.text = name
    }

"name"という名前のキーで情報があれば表示しています。
同じように名前の入力欄も情報があれば表示するよう変更します。

先程のviewDidLoad関数の nameTextField.delegate = selfの下に次の行を書き加えて下さい。

    // UserDefaultsの情報を画面にセットする
    if let name = UserDefaults.standard.value(forKey: "name") as? String {
      nameTextField.text = name
    }

入力された内容の保存処理も記述します。
先程のtextFieldShouldReturn関数の
textField.resignFirstResponder()の下に次の行を書き加えて下さい。

    // 入力された内容を保存する
    UserDefaults.standard.set(textField.text, forKey: "name")

"name"という名前のキーで保存しました。
実際のアプリの処理では、入力内容のチェックを行ったり、
余計なスペースを取り除いたり、保存ボタンを付けたりすると思いますが
ここではサンプルのため無条件にリターンキーを押されたら保存することになります。

▶ボタンを押して実行してみます。

何か入力してリターンキーを押します。
前の設定画面に戻ると名前の右側に入力したものは表示されていません。
あらためて名前のセルを押して入力画面に遷移すると、
先程入力した文字が入力欄にセットされていると思います。

設定画面に戻った時に表示されなかったのは、
UserDefaultsの変更があった時に変更通知を受け取る処理が記述されていないからです。

もう一度▶ボタンを押して実行すると今度は表示されるはずです。

設定画面でUserDefaultsの変更があった時に画面を更新する

設定画面を編集してUserDefaultsの変更通知を受け取る処理を記述します。

SettingsTableViewController.swiftを開きます。
viewDidLoadの「super.viewDidLoad()」の後に次のコードを追加してください。

    // UserDefaultsの変更を監視する
    NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange),
                                           name: UserDefaults.didChangeNotification, object: nil)


ソースコードの最後の「 } 」の直前に次のコードを追加してください。

  @objc func userDefaultsDidChange(_ notification: Notification) {
    // UserDefaultsの変更があったので画面の情報を更新する
    if let name = UserDefaults.standard.value(forKey: "name") as? String {
      nameLabel.text = name
    }
  }

  deinit {
    // UserDefaultsの変更の監視を解除する
    NotificationCenter.default.removeObserver(self, name: UserDefaults.didChangeNotification, object: nil)
  }

既にUserDefaultsに保存済なので一度アプリをアンインストールしてから
▶ボタンを押して実行してみます。

入力後に設定画面に戻ると入力内容が表示されます。

f:id:sakura_bird1:20180309012054p:plain:w400

解説は以上となります。

全体のソースコードはこちらにあります。
github.com


以上です。