macOS   SwiftUIプログラミング   アプリケーションの終了

ホーム

AppDelegate を使わずに、最後のウィンドウを閉じたら、アプリケーションも終了する方法を考えてみました。@ObservedObject というバインディング技術を使っています。なぜ AppDelegate を使うのをやめようとしているのかというと、Apple がアプリケーションのライフサイクルを AppDelegate から App へ移行しようとしているからです。

なお、このコーナーでは、任意のテキストエディタでコードを記述し、ターミナルを使ってビルドする方法で作業を進めています。Xcode をお使いの場合は、Xcodeで作業する場合 をご一読ください。

また、ターミナルで作業する場合も、Swift コンパイラや SwiftUI フレームワークなどをMac にインストールするために Xcode をインストールして、一度起動させなければなりません。インストールが終われば Xcodeは終了しても大丈夫です。
なお、作成したアプリケーションを App Storeに提出するためのファイルにするには、 Xcode を使わなければならなかったかもしれません。どこかで Xcode を使わずに作る方法を見たような気もするのですが...

私の開発環境は次のとおりです。

  • MacBook Air 2018年モデル、メモリ8G
  • macOS Monterey 12.4
  • Xcode 13.4.1
  • Swift 5.6.1

更新履歴
2022/07/12 コード説明をつけました。

AppDelegate を使わずに、最後のウィンドウを閉じたら アプリケーションが終了するようにする

任意のテキストエディタで次の二つのファイルを作成します。

App.swift

ファイル名は自由ですが、 大文字で始めなければなりません。Swift ファイルの拡張子は .swift です。 Xcode で作業をする場合は、(プロダクト名App(プロダクト名App.swift)に次のコードを記述します。


/************************************
    App.swift
    copyright    :   vivacocoa.jp
    last modified:   Jul. 11, 2022
************************************/

import SwiftUI

class Count: ObservableObject {
    @Published var count = 0
}

@main
struct FooApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
	

コード説明

  1. class Count: ObservableObject {
    View 間でデータを共有するためのクラスを定義しています。ObsevableObject はプロトコルになります。Google で翻訳すると「観察可能なオブジェクト」となりました。SwiftUI の class には、NSObject などのスーパークラスの記述は必要ないみたいです。逆に記述しても問題ありません。また、このクラスは必ずここに記述しなければならないということはなくて、例えば、View.swift の import から struct ContentView の間に記述しても問題ありません。
  2. @Published var count = 0
    Published 属性を付けて count プロパティを宣言しています。published をグーグルで翻訳すると「公開済み」になりました。いろいろなドキュメントには、データを共有するプロパティには @Published 属性を付けなければならないとなっていますが、実際には付けなくても問題なく動作します。SwiftUI はどんどん進化しているのでしょう。


View.swift

ファイル名は自由ですが、 大文字で始めなければなりません。Swift ファイルの拡張子は .swift です。 Xcode で作業を進める場合は、ContentView(ContentView.swift)に次のコードを記述します。


/************************************
    View.swift
    copyright    :   vivacocoa.jp
    last modified:   Jul. 11, 2022
************************************/

import SwiftUI

struct ContentView: View {
    @ObservedObject var count = Count()
    var body: some View {
        VStack {
            Text("Hello, world!")
            .font(.largeTitle)
            .fontWeight(.thin)
        }
        .frame(minWidth: 300, maxWidth:.infinity, minHeight: 200, maxHeight: .infinity)
        .onAppear {
            count.count += 1
        }
        .onDisappear{
            count.count -= 1
            if count.count < 1 {
                NSApplication.shared.terminate(self)
            }
        }
    }
}
	

コード説明

  1. @ObservedObject var count = Count()
    @ObservedObject 属性を付けて count プロパティを宣言して、Count クラスのインスタンスを代入しています。Observed object をグーグルで翻訳すると「観察対象」になりました。ここで大事なことは、View 構造体(ContentView)の中だけでデータをバインディングさせる場合は @State 属性を使い、違う View 構造体の間でデータをバインディング(共有といっても良いかも知れません)する場合は、@ObservedObject を使うということです。
    「あれ? 1つの ContentView じゃないの?」と思われた方もいると思います。しかし、New メニューアイテムを選択すると、この ContentView が複数作られるのです。
  2. .onAppear {
    「現れる時・表示する時」という意味になると思います。通常 GUI ではウィンドウが作成されるのと実際に表示されるのは別のイベントです。しかし、ここでは、表示 = 作成ぐらいで考えて大丈夫みたいです。
  3. count.count += 1
    Count クラスの count プロパティの値を1つ増やしています。
  4. .onDisappear{
    「消える時・非表示になる時」とう意味になると思います。通常 GUI ではウィンドウが非表示になるのとウィンドウが実際になくなるのは別のイベントです。しかし、ここでは、非表示 = 破棄ぐらいで考えて大丈夫みたいです。
  5. count.count -= 1
    まず Count クラスの count プロパティの値を1つ減らします。
  6. if count.count < 1 {
    前の行で、まずカウントを減らしてから、if 文で、count プロパティの値が 0 以下かどうか調べています。
  7. NSApplication.shared.terminate(self)
  8. アプリケーションを終了するコードです。しかしこのコードは、Objective-C の頃から使われてきたコードです。もしかするともっと新しいコードがあるのかも知れません。
  9. 最後に、Swift のクラスのインスタンスは参照型として渡されます。参照型ということは、通常は、参照先(参照元?)のメモリーは自動的に解放されません。しかし、SwiftUI では、明示的にメモリーを解放するコードはないみたいです。Swift のように nil を代入するという手段も使えないみたいです。ARC(Auto Reference Counting)でメモリ管理されていることを期待します。


ビルド


swiftc App.swift View.swift -o foo
mv foo Foo.app/Contents/MacOS
	

Xcode で作業を進めている場合は、Product メニューの Run をクリックするか、 Xcode の左上の右三角 ▶︎ をクリックします。

実行

作成した Foo アプリ(アプリケーションバンドル)をダブルクリックします。
Xcode で作業を進めている場合は何もする必要はありません。 しばらく待つとアプリケーションが起動します。

ウィンドウを閉じるとアプリケーションが終了します。 New メニューで、複数のウィンドウを作った場合は、最後のウィンドウを閉じた時だけアプリケーションが終了します。


課題

そもそも、New(新規)メニューを削除したいのですが、AppDelegate を使わずに、メニューを削除する方法はまだ見つけられていません。




1151 visits
Posted: Jul. 11, 2022
Update: Jul. 12, 2022

ホーム   目次