マルチウィンドウ再戦
Enchan1207 opened this issue · comments
ウィジェットModelの設計をどうするか…
- 識別子
- 「どこに表示するか」の値(座標, 表示/非表示フラグ等)
- 「何を表示するか」の値(ウィジェットの種類、コンテンツを表す情報等)
があればModelとしては成立する?
ウィジェット構造体として扱うならこれを配列として持つことも考えられるけど、ウィジェット間の関係がそんな密接じゃない(ウィンドウ管理はウィジェットModelの仕事じゃない)ことを考えると
ひとつのWindowControllerにひとつのWidgetModelが対応するかたちがベターなのかなあ?
ちょっと綺麗かなと思ったのは、
ウィジェット構成 Model
がその座標と種別をもち、WindowController
がそのインスタンスを保持することで、WindowController
は Model
の変更を検知した時に自身の持つ ViewController
へ通知するまたは再構成するだけでよくなるという流れ
ウィジェットの種類とオプションを別で持つべきかなとか考えたんですが、付属型enum(enumに値持たせてパターンマッチで値取り出すやつ)使えばきれいにできそうな気がしてきた
コマンドウィジェットなら実行内容と更新インターバル メディアウィジェットならURLとMIME(Viewの構成に必要)を持ってれば今んとこ問題ないじゃん?
… と思ったけどMIMEはUTIから取得できないこともないのか
テキストレイヤの文字色などもございますが、必要ならWidgetInfoクラスか何かにしてしまえばいいか
でも現状WCからVCに通知する手段がないんだよな
ウィジェットVCを共通のprotocolに準拠させて、contentViewController
をダウンキャストするかあ??
WCに渡されたModelのdelegateをVCにするとかいうアクロバティック・代入も考えましたが、それはどうなんだ…?
WidgetModelは「ウィジェットが保持するコンテンツ(≒VC)」も扱うのと、それが変化した時にVCを再構成できるのはWCだけなので、単純にdelegationしちゃうのはまずい やはりWidgetModelのdelegateはWCであるべき
WidgetModelDelegate
widgetKindDidChange
widgetInfoDidChange
みたいな感じかねえ…
値付きenumだと付属する値の変化を読めないというデメリットがある…
なんでこんなWidgetModelのハンドル困るんだ?
種類と位置が変わったときはWCが、表示内容が変わったときはVCがそれを処理してるのが悪いのか
WidgetModelが処理するべき「表示内容」とは何だ? MediaModelとかShellCommandModelと何が違う?
シェルコマンドは「実行するコマンド」「更新頻度」を渡すと定期的にその結果を返す(または更新頻度はControllerが処理する)
メディアはまあただURLを持つだけ(本当はAsset持ってもいい気がしますが…)
ウィジェットごとに持つ情報が異なるとなると、そもそもそのModelクラスをサブクラスしてしまった方が良いのでは?とも思う
それでもデリゲートは変わらないんだよな…
やっぱりウィジェット情報 widgetInfo
を [String: Codable]
で持って widgetInfoDidChange
でまとめて受けるのが一番楽じゃないかなあ
NotificationCenterを使うな!!!!!! (大声)
マルチキャストデリゲートか何かにしないとマジでやってられなくなるぞこれ
とりあえず参考にしつつ実装してみた
よくよく考えればWCにモデルを渡した段階でContentViewControllerも生成できるんだよな
WidgetModelからVC, WCを生成するよう変更
生成処理をもう少しなんとかしたいんだよな、具体的にいうとここ:
// ウィジェットモデルの配列からウィジェットWCを生成
widgetWCs = widgetModels.compactMap({ widgetModel in
// TODO: WidgetModelから渡ったinfoを誰かに渡す (WC? VC?)
// TODO: 一連の処理をファクトリ化する
// ViewControllerを生成
let widgetVC: NSViewController
switch widgetModel.kind {
case .Media:
widgetVC = MediaWidgetViewController(mediaModel: .init())
case .ShellCommand:
widgetVC = ShellCommandViewController(shellCommandModel: .init())
}
// WindowControllerを生成
let widgetWC = WidgetWindowController(window: .init(contentViewController: widgetVC), model: widgetModel)
return widgetWC
})
WCとWidgetWindowの生成処理は共通だから、本来はWidgetModelからwidgetVCを生成する部分をファクトリかしてWCのイニシャライザに突っ込むべきよね?
待てよ? WidgetModelとは別にWCとVCを結ぶprotocolを用意すれば…接続は可能なのでは
いやでもやりたいのは参照であって接続ではない?
weakでWidgetModelを持つVCをWidgetViewControllerとして定義して継承させるようにする?
そうすれば初回だけはWidgetModelの情報から初期化できる?
これはかなり綺麗に纏まったのでは
func applicationDidFinishLaunching(_ aNotification: Notification) {
// メニューバーボタンを構成
configureMenuBarButton()
// ウィジェットモデルの配列からウィジェットWCを生成
widgetWCs = widgetModels.compactMap({.init(model: $0)})
// アプリをアクティベート
activateApp()
// ウィンドウを表示
widgetWCs.forEach({$0.showWindow(nil)})
}
今気づいたんだけど
kind
だけが書き換わると、一瞬だけ「前のウィジェットのinfoを保持した状態で、次のウィジェットのkindに切り替わってる」瞬間ができることになるよな?
うん、これはなんかまずい気がする 流石にkindはimmutableにした方がいい
WidgetModelの中でウィジェット構成情報をencloseしてそれ自体を再代入可能にする手も考えられますが、流石に頭が悪い
kindをimmutableに修正
WidgetModelをベースとしたウィジェットVCの生成をfailableからthrowableに変更 これにより不正な情報が与えられたときにファクトリ内でフォールバック用VCに切り替えることができる
でもこのフォールバックは「そもそも構成情報がおかしい」時に投げられるものなので、初期化後にinfoが書きかわった(=widget(_ model: WidgetModel, infoDidChange to: [String : String])
が呼ばれた)際の処理については感知しないんですよね
いい感じになってきた
フォールバックVC作るか
完成!ついでに名前おかしかった部分修正
VCもWCもWidgetModelをobserveするのがベターなのかなあ でもWCがVCのインスタンス持ってるんだしお前が通知しろよって言われちゃうとそれまでなんだよな…
ちょっと困ってるので整理
まず上(抽象的な方)から見ると、「WidgetModel
のinfo
が変わった時にVCに何かしらの処理をさせたい」
- WidgetModelDelegateのデリゲートメソッド:
- 表示状態 (visibility) が変化したとき
- 枠 (frame) が変化したとき
- 構成情報 (info) が変化したとき
このうち上二つはWC(WindowController)で処理したいけど、構成情報の変化はVC(ViewController)じゃないと処理できない
VCはWCのもつWidgetModelをweakで持ってるから、そこからデリゲートを生やすことは可能
ただそうなるとWidgetModelをVCとWCの両方がデリゲートすることになるし、WCはinfoの変化、VCはvisibilityとframeの変化の際に呼ばれるデリゲートメソッドを無視することになり効率が悪い
解決策:
-
WCもVCもWidgetModelのdelegateになる
- 効率が悪い
- お互いに無意味なメソッドを持つことになる
-
デリゲートを分割する:
- 表示に関わるデリゲート
WidgetModelWindowDelegate
と 内容に関わるデリゲートWidgetModelContentDelegate
に分割 - 前者はWCが、後者はVCが担う
- Modelのイベントを担うのがWC/VCだけじゃないかもしれない(設定画面が持つかも、ここはふわふわ)ことを考えると、マルチキャストデリゲートが二つ必要になるので面倒
- 表示に関わるデリゲート
-
WCがデリゲートするのは変えずに、構成情報の変化だけVCに何らかの方法で通知する
- どうやって? VCにメソッドを生やす
- VCとWCの結合度が上がりすぎる、まあ別にWidgetWindowControllerがWidgetViewController以外を保持することもないだろうしあんまり気にならないっちゃ気にならないけど…
- VCのサブクラスがそれをオーバライドすることを強制できないのでは
-
WCじゃなくてVCがWidgetModelDelegateになる
- !?
- VCはWindowおよびWCへの参照を持てるので可能といえば可能
- VCがウィンドウを閉じるとかいうわけわかんないことになる
- VCがWCの顔色を伺うのは気持ちが悪い
-
WidgetViewControllerをprotocolにする
- WidgetModelへの参照を持つのをやめる (VCがあそこの情報を参照するのはおかしい、infoが変わった時とinit時だけでいい)
- infoが変わったことを通知するだけのアレになる
- WCはVCをデリゲートとして扱える、VCはWCのデリゲート先として表示内容の更新を行える
- WCがデリゲート元とデリゲート先を担うことになるのがちょっとキモい
一番下のを採用しました これならまあWCは自身のVCがWCじゃなくてもいいしVCは自身がWidgetVCでなくてもいいのである程度結合弱いと言って過言じゃないんじゃないでしょうか 知りませんけど
とりあえず更新まではできたのでマルチウィンドウ達成と言っていいんじゃないかと