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


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

前回は設定パネルの表示のためのフォーム部品の設置について説明しました。今回は設定パネルに設置したフォーム部品からデータを取り出しアプリ内で永続的に保存する例を紹介します。

まず、TwitStockerで設定項目として永続的にデータを保存しておくべきものは以下の2つです。

  • ハッシュタグの文字列
  • スワイプ時の確認非表示ON/OFFのフラグ

english_screen4

前回の記事で設定パネルにあたるSettingViewControllerのinpu textなどのフォームの設置についてのコードの中で、UITextFieldのインスタンスとしてhashTagのtextフィールド、UISwitchのインスタンスとして確認非表示のための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()

        ...
        // closeボタンの設置とコールバックの設定
        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)

        ...
        // hashtagのためのtextフィールドを作成し、self.hashtagInputにセット
        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

        // 確認表示・非表示を切り替えるswitchを作成し、self.noConfirmSwitchにセット
        let noConfirmLabel: UILabel = UILabel(frame: CGRectMake(rowPaddingLeft, rowPaddingTop, width - switchWidth - rowPaddingLeft, rowHeight))
        noConfirmLabel.text = NSLocalizedString("setting_swipe_confirm_label", comment: "")
        let noConfirmSwitch = UISwitch(frame: CGRectMake(width - switchWidth - rowPaddingLeft, rowPaddingTop + 4, switchWidth, rowHeight))
        noConfirmSwitch.on = SettingStore.sharedInstance.isNoConfirm()
        self.noConfirmSwitch = noConfirmSwitch
       
        ...     
    }
}

保存されるタイミングはこの設定パネルが閉じるときとして実装します。以下のコードの部分で閉じるボタンが押された時にUI部品のデータをCoreDataを使って永続的に保存して、設定パネルを閉じています。

SettingViewController.swift

    func onClickClose() {
        // save all settings
        SettingStore.sharedInstance.saveHashtag(self.hashtagInput?.text)
        if let noConfirm = self.noConfirmSwitch {
            SettingStore.sharedInstance.saveNoConfirm(noConfirm.on)
        }
        // close modal view
        dismissViewControllerAnimated(true, completion: nil)
    }

hashtagInputとnoConfirmはいずれもオプショナルなので、適切にプロパティにアクセスするためには一度オプショナルではない型に変換する必要があります。なので、let noConfirmにself.noConfirmSwitchをifの条件文で代入して変換しています。

さて、SettingStoreというクラスはCoreDataを扱うクラスです。以前の第9回目CoreDataの記事で紹介したものと同様です。

まずはCoreDataのmodelの作成として、xcode上でhashtagとnoConfirmをそれぞれStringとBooleanで作成します。

スクリーンショット 2015-03-08 16.51.17

そしてシングルトンのCoreDataのモデルに対応するユーティリティクラスを作成します。

SettingStore.swift


import Foundation
import UIKit
import CoreData

class SettingStore {
    
    let entityName: String = "Setting"
    
    let KEY_HASHTAG: String = "hashtag"
    let KEY_NO_CONFRIM: String = "noConfirm"
    
    var config: NSManagedObject?
    
    class var sharedInstance :SettingStore {
        struct Static {
            static let instance = SettingStore()
        }
        return Static.instance
    }
    
    // read from CoreData
    func load() {
        /* Get ManagedObjectContext from AppDelegate */
        let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let manageContext = appDelegate.managedObjectContext!
        /* Set search conditions */
        let fetchRequest = NSFetchRequest(entityName: self.entityName)
        var error: NSError?
        /* Get result array from ManagedObjectContext */
        let fetchResults = manageContext.executeFetchRequest(fetchRequest, error: &error)
        if let results: Array = fetchResults {
            if results.count > 0 {
                self.config = results[0] as? NSManagedObject
            }
        }
    }
    
    func saveSettingData(key: String, value: AnyObject?){
        /* Get ManagedObjectContext from AppDelegate */
        let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let managedContext: NSManagedObjectContext = appDelegate.managedObjectContext!
        if self.config == nil {
            /* Create new ManagedObject */
            let entity = NSEntityDescription.entityForName(self.entityName, inManagedObjectContext: managedContext)
            self.config = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext)
        }
        /* Set the name attribute using key-value coding */
        self.config?.setValue(value, forKey: key)
    }
    
    func getHashtag() -> String? {
        return self.config?.valueForKey(self.KEY_HASHTAG) as String?
    }

    func saveHashtag(hashtag: String?) {
        self.saveSettingData(self.KEY_HASHTAG, value: hashtag)
    }
    
    func isNoConfirm() -> Bool {
        if let r = self.config?.valueForKey(self.KEY_NO_CONFRIM) as Bool? {
            return r
        }
        return false
    }

    func saveNoConfirm(noConfirm: Bool) {
        self.saveSettingData(self.KEY_NO_CONFRIM, value: noConfirm)
    }
}

上記のクラスにhashtagとnoConfirmそれぞれのモデルの型に応じてのgetter/setterを記述しています。
このユーティリティクラスを利用することで永続的データの読み出しと書き出しをほとんどDataを意識することなく、外側のクラスから利用できるようになっています。とりあえず、これを使っていましたが、genericsとか使ってもっと汎用的に書けそうですね。

あとはTwitStockerを起動した時などでは、以下のgetterを通じてconfigの値を読み出して定義動作の振る舞いを変更すればOKです!

例えば、hashtagの設定に応じてTwitter Search APIに渡すQueryを書き換える部分

        var q = "from:" + Twitter.sharedInstance().session().userName;
        let hashtag = SettingStore.sharedInstance.getHashtag()
        if hashtag == nil || hashtag == "" {
            // hashtagが設定されていない場合は"URLを含んで画像を含まない"という条件にする
            q += "+filter:links+-filter:images"
        }else{
            // hashtagが設定されている場合はhashtagで検索
            q += "+" + hashtag!
        }

確認非表示の場合に右スワイプでそのままfavoriteする例

        if SettingStore.sharedInstance.isNoConfirm() {
            // 確認無しにfavorite APIをコールする
            self.submitFavorite(index, cell: cell)
        } else {
            // show confirmation
            ...
        }

以上、今回は設定パネルに設置したフォーム部品からデータを取り出しアプリ内で永続的に保存する例を紹介でした。次回は NSDateを使って日付の比較をする部分の解説をしようと思います。

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