4713
NSUserDefaultsについてお伺いしたいです。  
例えば4つほどのViewが存在して、2つ目のビューまで遷移したとします。  
そこで、「2つ目のViewまで遷移した」というデータを永続化させ、次回ビルド時にも2つ目のViewから始めたいのですが、  
その場合、NSUserDefaultsを使うのは可能でしょうか？

新しい言語ということもあるのか、Swift版NSUserDefaultsの情報が少なく、Web上のページや解説動画は全て見たのですが、よく理解できませんでした……。

お詳しい方がいましたら、どうか回答宜しくお願い致します。  
出来れば上記した内容のコードをお願いしたいです。  
また、それに近い参考Webページがあれば、ご紹介して頂けるととても嬉しいです。

宜しくお願い致します。

* * *

【補足】  
複数のViewControllerを隔ててしまうとNSUserDefaultsは機能しないのでしょうか？  
2つ目のViewControllerまで遷移したことを記録し、再起動した時に、initial
Viewに表示されている「Loadボタン」を押すと、2つ目のViewControllerに遷移する……などは不可能でしょうか？  
NSUserDefaultsはどこまで画面遷移したか、などの記録ではなく、StringやIntなどの値しか記録できないのでしょうか？
CC BY-SA 3.0
2015-01-16T12:14:14.117
4695
2015-01-17T00:48:28.530
2015-01-16T16:14:48.833
5832
5832
question
ios swift
NSUserDefaultsでのView遷移の永続化
実装のポイントは

  1. `NSUserDefaults`による初期画面設定(文字列や数値)の入出力
  2. `AppDelegate`でのViewControllerの選択

の2点のように思われます。

`NSUserDefaults`については入出力は

  1. `standardUserDefaults`を取得する。
  2. [入出力メソッド](http://iphone-tora.sakura.ne.jp/nsuserdefaults.html)を呼ぶ。

という2ステップで完了します。[Swiftでの実装例](http://qiita.com/mochizukikotaro/items/9098cd3c091787f9aacb)もいくつかありました。

初期表示画面の変更は`AppDelegate`の`application:didFinishLaunchingWithOptions:`で実装します。`Storyboard`の構成次第ですが、

```

    self.window.rootViewController.performSegueWithIdentifier("segue",sender: nil)
    
```

で強制的に遷移させればいいのではないかと思います。
CC BY-SA 3.0
2015-01-16T12:46:46.297
4696
2015-01-16T12:46:46.297
5750
4695
answer
**NSUserDefaultsについて**

  * NSUserDefaultsはキーと値のペアを保存するだけであって、「どこまで画面遷移したか」のような特定の状態を記録する専用の機能があるわけではありません
  * NSUserDefaultsはアプリのどこからでも呼び出すことができ、値を共有できます。複数のビューコントローラを隔てると機能しない、などといったことはありません
  * NSUserDefaultsへの編集は、メモリ上のインスタンスに対してはすぐに反映されますが、ストレージへの保存は一定の間隔でまとめて行われます。編集内容をストレージへ即時保存させるにはsynchronize()を行います

**遷移の永続化について**

NSUserDefaultsに、特定の用途のための記録機能がないことは前述の通りです。遷移状況の永続化とその復元は、そのアプリの画面遷移や状態値など、全体の設計に合わせて、開発者が実装するものです。

**画面遷移の方法**

iOSアプリの標準的な画面遷移方法に絞って話を進めます。iOSでの画面遷移は、

  * UIViewControllerによるpresentViewController() / dismissViewControllerAnimated()
  * UINavigationControllerによるpushViewController() / popViewControllerAnimated()

の二通りの方法があります。

**画面遷移の経路**

さて、画面遷移の記録と復元では、以前の画面を復元するだけでなく、そこまでの経路上に存在するビューコントローラへ「戻る」ことができるようにもする必要があります。経路上のUIViewControllerは、

  * UIViewController同士のpresent / dismissでは、呼び出し元と呼び出された側がお互いの参照を presentedViewController / presentingViewControllerプロパティに持っています

  * UINavigationControllerでの遷移では表示した順にviewControllersプロパティに入っています

**画面遷移の永続化と復元の考え方**

「遷移の記録」とはすなわち、経路上のビューコントローラの記録です。「遷移の復元」とは、それらのビューコントローラをアプリの起動時に必要な分だけpresent...、またはpush...しておくということです。

**経路上のビューコントローラの記録方法**

NSUserDefaultsを使用する、という前提で説明します。NSUserDefaultsには色々な型の値を保存できますが、iCloudとの連携なども踏まえて、保存するデータサイズはなるべく小さい事が望まれます。

もし、あなたのアプリの画面遷移に分岐がなく、画面遷移の回数によって表示されるビューコントローラが特定できるのであれば、画面遷移の記録は単純にその回数を整数で保存すればいいでしょう。

もし、画面遷移に複雑な分岐があるなら、それぞれのビューコントローラにStoryboard
IDをつけ、表示した順にIDを配列へ格納して保存するなどの方法が考えられます。ストーリーボードを使用していないのであれば、UIViewControllerにnameなどのプロパティを加える必要も出てくるでしょう。

この記録を行うタイミングは、viewDidLoad()や画面の表示後が適しています。また、遷移の復元中にこの記録を行わないように注意しなくてはいけません。

**実例**

画面の遷移をビューコントローラのStoryboard IDで配列の形に記録するとしましょう。例えば、以下のようなクラスを作成します。

```

    import UIKit
    
    class RestoringManager: NSObject {
    
      var historyCache: [String]?
    
      func setBlank() {
        let userDefaults = NSUserDefaults.standardUserDefaults()
        userDefaults.setObject([String](), forKey: "history")
        userDefaults.synchronize()
      }
    
      func recordIdentifier(identifier: String) {
        let userDefaults = NSUserDefaults.standardUserDefaults()
        var history: [String] = userDefaults.arrayForKey("history") as [String]
        history.append(identifier)
        userDefaults.setObject(history, forKey: "history")
        userDefaults.synchronize()
      }
    
      func removeIdentifier(identifier: String) {
        let userDefaults = NSUserDefaults.standardUserDefaults()
        var history: [String] = userDefaults.arrayForKey("history") as [String]
        let index = find(history, identifier)!
        if (index != NSNotFound) {
          history.removeAtIndex(index)
          userDefaults.setObject(history, forKey: "history")
          userDefaults.synchronize()
        }
      }
    
      func restoreViewControllerPresentation(currentViewController: UIViewController) {
        if (historyCache?.count == 0) {
          return;
        }
        let nextIdentifier = historyCache?.first
        let nextViewController: UIViewController = currentViewController.storyboard?.instantiateViewControllerWithIdentifier(nextIdentifier!) as UIViewController;
        currentViewController.presentViewController(nextViewController, animated: false) { () -> Void in
          self.historyCache?.removeAtIndex(0)
          self.restoreViewControllerPresentation(nextViewController)
          return
        }
      }
    }
```

このクラスをいつでも使えるようAppDelegateに保持し、生成時に前回起動時の遷移記録をキャッシュします。

```

    import UIKit
    
    @UIApplicationMain
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
      var window: UIWindow?
      var restoringManager: RestoringManager?
    
      func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        self.restoringManager = RestoringManager()
        self.restoringManager?.historyCache = NSUserDefaults.standardUserDefaults().arrayForKey("history") as? [String]
    
        return true
      }
    }
```

最初の画面（おそらく、「Load」ボタンがあるでしょう）で今回の遷移記録を初期化します。「Load」ボタンに割り当てられた関数に、遷移の復元の実行を記述します。

```

    import UIKit
    
    class ViewController: UIViewController {
      override func viewDidLoad() {
        super.viewDidLoad()
    
        (UIApplication.sharedApplication().delegate as AppDelegate).restoringManager?.setBlank()
      }
    
      @IBAction func Load(sender: AnyObject) {
        (UIApplication.sharedApplication().delegate as AppDelegate).restoringManager?.restoreViewControllerPresentation(self)
      }
    }
```

二つ目以降の画面で行うべきことは、読み込み時に遷移記録を追加することと、閉じる際に遷移記録から自身を削除することです。

```

    import UIKit
    
    class SecondViewController: UIViewController {
    
      override func viewDidLoad() {
        super.viewDidLoad()
        (UIApplication.sharedApplication().delegate as AppDelegate).restoringManager?.recordIdentifier(self.restorationIdentifier!)
      }
    
      @IBAction func dismiss(sender: AnyObject) {
        (UIApplication.sharedApplication().delegate as AppDelegate).restoringManager?.removeIdentifier(self.restorationIdentifier!)
        self.dismissViewControllerAnimated(true, completion: nil)
      }
    }
```

**ポイント**

この方法でのポイントとなる知識は、

  * UIViewControllerに設定したStoryboard IDの取得（UIViewControllerのrestorationIdentifier）
  * IDを適切なタイミングで遷移記録として保存または削除すること
  * それを使ってUIViewContollerを生成する方法（UIStoryboardのinstantiateViewControllerWithIdentifier()）

などです。

以上が、かんたんな画面遷移の記録と復元に関するお話です。実際に画面遷移の記録と復元を実装するには、状態復元機能をつけることを前提に、アプリ全体の設計を上手に行う必要があります。適した方法というのはアプリごとに千差万別です。最適解はアプリの開発者本人の実力で生み出すしかありません。

※じぶんはSwiftを使ってないので型キャストなどにおかしな点があるかもしれません（一応動作はしました）。コードは参考程度に考えてください。
CC BY-SA 3.0
2015-01-17T00:48:28.530
4713
2015-01-17T00:48:28.530
4695
answer