クリアメモリ

プログラミングやモーショングラフィックス、便利なアプリケーションなど雑多に記録するブログ

【Swift】常に最前面に表示されるOSXアプリの作り方

f:id:clrmemory:20170422183934p:plain 

今回はCocoa programmingで「常に最前面に表示されるアプリ」の作り方です。

 

例えば、時計アプリを起動している時にChromeなど別のアプリを起動すると、時計アプリが後ろに隠れてしまいますよね。

今回は別のアプリを操作していても、任意のアプリを最前面に表示する方法を紹介します。

 

 

はじめに

 

今回紹介する方法を行うことで、以下のようなアプリが完成します。

 

Windowアプリ操作時

 

Chrome操作時

 

このように、アプリケーションを操作している時はWindowが明るくなり、別のアプリケーションを操作している時はWindowが暗くなるけれど、背面には隠れないようになりました。

つまり、アプリを起動していれば常にどこかしらに表示されるというわけですね。

 

また、こちらの動画から実際の動作を確認しておくと、開発するアプリのイメージが付きやすくなるかもしれません。

時計アプリやタイマーアプリなど、使用していない時も常に表示したいアプリを作る時は試してみてください。

では、まずストーリーボードを作成しましょう。

 

ストーリーボードを作る

 

今回のアプリはオブジェクトを表示させる必要がないので、ViewControllerの中身は特に何も設置しませんでした。実際にアプリ開発を行う時には、ここにオブジェクトが設置されているはずです。

 

 

今回ストーリーボードの操作が必要なのは、Menuに「最前面に表示」ボタンを設置です。以下のようなメニューをアプリのメニューバーから設定できるようにしましょう。

 

 

Xcode右下のオブジェクトリストから「Menu Item」を探してください。

 

 

続いて「Application Scene」の中に「Window」という項目があるので、その一番下に「Menu Item」を追加してみましょう。

 

 

このようになるので、追加された「Item」の名前を変更してください。今回は「Stay in Front」という名前に変更しました。

 

 

今回のストーリーボードの変更点はこれだけです。

 

メニューをチェックする

 

先ほど作成した「Stay in Front」メニューをクリックすると、チェックがついたり消えたりするようにしてみましょう。今回のコードは以下になります。

 

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var stayInFrontMenu: NSMenuItem!


    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

    @IBAction func menuClicked(_ sender: Any) {
        toggleMenuChecked()
    }

    func toggleMenuChecked(){
        if stayInFrontMenu.state == NSOnState {
            stayInFrontMenu.state = NSOffState
        }else if stayInFrontMenu.state == NSOffState{
            stayInFrontMenu.state = NSOnState
        }
    }
}

 

編集するコードは「AppDelegate」です。

これで、先ほど作成した「Stay in Front」メニューをクリックした時にチェックが付き、すでにチェックがついている状態ならチェックが外れるようになりました。

 

 

stayInFrontMenuは「アウトレット接続」

menuClickedは「アクション接続」

それぞれ、先ほど作成した「Stay in Front」メニューと接続されています。

 

チェックのON、OFFを変更するコードは「.state」です。こちらは「NSOnState」で設定するか、「1」でも変更できます。(NSOnState == 1 , NSOffState == 0)

 

ちなみにこのコードは、後ほど再度編集します。

 

ウィンドウを最前面に表示

 

では、実際にウィンドウを最前面に表示してみましょう。

今回使用したコードは以下になります。

 

import Cocoa

class WindowController: NSWindowController {

    override func windowDidLoad() {
        super.windowDidLoad()

        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.

    stayInFront()
    }

    func stayInFront(){
    let normalWindow = Int(CGWindowLevelForKey(.normalWindow))
    let floatingWindow = Int(CGWindowLevelForKey(.floatingWindow))

    if window?.level == normalWindow{
        window?.level = floatingWindow
    }else if window?.level == floatingWindow{
        window?.level = normalWindow
    }
    }
}

 

新しく「WindowController」というクラスを作成し、ストーリーボードで設定してあります。この工程は省くので、各自設定しておいてください。

 

まずは、わかりやすいように「起動時に最前面で表示」できるようにしました。

先ほど作成した「チェックのON,OFF」を適用するためにはこのコードでは不十分なので注意してください。

 

 

では、アプリケーションを最前面に表示するためのコードの解説をしておきます。

 

window?.levelにCGWindowLevelForKey( .XXXXXX )を指定することで、ウィンドウを最前面に表示するかを変更することができます。

「.normalWindow」で通常のウィンドウ、「.floatingWindow」で最前面のウィンドウになるので、これらを「通常ウィンドウだったら最前面に」「最前面ウィンドウだったら通常に」切り替えられるようにしました。

 

これで、ウィンドウを最前面表示できたのですが、これだけだと先ほど作成したメニューから変更できません。では、作成したコードをつなげてみましょう。

 

チェックを判定する

 

先ほど編集した「AppDelegate」を再度編集します。

ここでは「NotificationCenter」を使用して、stayInFrontMenuをクリックした時に通知するようにしてみました。

 

NotificationCenter.default.post(name: NSNotification.Name("StayInFront"), object: nil)

 

このコードを「menuClicked」の中に追加してください。

完成したAppDelegateがこちら

 

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var stayInFrontMenu: NSMenuItem!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
    }

    func applicationWillTerminate(_ aNotification: Notification) {
    }

    @IBAction func menuClicked(_ sender: Any) {
        toggleMenuChecked()

        NotificationCenter.default.post(name: NSNotification.Name("StayInFront"), object: nil)
    }

    func toggleMenuChecked(){
        if stayInFrontMenu.state == NSOnState {
            stayInFrontMenu.state = NSOffState
        }else if stayInFrontMenu.state == NSOffState{
            stayInFrontMenu.state = NSOnState
        }
    }
}

 

 

続いて、WindowControllerに通知を受け取る処理を記述していきます。

WindowControllerのviewDidLoad( )内を以下のようにしましょう。

 

NotificationCenter.default.addObserver(self, selector: #selector(nCenter), name: NSNotification.Name("StayInFront"), object: nil)

 

先ほど紹介したコードでは、viewDidLoad( )で「stayInFront」を呼び出していましたが、この処理は必要なくなるので、削除してください

 

最後に通知を受け取る「nCenter」を追加します。

WindowControllerに、以下の記述を追加してください。

 

func nCenter(notification: NSNotification){
    stayInFront()
}

 

完成したWindowControllerがこちら

 

import Cocoa

class WindowController: NSWindowController {

    override func windowDidLoad() {
        super.windowDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(nCenter), name: NSNotification.Name("StayInFront"), object: nil)
    }
    func stayInFront(){
    let normalWindow = Int(CGWindowLevelForKey(.normalWindow))
    let floatingWindow = Int(CGWindowLevelForKey(.floatingWindow))

    if window?.level == normalWindow{
        window?.level = floatingWindow
    }else if window?.level == floatingWindow{
        window?.level = normalWindow
    }
     }
     func nCenter(notification: NSNotification){
    stayInFront()
     }
}

 

これでアプリを実行すると、上部の「Window」項目にある「Stay in Front」をチェックすることで、ウィンドウを最前面に表示できるようになったかと思います。

 

まとめ

 

今回紹介した方法を使うことで、最前面に表示するアプリケーションを作成できました。時計アプリやタイマーアプリなどの常に表示しておきたいアプリを作成するなら、この方法を使ってみてください。

アプリケーションのStay in Frontメニューにコマンドを追加すると、より使いやすくなるかもしれませんね。ぜひ試してみてください。

 

ではまた。