JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた〜その16 Setting Page〜


JavaScriptプログラマー(JSer)がSwiftデビューして、ただ作りたいアプリを作ってみたシリーズ第16回目です。

前回は実機で動作確認する方法の紹介でした。今回はNavigationに設置した設定アイコンを押したときに現れる設定に関するページの実装についてです。

設定アイコンの設置

UINavigationControllerのヘッダの右上の部分に設定アイコンを設置する方法は5回目の連載記事で紹介しました。

少しおさらいすると、設定ボタン用のアイコンを事前に登録し、UIButtonをイメージを読み込んで、self.navigationItem.rightBarButtonItemにUIBarButtonItemとしてセットにセットします。

BaseTweetViewController.swift

import Foundation
import UIKit
import TwitterKit
 
class BaseTweetViewController: UIViewController {
     
    override func viewDidLoad() {
        super.viewDidLoad()
        // set title
        self.navigationItem.title = "title"
 
        // nav right item button
        var settingBtn:UIButton = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
        settingBtn.addTarget(self, action: "onClickSetting", forControlEvents: UIControlEvents.TouchUpInside)
        settingBtn.frame = CGRectMake(0, 0, 24, 24)
        settingBtn.setImage(UIImage(named: "settings-50.png"), forState: .Normal)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: settingBtn)
    }
 
    func onClickSetting() {
        let settingViewCtrl = SettingViewController()
        let modalView = UINavigationController(rootViewController: settingViewCtrl)
        modalView.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
        self.presentViewController(modalView, animated: true, completion: nil)
    }
}

onClickのイベントハンドラでmodalのviewを表示するための処理をしています。
今回は下からすっと表示されるpresentViewController(animated: true)を使用しています。この時点で実行して設定アイコンをタップすると、何もない画面が出現すると思います。

では、新規に設定ページと対応するSettingViewControllerを作っていきましょう。

設定項目

TwitStockerで設定できる項目は以下のようになっています。

  • ハッシュタグ: 設定すると自分のツイートの中で特定のhashtagを含むものを表示するようになる。設定しないとURLを含むツイートを全て表示する。
  • スワイプ時の確認非表示: ONにするとtweetをスワイプした時のconfirmationを出さないようになる
  • 既読データをリセット: 既読としてCoreDataに保持しているデータを破棄するためのボタン
  • Twitterログアウト: ログアウトボタン。押されるとチュートリアル画面に戻る。

完成時のスクリーンはこんな感じ。
english_screen4

設定ページの実装

実装方針

配置に関して、本当はstoryboardを使ったりすると簡単に配置などできるのかもしれませんが、イマイチ使い方がわからないため、フォーム部品の配置などはコードで行っています。
データの保存に関しては、第9回記事で紹介したCoreDataを使用してサーバーは使用せずクライアント側で永続的に設定データを保持しています。

コード

SettingViewController.swiftを作成して初期化時のviewDidLoadの中でフォームを配置していきます。今回の記事ではフォームの設置についての解説でデータの保存に関しては次回の記事で。
完成図を見てもらうとわかるように、TwitStockerではフォームの部品を設置する際に1行の項目の中の左側にラベル、右側にinput textやswitchのフォーム部品を設置するようなレイアウトになっています。この位置調整にだいぶ苦労してひどくわかりづらいコードになっています。(もっと良いレイアウトの方法をご存知でしたら教えていただけると助かります!)

SettingViewController.swift

import Foundation
import UIKit
import TwitterKit

class SettingViewController: UIViewController {
    
    var closeBtn: UIBarButtonItem?
    var hashtagInput: UITextField?
    var noConfirmSwitch: UISwitch?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 設定ページのタイトルのセットと背景色の指定
        self.title = NSLocalizedString("setting_title", comment: "")
        self.view.backgroundColor = Constants.Theme.base()

        // 右上に表示される閉じるボタンの設置
        var closeBtn:UIButton = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
        closeBtn.addTarget(self, action: "onClickClose", forControlEvents: UIControlEvents.TouchUpInside)
        closeBtn.frame = CGRectMake(0, 0, 20, 20)
        closeBtn.setImage(UIImage(named: "close-50.png"), forState: .Normal)
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: closeBtn)

        // フォーム部品配置のためのパラメーター
        let width = self.view.frame.size.width
        let height = self.view.frame.size.height
        let rowHeight: CGFloat = 40.0
        let rowPaddingTop: CGFloat = 4.0
        let rowPaddingLeft: CGFloat = 20.0
        let inputWidth: CGFloat = 160.0
        let switchWidth: CGFloat = 60.0

        // ハッシュタグ設定のためのラベルの生成
        let hashtagLabel: UILabel = UILabel(frame: CGRectMake(rowPaddingLeft, rowPaddingTop, width - inputWidth - rowPaddingLeft * 2, rowHeight))
        hashtagLabel.text = NSLocalizedString("setting_hashtag_input_label", comment: "")

        // ハッシュタグ設定のためのinput textフィールド
        let hashtagInput = UITextField(frame: CGRectMake(width - inputWidth - rowPaddingLeft, rowPaddingTop, inputWidth, rowHeight))
        hashtagInput.borderStyle = UITextBorderStyle.None
        hashtagInput.placeholder = NSLocalizedString("setting_hashtag_input_placeholder", comment: "")
        hashtagInput.text = SettingStore.sharedInstance.getHashtag()
        hashtagInput.delegate = self
        self.hashtagInput = hashtagInput

        // ハッシュタグ設定のためのラベルとtextフィールドをUIViewに一度セットする
        let container1 = UIView()
        container1.backgroundColor = UIColor.whiteColor()
        container1.frame = CGRectMake(0, 100, width, rowHeight + rowPaddingTop * 2)
        container1.addSubview(hashtagLabel)
        container1.addSubview(hashtagInput)
        // ハッシュタグのラベルとtextフィールドセットを画面に追加
        self.view.addSubview(container1)

        // ハッシュタグの説明文、複数行や折り返しの設定をしている
        let hashtagDescription: UILabel = UILabel(frame: CGRectMake(rowPaddingLeft, 160, width - rowPaddingLeft * 2, 0))
        hashtagDescription.text = NSLocalizedString("setting_hashtag_description", comment: "")
        hashtagDescription.font = UIFont.systemFontOfSize(12)
        hashtagDescription.textColor = Constants.Theme.gray()
        hashtagDescription.numberOfLines = 0
        hashtagDescription.textAlignment = NSTextAlignment.Left
        hashtagDescription.sizeToFit()
        hashtagDescription.lineBreakMode = NSLineBreakMode.ByCharWrapping
        self.view.addSubview(hashtagDescription)
        
        // スワイプ時の確認非表示設定のラベル
        let noConfirmLabel: UILabel = UILabel(frame: CGRectMake(rowPaddingLeft, rowPaddingTop, width - switchWidth - rowPaddingLeft, rowHeight))
        noConfirmLabel.text = NSLocalizedString("setting_swipe_confirm_label", comment: "")

        // スワイプ時の確認非表示設定のSwitch部品
        let noConfirmSwitch = UISwitch(frame: CGRectMake(width - switchWidth - rowPaddingLeft, rowPaddingTop + 4, switchWidth, rowHeight))
        noConfirmSwitch.on = SettingStore.sharedInstance.isNoConfirm()
        self.noConfirmSwitch = noConfirmSwitch

        // スワイプ時の確認非表示設定ラベルとswitch部品をまとめて追加
        let container2 = UIView()
        container2.backgroundColor = UIColor.whiteColor()
        container2.frame = CGRectMake(0, 260, width, rowHeight + rowPaddingTop * 2)
        container2.addSubview(noConfirmLabel)
        container2.addSubview(noConfirmSwitch)
        self.view.addSubview(container2)

        // 既読データのリセットボタンの配置
        let restBtn: UIButton = UIButton(frame: CGRectMake(rowPaddingLeft, 340, width - rowPaddingLeft * 2, rowHeight))
        restBtn.setTitle(NSLocalizedString("setting_reset_read_data", comment: ""), forState: UIControlState.Normal)
        restBtn.backgroundColor = Constants.Theme.reset()
        restBtn.layer.cornerRadius = 8
        restBtn.addTarget(self, action: "onClickResetReadData", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(restBtn)
        
        // Twitterログアウトボタンの配置
        let logoutBtn: UIButton = UIButton(frame: CGRectMake(rowPaddingLeft, 400, width - rowPaddingLeft * 2, rowHeight))
        logoutBtn.setTitle(NSLocalizedString("setting_twitter_logout", comment: ""), forState: UIControlState.Normal)
        logoutBtn.backgroundColor = Constants.Theme.twitter()
        logoutBtn.layer.cornerRadius = 8
        logoutBtn.addTarget(self, action: "onClickLogout", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(logoutBtn)
        
        // footerの配置
        let footer: UILabel = UILabel(frame: CGRectMake(rowPaddingLeft * 2, height - 50, width - rowPaddingLeft * 4, rowHeight))
        footer.text = NSLocalizedString("common_app_name", comment: "") + " " + NSLocalizedString("common_version", comment: "") + Constants.Product.version() + ", " + NSLocalizedString("common_copyright", comment: "")
        footer.textColor = Constants.Theme.gray()
        footer.adjustsFontSizeToFitWidth = true
        self.view.addSubview(footer)
    }

    ...
}

こんな感じで位置調整をダサい方法でしつつ、なんとか納得のいくフォーム部品のレイアウト構成ができました。

今回の記事では設定パネルの表示の部分を説明しました。次回はフォームの部品からデータを取得したり、CoreDataに保存したりする部分の解説をしようと思います。

TwitStockerのダウンロードはこちらから。
https://itunes.apple.com/en/app/twitstocker/id958798898?l=ja&ls=1&mt=8