macOS   SwiftUIプログラミング   ボタン

ホーム

【お知らせ】
SwiftUIで作った macOS Todo アプリ ToDone を100ダウンロードまで無料にしました。マニュアルページは、ToDone サポートページ です。

【本文】
この章では、ボタンを使ってアプリケーションに処理(機能)を追加します。

@Stateをつけて宣言したプロパティは、その値を変更すると、その値を利用したビュー(View、GUI部品)の値も変わるという便利なものです。また、SwiftUI において、View をどのように配置していくかについては、全章にわたって例を示していきたいと思います。

このコーナーでは、任意のテキストエディタでコードを記述し、 ターミナルを使ってビルドする方法で作業を進めています。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/11 詳しいコード説明をつけました。

Button

この章では、ボタンとレイアウトについて説明します。

なお、以前は Sub.swift というファイル名でしたが、View.swift というファイル名で統一することにしました。View.swift を次のように書き換えてください。Xcode で作業する場合は、ContentView(ContentView.swift)を次のように書き換えてください。

View.swift


import SwiftUI

var count = 0

struct ContentView: View {
    // num プロパティ(メンバ変数)を @Stateをつけて宣言します。
    @State var num = "0"
    var body: some View {
        VStack {
            Spacer()
            // Text に設定する文字を @State を付けた num にします。
            Text(num)
                .font(.largeTitle)
                .fontWeight(.ultraLight)
            Spacer()
            HStack {
                Spacer()
                Button(action: {
                    count -= 1
                    // num の値を変更すると Text に表示される文字列も自動で変わります。
                    num = String(count)
                    
                            }) {
                                Text("-")
                                    .frame(maxWidth: 70, maxHeight: 24)
                            }
                Button(action: {
                    count += 1
                    num = String(count)
                            }) {
                                Text("+")
                                    .frame(maxWidth: 70, maxHeight: 24)
                            }
            }
        }
        .padding(10)
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
    }
}
	

コード説明

  1. var count = 0
    グローバル変数として宣言してますが、@State var count = 0 として、ContentView 構造体の中でも宣言できます。Swift の構造体のプロパティは、値の変更ができませんが、@State を付けることによって、値の変更ができるようになります。
  2. @State var num = "0"
    @State にはそのプロパティの値を変更すると、そのプロパティを参照する View で表示される値も変更されるという機能があります。この機能を Apple ではバインディング(binding、束ねること。縛ること。)と呼んでいます。
  3. Spacer()
    無限大のスペースをあけます。Spacer(minLength: 10) とすると、スペースの最小値を 10 ピクセルに設定できます。さらに続けて .fixedSize() を付けると、スペースの大きさが minLength の値に固定されます。ここでは Text の上下を Spacer() で囲むことによって、Text を上下の中央に配置しています。
  4. HStack {
    HStack は子 View を左から横に10個まで配置できるコンテナーです。VStack のなかに HStack を配置していることに注意してください。
  5. Button(action: {
    ボタンを配置しています。ボタンのアクション(action、動作・処理)とボタンに表示される文字列やイメージなどの設定に、ここでは Button(action: {}){Text()} という形をとっていますが、いろいろな記述方法があるみたいです。
  6. count -= 1
    count グローバル変数をマイナス1しています。なお count は ContentView 構造体の中で @State var count と記述して、変更可能なプロパティとして宣言することもできます。
  7. num = String(count)
    count の値を String 型にキャストした値を num プロパティに代入しています。num プロパティは 最初は 0 と表示されていた Text とバインディングされているので、この時点で Text にはプラス1された数字が表示されます。
  8. Text("-")
    ボタンに表示される文字列を「-」に設定しています。
  9. .frame(maxWidth: 70, maxHeight: 24)
    .frame モディファイアを使って Text のサイズを設定しています。通常ボタンのサイズは内部に表示される文字列やイメージがちょうど収まるサイズに自動的に調整されます。ボタンの大きさを変えたい場合は、内部に表示される文字列やイメージの .frame モディファイアで調整します。
  10. count += 1
    「+」ボタンでは count の値を1つ増やしています。
  11. .padding(10)
    パディング(padding、詰め物)は、View の境界線のすぐ内側に指定されたピクセル数のスペースを取ります。境界線の外側にスペースを取る Spacer とは違います。パディングを次の行の .frame の後から設定すると、ウィンドウのサイズは、幅340 縦240 になってしまいます。


以前は、Main.swift というファイル名でしたが、App.swift というファイル名で統一することにしました。Xcode で作業する場合は、プロダクト名App(プロダクト名App.swift)に次のコードを記述してください。

App.swift


import SwiftUI

@main
struct FooApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
   }
}
	

コード説明

  1. class AppDelegate: NSObject, NSApplicationDelegate
    以前のサンプルでは、NSWindowDelegate というデリゲートも付けていましたが、更新後は NSWindowDelegate は付けないことにしました。NSWindowDelegate はおもに、ウィンドウのサイズ変更や移動に関するイベントを受け取るものです。アプリケーションが起動した時のウィンドウサイズもこのデリゲートで設定できたと思います(Apple のドキュメントで見たような気がしてたのですが、もう1度見つけることができません。NSWindowDelegate では起動時のウィンドウサイズを設定できないのかもしれません)。しかし SwiftUI において、デリゲートは廃止の方向なので、View でウィンドウサイズを設定することで統一することにします。なお、更新前は、NSWindowDelegate を採用しながらも、実際のメソッドは記述していませんでした。デリゲートではどのメソッドを実装するかはプログラマの任意です。メソッドを実装していなくても問題はありません。一方、プロトコルは定義されているすべてのプロパティとメソッドを実装しなければなりません。またデリゲートで定義されているのはメソッドだけです。プロパティは宣言されていません。


アプリケーションの名前を変えるために、Info.plist の CFBundleName を書き換えます。ここでは「Counting」という名前にしました。 Xcode で作業している場合は、ここは無視してください。 機会があれば、 また説明します。

Info.plist


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleExecutable</key>
	<string>foo</string>
	<key>CFBundleIconFile</key>
	<string>icon-macbook.icns</string>
	<key>CFBundleName</key>
	<string>Counting</string>
</dict>
</plist>
    


ビルド


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

実行

+ ボタンをクリックすると数がひとつ増えて、 - ボタンをクリックすると数がひとつ減ります。

ウィンドウを拡大しても、ボタンの位置は常に右下に表示されます。

別案

次のようなものも良いかもしれません。

View.swift


import SwiftUI

var count = 0

struct ContentView: View {
    @State var num = "0"
    var body: some View {
        VStack {
            Text(num)
                .font(.largeTitle)
                .fontWeight(.ultraLight)
                .foregroundColor(.white)
                .frame(maxWidth: .infinity,  maxHeight: .infinity)
                .background(.green)
            HStack {
                Spacer()
                Button(action: {
                    count -= 1
                    num = String(count)
                            }) {
                                Text("-")
                                    .frame(maxWidth: 70, maxHeight: 24)
                            }
                Button(action: {
                    count += 1
                    num = String(count)
                            }) {
                                Text("+")
                                    .frame(maxWidth: 70, maxHeight: 24)
                            }
            }
        }
        .padding(10)
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 200, maxHeight: .infinity)
    }
}
    

コード説明

  1. Text(num) 中略 .frame(maxWidth: .infinity, maxHeight: .infinity)
    Text に対して、.frame(大きさ)を縦横無限大に設定しています。こうすることによって拡大できる範囲で最大の大きさになります。Text のデフォルトのサイズは表示する文字列の大きさになります。またこのように設定することによって、Text を囲む Spacer は必要なくなります。
  2. .background(.green)
    Text に対して背景色を設定しています。Spacer で上下にスペースを取っていた場合は、文字列の大きさだけに背景色が適用されます。



26937 visits
Posted: Jun. 24, 2022
Update: Jul. 11, 2022

ホーム   目次