Month: December 2015

Swift:ファイルのopen/closeとアプリの再開

ちょっとした応用問題に遭遇しました。

以前書いたように、アプリをクローズしたあと、アイコンをクリックすると、クローズする前の状態のwindowが開くようにしていました。これはこれで便利です。

func applicationShouldHandleReopen(sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
 if flag == false && sender.windows.count > 0 { sender.windows[0].makeKeyAndOrderFront(self) }
return true  

ところが、これだとファイルを閉じてもwidowに残ったまま、つまりcloseできません。どうしたものかと考えたのですが、結局、これを取り除き、File➡️Closeに次のIBActionメソッドを接続したら、うまく行きました。

@IBAction func performClose(sender: AnyObject?) {
xbOutWindow.close()   
}

やりたかったことは、Openでファイルを開け/windowを開く。逆に、Closeでファイルを閉じ、windowを閉じる。ただし、アプリはterminateさせない。func applicationShouldHandleReopenを残したままで、openフラグとか作っていろいろやってみたのですが、落ちまくるのでこの修正版はgive upし、上記の方法に変更しました。やれやれ。

Swift:NSSavePanelのソースコード

NSSavePanel / NSOpenPanel / NSAlertのサンプルコードです。

ソースコードはここにあります。

  • func名がCallからはじまるものは実際のアプリの実装でも使っています。
  • NSOpenPanelでは複数のURLが返ってくるように設定できますが、NSSavePanelはひとつしか返ってきません。しかも、配列で返ってきますので、ここは注意です。
  • インタフェース上で、cancelされたらfalseを返り値にしていたのですが、URL配列だけにしました。キャンセルのときはNSURL()を返します。しかし、NSURL()=nil!です。ここは注意。
  • full filepathはURLのあとに.pathをつけるだけです。folderpathとファイル名は、var wPathArray:[String] = pNSURL.pathComponents!で得られます。
  • なお、saveで上書きかどうかは自分でalertを出す必要がありませんでした。
  • このサンプルではファイルの読み込み・書き込みはありません。URL/pathのハンドリングだけです。

蛇足になりますが、ファイルの読み込み・書き込みはNSSavePanel / NSOpenPanelを使ったほうがいいようです。

Swift:PullDownMenuの処理

PullDownMenueとPopUpMenuの違いは失念しましたが、どこかのブログに記載がありました。

PullDownMenuの場合、最初のmenuItemは表示用になります。インデックス#1から値を割り当てる必要があります。

それはすぐに対応できたのですが、チェックをどう制御すればいいか、すぐにわかりませんでした。

実装の仕方には、PullDownMenuが固定のケースと配列から割り付ける可変のケースがあります。後者の場合、On / Offをどう制御すればいいか、さっぱりわかりませんでした。

結局、次のようにしました。

  • 一番重要なことは、操作対象をNSPopUpButtonCellにすることです。個々のmenuItemとかNSMenuでは、うまくいきません。
    @IBOutlet var zOutRemoveMetaCell:NSPopUpButtonCell!
  • どのmenuItemがクリックされたか、そのインデックスをsaveするようにします。初期値は、このPullDownMenuのどれを初期値にするか開発者にわかるはずで、その値をsetしておけばいいです。ポイントは、初期設定の実行時に、zOutMapOverlayCell!.itemAtIndex(cm.rcMapOverlayIndex)?.state = NSOnStateのようにして、クリック状態を作り出すことです。
  • もし、ユーザーがクリックした時点で判定したいのであれば、初期値をマイナスとし、次のようにします。何番目のmenuItemかは、itemAtIndex(インデックス)?.stateのような書き方になります。どうせなら、[ ]にしてほしかった・・・。ここにたどり着くのに時間がかかりました。

if cm.rcRemoveMetaIndex >= 0 {  zOutRemoveMetaCell!.itemAtIndex(cm.rcRemoveMetaIndex)?.state = NSOffState  }
cm.rcRemoveMetaIndex  = zOutRemoveMetaCell!.indexOfSelectedItem
zOutRemoveMetaCell!.itemAtIndex(cm.rcRemoveMetaIndex)?.state = NSOnState

  • チェックを入れるとき、NSOnState / NSOffStateを使った方がきっといいのでしょうね。

Swift:Xcode 7.2 Pasteできない。

コマンド+CはOKでもコマンド+Vができません。

どこかで何か悪いことをしたのでしょうか?

仕方がないので、preferences➡️Key bindingsへ行き、pasteで検索してコマンド+Aに変えたらOKになりました。少し使いにくいですが、これでやりくりします。

いつも問題だらけですね。

1994年にイスラエルの企業と共同開発をしたとき、最初のミーティングで先方のV.P.が「自分の仕事は問題をマネージすることだ。問題があるから自分がいる。」と言ったとき、強い衝撃を受けました。日本では、manageは管理すること、つまり制御下に入れることと勘違いしがちですが、すでにルーチン化されていてうまく動いているものを注意深く見るのがマネージャのしごとではありません。

組織が抱える問題を解決するために、問題を抽出し、解決可能な課題に振り分け、原因と対策を明らかにし、アクションを管理することが本当のしごとだと思うのですが、どうでしょうか。

その昔、大学を出てすぐの頃、経営の教育プログラムで「管理とは、他人を通じて自分の目標を達成すること」とならった記憶があります。確かに、自分がすべてのことをできない以上、何かを成し遂げるには、ほかの人々とうまく働く必要があります。

ややっこしいですが、避けて通れない道ですね。

Swift:単純な違いでも結果は大違い。

「applicationDidFinishLaunchingに制御が渡らない」というアプリの制御の話です。

すでに公開しているNSOpenPanel / NSToolbarのサンプルプログラムとほぼ等価のコードをアプリで書いています。しかし、サンプルプログラムと違う動きをしていました。どこが違うかというと、アプリでは、awakeFromNibのあとwindowが開いてしまい、applicationDidFinishLaunchingに制御が渡らないのです。

Screenshot 2015-12-21 18.32.17

アプリ自体は正常に動作します。問題は、アプリwindowを閉じたあと、Dockのアイコンをクリックしてもアプリが再起動しない。この問題は、サンプルアプリを作った時点で認識していたのですが、調査が終わっていませんでした。

先程、きっちり調べたら、.xibの接続に問題があることがわかりました。

  • 上図:File’s Owner – AppDelegateを接続する。
  • 下図:XbWindowでXbWindow – AppDelegateを接続する。
  • 下図:XbWindowでdelegate – ToolBarを接続する。

Screenshot 2015-12-21 18.41.44

Screenshot 2015-12-21 18.42.14

Swift:NSToolbarソースコード2

実際の使用に合わせて修正しました。表題はNSToolbarになっていますが、実態はNSOpenPanelに関わる修正です。

ソースコードはここにあります。主な修正点は次のとおりです。

  • いずれ、ファイル名とフォルダまでのpathに分けて表示する必要があるので、その処理をするメソッドをクラスの外側に切り出しました。
  • NSOpenPanelの処理もクラスの外側に切り出しました。NSWindowをパラメタ渡しにしてみたのですが、うまくいかないので、キャンセルはメイン側で判別・処理することにしました。
  • 処理対象のファイル数をひとつにするか、複数にするか迷いがあるのですが、単純化してひとつにする方針がよいかなあ、とぼんやり考えています。そのため、サンプルプログラムもひとつにしました。

次のような勘違いがありました。これにはすっかり混乱させられました。まだまだ修行が足りません。

  • NSOpenPanelでキャンセルのときxbWindow.close()を使い、次にアプリが再開されたときにxbWindow.display()を使うのかと思っていたのですが、全くそうではありませんでした。
  • xbWindow.close()で開いているwindowを閉じるというのは適切です。このあと、applicationShouldTerminateAfterLastWindowClosedでfalseの処理になります。
  • そのあと再開するとき、Dockのアプリアイコンを叩くとapplicationShouldHandleReopenがキックされ、そのあと自動的にwindowが開きます。
  • メニュのOpen fileを叩いたときも、このIBActionが動くようにしておけばいいのかなあと思っています。
  • 重要なことを忘れていました。awakeFromNibの中でアプリの初期処理としてのNSOpenPanelの処理ExecSetPicturePath()を実行するとなぜかうまく行きません。applicationDidFinishLaunchingの中に移したらOKになりました。

Swift:NSToolbarソースコード

「多分」、一通り理解できたと思います。

ソースコードSampleXibToolbar v2.0.0には次の内容が含まれます。

  • NSToolbarでタイトルバーなどをカスタマイズ。
  • NSOpenPanelで指定したファイルタイプを取り込む方法。
  • アプリケーションのウインドウのクローズと再開。
  • ターミネート。

注意:storyboardでは、NSToolbarがうまく動かなかったので、.xibを使っています。ソースコードの中に削除するべきものがあったら教えてください。

Swift:なるほど。再開の仕方。

ウインドウを閉じたあと、再表示してアプリを再開するにはどうしたらいいのでしょう?

ここで少しばかり引っかかりました。詳細は、Single WindowなOSX Appで全てのWindowが閉じたときの挙動でリジェクトに書いてあります。このブログは参考になりました。深く感謝です。もう少しでgive upし、後回しにするところでした。

func applicationShouldHandleReopen(sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool  {
    if flag == false && sender.windows.count > 0  {
sender.windows[0].makeKeyAndOrderFront(self)
}

    return true
}