macOS   SwiftUIプログラミング   宣言型フレームワーク

ホーム

この「mac OS SwiftUI プログラミング」の最初の章は、「初めの一歩」か、「Xcodeで作業する場合」になります。

しかしながら Apple の公式ドキュメント「SwiftUIの概要 → Documentation → SwiftUIを学ぶ」では、『SwiftUI は、アプリのユーザー インターフェイスの作成に役立つ宣言型フレームワークです。SwiftUI アプリの構造を形成する基本的な構成要素はApp、、、、SceneおよびViewプロトコルです。』となっています。

各種プロトコルについては、他の章で説明していますが、まずは、この『宣言型フレームワーク』とはなんぞやということを、この章では説明できれば良いなと思っています。

そして気力があれば、各種プロパティについても、次章「補足2」でさらに深く説明できるようにしたいと思います。ようは、SWiftUIの一行一行まで分からないところをなくしていきたいと思っています(あくまでも私の希望ですが)。

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

Xcodeを使わない場合でも Command Line Tools はインストールしなければなりません。ターミナルを開いて、


    xcode-select --install
    
とコマンドすれば、お使いのシステムで使える最新の Command Line Tools がインストールされます。

また、作成したアプリケーションを App Storeに提出するためのファイルにするには、 Xcode を使わなければなりませんが、どこかで Xcode を使わずに作る方法を見たような気がします。もし見つかれば追って記載します。

なお、このコーナーでは macOS のプログラミングのみを説明しています。

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

  • MacBook Air 2018年モデル、メモリ8G
  • macOS Ventura 13.6.1

ユーザーインターフェイス

ユーザーインターフェイス(User Interface、UI)は、プリケーションの見かけと、その操作のことです。

フレームワーク

フレームワーク(Framework)は、ある目的のために作られた関数・定数・構造体・クラスなどの集合です。SwiftUI は macOS や iOS アプリケーションのユーザーインターフェイスを作るためのフレームワークです。

宣言型フレームワーク

さて、宣言型フレームワークですが、2013年に JavaScript 用の React フレームワークが登場した頃から使われるようになった言葉です。

とりあえずどういうものか ChtGPT に聞いてみました。

私:
宣言型フレームワークって何ですか?

ChatGPT:
宣言型フレームワークは、プログラムが達成すべき目標や振る舞いを記述する際に、具体的な手順や手続きを指示するのではなく、その目標や振る舞いそのものを宣言的に記述するプログラミングフレームワークのことです。これは、目標の指定に焦点を当て、具体的な手続きの制御や流れについてはフレームワークが処理する形式です。 宣言型プログラミングは、データやルールの関係性を定義し、システムにそれをどのように実現するかを指示することが一般的です。これに対して、命令型プログラミングは、具体的な手続きやステップを指示してプログラムを作成します。 例えば、SQL(Structured Query Language)は宣言型の言語の一例です。SQLを使用すると、データベースからデータを取得するための条件や関係性を指定しますが、具体的なデータの取得手続きについては指示しません。データベースエンジンがその指定された条件に基づいてデータの取得を行います。 JavaScriptのReactやVueなどのフロントエンドフレームワークも宣言型アプローチを採用しています。開発者はUIの状態や表示を宣言し、フレームワークがそれに基づいて適切な更新や描画を行います。




上記の言葉を補足的に説明すると、宣言型は、コード上で UI を配置したい場所に記述します。一方命令型は、まず UI を作成して、コード上の色々な場所で、その UI の設定などをしていきます。

その他の言葉の説明としては、

  1. 命令型の中には、手続き型や、オブジェクト指向型も含めて語られています。
  2. ChatGPT の説明には出てきませんでしたが、よく関数型というものも登場します。関数型は宣言型の代表的なもので、Haskell などが有名です。
    関数型を命令型だと書き間違っていました。関数型は宣言型です。2024/1/22に訂正しました。

Greeting アプリケーション

上記の説明だけでは、どういうものなのか、当然のこと分かりません。ここでは、実際に命令型フレームワークの AppKit と、宣言型フレームワークの SwiftUI で同じアプリケーションを作ってみました。

まずは、AppKit を使った例です。次の3つのファイルを作ります。

main.swift


/************************************
    main.swift
    copyright    :   vivacocoa.jp
    last modified:   Jan. 19, 2024
************************************/
import Foundation
import AppKit

let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.run()
	

AppDelegate.swift


/************************************
    AppDelegate.swift
    copyright    :   vivacocoa.jp
    last modified:   Jan. 19, 2024
************************************/
import Foundation
import AppKit
 
class AppDelegate: NSObject, NSApplicationDelegate {
     
    var window: NSWindow!
     
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        window.makeKeyAndOrderFront(nil)
    }
     
    func applicationWillFinishLaunching(_ notification: Notification) {
         
        // ウィンドウの作成
        window = NSWindow(contentViewController: ViewController())
        window.title = "Window"
        window.center()
    }
 
    // ウィンドウが閉じられたらアプリも終了する
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }
}
	

ViewController.swift

AppKit では、UI のほとんどを、次の ViewController.swift に記述します。


/************************************
    ViewController.swift
    copyright    :   vivacocoa.jp
    last modified:   Jan. 19, 2024
************************************/
import Foundation
import AppKit
 
class ViewController : NSViewController {
    
    // ラベルの作成
    let label = NSTextField(string: "")          //編集可能ラベル
//  let label = NSTextField(labelWithString: "") //変数不可ラベル
    
    // ボタンの作成
    let button = NSButton(
            title: "Greeting",
            target: Any.self, action: #selector(buttonDidClick))
    
    enum State: Int {
        case isEmpty = 1
        case isHello = 2
        case isBye   = 3
    }
    var state: State = .isEmpty
     
    override func loadView() {
        self.view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 200))
        
        // ラベルの設定
        label.frame = NSRect(
            x: 20, y: view.frame.height * 0.5 + 20,
            width: view.frame.width - 40, height: 30)
        label.isBordered = false  
        label.alignment = .center
        label.textColor = .blue
        label.isBezeled = true
        label.bezelStyle = .roundedBezel
        label.placeholderString = "Enter your name."
        
        // ボタンの設定
        button.frame = NSRect(
                x: view.frame.width / 2 - 50,
                y: view.frame.height / 2 - 50,
                width: 100, height: 30)
        
        // ラベルをウィンドウは配置する     
        view.addSubview(label)
        // ボタンをウィンドウへ配置する    
        view.addSubview(button)
    }
    // ボタンをクリックした時の処理
    @objc private func buttonDidClick(_ sender: NSButton) {
        switch state {
            case .isEmpty:
                label.stringValue = "Hello, " + label.stringValue
                label.isEditable = false
                state = .isHello
            case .isHello:
                label.stringValue = "Bye..."
                state = .isBye
            case .isBye:
                label.stringValue = ""
                label.isEditable = true
                state = .isEmpty
        }
    }
     
    override func viewDidLoad() {
        //print("viewDidLoad")
    }
    
    // ウィンドウがリザイズされた時のラベルとボタンの再配置処理
    override func viewDidLayout() {
        label.frame = NSRect(
            x: 20,
            y: view.frame.height * 0.5 + 20,
            width: view.frame.width - 40, height: 30)
        button.frame = NSRect(
                x: view.frame.width / 2 - 50,
                y: view.frame.height / 2 - 50,
                width: 100, height: 30)
    }
}
    

上記のように、命令型 UI の AppKit では、ラベルやボタンなどの UI パーツの記述が、作成、設定、配置、処理、再配置で、別々の場所に書かれます。

コンパイルと実行方法


//ターミナルで3つのファイルを作成したディレクトに移動して、次のようにコマンドします。
swiftc ViewController.swift AppDelegate.swift main.swift -o hello
//次にアプリケーションバンドルを作ります。
//アプリケーションバンドルは app という拡張子のついたフォルダです。
mkdir Hello.app
//実行ファイルをアプリケーションバンドルの中に移動します
mv hello Hello.app/
//Helloというアプリケーションが出来上がりますので、それをダブルクリックして起動します。
    

Hello アプリケーション

ラベルに名前を入力して Greeting ボタンをクリックします

ラベルに「Hello, 名前」と表示されます。そしてラベルは編集不可能となります

もう一度 Greeting ボタンをクリックすると、Bye... と表示されます

ラベルはまだ編集不可能です。もう一度 Greeting ボタンをクリックすると、 空白のラベルに戻り編集可能になります




次に、宣言型フレームワークと言われる SwiftUI を使った例です。次の2つのファイルを作成します。

App.swift


/************************************
    App.swift
    copyright    :   vivacocoa.jp
    last modified:   Jan. 19, 2024
************************************/
import SwiftUI

@main
struct Hello: App {
	@State var title: String = "Window"
	@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
	var body: some Scene {
		WindowGroup {
			ContentView()
			.navigationTitle(title)
		}
		.windowResizability(.contentSize)
		.defaultPosition(.center)
	}
}

class AppDelegate: NSObject, NSApplicationDelegate {
	func applicationShouldTerminateAfterLastWindowClosed(
	_ sender: NSApplication) -> Bool {
		return true
	}
}
    

View.swift

SwiftUI ではほとんどの UI を、次の View.swift に記述します。


/************************************
    View.swift
    copyright    :   vivacocoa.jp
    last modified:   Jan. 19, 2024
************************************/
import SwiftUI

struct ContentView: View {
	
	@State var hello = ""
	@State var isDisabled = false
	
	enum STATE: Int {
		case isEmpty = 1
		case isHello = 2
		case isBye   = 3
	}
	@State var state: STATE = .isEmpty
	
	var body: some View {
		VStack {
		    
			// ウィンドウ内の上部に隙間を設ける
			Spacer()

			// ラベルの作成から設定までのすべてを一式文で行う
			TextField("Enter your name", text: $hello)
			.textFieldStyle(.roundedBorder)
			.disabled(isDisabled)
			.foregroundColor(.blue)
			.multilineTextAlignment(.center)
			
			// ラベルとボタンの間に 40 ピクセルの隙間を設ける
			Spacer(minLength: 40)
			
			// ボタンの作成と処理と設定までのすべてを一式文で行う
			Button(action: {
				switch state {
					case .isEmpty:
						hello = "Hello, " + hello
						isDisabled = true
						state = .isHello
					case .isHello:
						hello = "Bye..."
						state = .isBye
					case .isBye:
						hello = ""
						isDisabled = false
						state = .isEmpty
				}
			}) {
				Text("Greeting")
				.frame(maxWidth: 100, maxHeight: 30)
			}
			
			// ウィンドウ内の下部に隙間を設ける
			Spacer()
		}
		
		// ウィンドウ自体の設定
		.padding(20)
		.frame(minWidth: 300, maxWidth: .infinity,
				minHeight: 200, maxHeight: .infinity)
	}
}
    

上記のように、宣言型 UI の SwiftUI では、ラベルやボタンなどの UI パーツは、それが実際に配置される場所に記述します。ウィンドウがリサイズされた場合の UI パーツの再配置の記述も必要がありません。こちらのほうが記述するコードと UI の関係が明確になっています。
このように UI を配置したい場所に、宣言(定義)するかのように記述して、配置と設定と処理まで行うことから『宣言型』と呼ぶとも言われています。

コンパイルと実行方法


//ターミナルで2つのファイルを作成したディレクトに移動して、次のようにコマンドします。
swiftc App.swift View.swift -o hello2
//次に、アプリケーションバンドルを作ります。
//アプリケーションバンドルは app という拡張子のついたフォルダです。
mkdir Hello2.app
//実行ファイルをアプリケーションバンドルの中に移動します
mv hello2 Hello2.app/
//Hello2というアプリケーションが出来上がりますので、それをダブルクリックして起動します。
    

Hello2 アプリケーション

ラベルに名前を入力して Greeting ボタンをクリックします

ラベルに「Hello, 名前」と表示されます。そしてラベルは編集不可能となります

もう一度 Greeting ボタンをクリックすると、Bye... と表示されます

ラベルはまだ編集不可能です。もう一度 Greeting ボタンをクリックすると、 空白のラベルに戻り編集可能になります

なお、初回起動時のウィンドウの位置とサイズは指定していますが、次回起動時には前回の最後の位置とサイズになります。

まとめ

2013年に React が、2019年に SwiftUI が新しい UI フレームワークとして登場して、それらは宣言型と呼ばれました。宣言型は、UI を配置する場所に UI のコードを宣言するように記述するので、宣言型と呼ばれるようになったと思われます。

上記の考え方が間違っていたとしても。SwiftUI は実際に記述しやすく、コーディングしていると気持ちが良いものです。

SwiftUI でどんどん記述して、その便利さを享受することがまずは大事です。




一方で、SwiftUI は、AppKit に比べると細かい設定ができません。このことは今後改善されるかもしれまんし、あるいはこれが宣言型フレームワークの限界かもしれません。どちらにしても新しい技術はどんどん生まれるものです。あまり思い悩まずに先に進むことが肝要かと思います。




今回、この章を書いてみて、私は JavaScript と React の勉強をしたくなりました。HTML はマークアップ言語でプログラミング言語とは違いますが、考えてみれば宣言的です。HTML の感覚で UI パーツの配置ができるのが SwiftUI です。




まだ訂正はあるかも...


27090 visits
Posted: Jan. 18, 2024
Update: Jan. 23, 2024

ホーム   目次