1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NSOutlineViewの落とし穴(2)...開閉状態をアプリ起動時に再現する

Posted at

背景

StoryboardのNSOutlineViewには

  • Autosave Expanded Items
  • Autosave Name

というチェックがあり、これをチェックすれば開閉状態をアプリ起動時に再現してくれるのだろう...と普通は思いますよね?
ところが、そうはいかないというお話です。
まぁ、Appleのドキュメントを見ろ...ということではあるのですが。

スクリーンショット 2025-11-28 11.49.43.png

二つのメソッドを実装しなくてはいけない

まずはこれ。

func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any?

これは開いた状態を保存したいアイテムに関するものを返すメソッドです。
返すものはAny型なので何でも良く、普通に考えると、前回の続きから言えば単純にitemを返せばいい気がします。
念の為itemの型を確認するとして、

if let page = item as? Page {
    return item
}
return nil
 

という感じでしょうか。
実際は、これではダメなんですが、ひとまず次に進みます。

そして、次に実装するメソッドはこれ。

func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any?

アプリが起動した時、開いた状態を復元するために必要です。
これ、何を返せばいいんでしょう?
おそらく引数のobjectは、Autosaveで保存したitemです。
そうすると、前回起動していた時にNSOutlineViewが保持していたインスタンスと、今回起動した時のインスタンスは別物です。
ということは、開いた状態を保存する際に返したitemは適切ではないのです!

保存・復元メソッドでは何を返せばいい?

NSOutlineViewはitem、つまりインスタンスが同一でなくてはいけません。
これは前回の投稿で記載した通りです。
そうなると、保存・復元にはインスタンスが別物であっても、どのインスタンスを開くのかを特定できる必要があるのです。

今回NSOutlineViewで表示しているクラスはこのようなものです。

public class Scene: Codable {
    public var pageNumber = 0
    public var sceneNumber = 0
    public var sceneTitle = "SCENE 1 - 1"
}
public class Page: Codable {
    public var pageTitle = "PAGE 1"
    public var pageNumber = 0
    public var scenes = [Scene](repeating: Scene(), count: 10)
}

スクリーンショット 2025-11-16 18.24.03.png

Pageクラスには「pageNumber」というプロパティがあります。
今回の場合は0〜9の範囲で持っており、これはインスタンスが違っても同じなのです。
そこで、これを保存しておけば、pageNumberプロパティを利用して復元できます。

そこで、この数字を保存する方法を考えます。

開いた状態を保存するメソッド

func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
    if let page = item as? Page {
        return ["pageNumber": page.pageNumber]  //展開するページの番号を辞書で保存しておく
    }
    return nil
}

このように、Dictionaryで開いておくページ番号を返します。

開いた状態を復元するメソッド

func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
    if let dic = object as? [String: Int] { //辞書からexpandするページを特定する
        return memory[dic["pageNumber"]!] as Any
    }
    return nil
}

memoryという配列はPageクラスを10個持っており、その中に入っているPageクラスはNSOutlineViewが保持しているものと同一インスタンスです。
なので、Dictionaryに保存されていたページ番号からPageクラスの参照を返せばOKです。

最後に

以前からNSOutlineViewの展開状態を保存することができず、ずっと不具合だと思っていました。
Appleのドキュメントをちゃんと読めばわかることですが、もう少しわかりやすい方法があるといいですね。

これでNSOutlineViewを使ったアプリがより使いやすくなりそうです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?