macOS   SwiftUIプログラミング   テーブル

ホーム

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

【本文】
今までは、ビュー(View、UI部品)をレイアウトするのに、VStack や HStack 使ってきました。このように View をレイアウトするものをコンテナ(container)と言います。この章では Table(テーブル)いうコンテナを説明します。なおコンテナも 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

Table

SwiftUI の Table は、macOS 12.0 以降で動作します。


App.swift

Xcode では、プロダクト名App.swift になります。


import SwiftUI

@main

struct FooApp: App {
    init() {
        // Tab(タブ)に関するメニューを削除するために次のコードを追加しました。
        NSWindow.allowsAutomaticWindowTabbing = false
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands(content: {
            // New Window メニューを削除するために次のコードを追加しました。
            CommandGroup(replacing: .newItem) {}
        })
    }
}
	


View.swift

Xcode では、ContentView.swift になります。


import SwiftUI

struct ContentView: View {
    
    struct Student: Identifiable {
        let name: String
        let grade: String
        let score: Int
        let id = UUID()
    }

    private var students = [
        Student(name: "Bob", grade: "B", score: 75),
        Student(name: "Dave", grade: "D", score: 55),
        Student(name: "Andy", grade: "A", score: 90),
        Student(name: "Cathy", grade: "C", score: 60)
    ]
    
    var body: some View {
        Table(students) {
            TableColumn("Name", value:\.name)
            TableColumn("Grade", value:\.grade)
            TableColumn("Score") { item in Text(String(item.score))}
        }
        // ウィンドウの大きさを設定しています
        .frame(minWidth: 500, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        // ウィンドウを閉じたらアプリケーションも終了するようにしています
        .onDisappear(){ NSApplication.shared.terminate(self) }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
	


アプリケーションバンドル

Xcode で作業する場合は、この部分は無視してください。 ターミナルで作業する場合は「アイコン」を参考にしてアプリケーションバンドルを作ってください。 アプリケーションの名前を変えるために、Info.plist の CFBundleName を書き換えます。ここでは「Table」という名前にしました。

アイコンは次のサイトから .icns ファイルをダウンロードしました。
フリーアイコンSVG、PNG、ICO、ICNS

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>Table</string>
</dict>
</plist>
    


ビルド

Xcode で作業している場合は、▶︎(ビルドボタン)をクリックするか、⌘Rを押してください。ターミナルでビルドする場合は次のようにします。


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

実行

行を選択できるようにする

View.swift

Xcode では、ContentView.swift になります。App.swift(プロダクト名App.swift)は、前述と同じです。


import SwiftUI

struct ContentView: View {
    
    struct Student: Identifiable {
        let name: String
        let grade: String
        let score: Int
        let id = UUID()
    }

    private var students = [
        Student(name: "Bob", grade: "B", score: 75),
        Student(name: "Dave", grade: "D", score: 55),
        Student(name: "Andy", grade: "A", score: 90),
        Student(name: "Cathy", grade: "C", score: 60)
    ]
    
    // 単一行を選択できるようにするには次のプロパティを宣言します
    @State private var selectedStudent:Student.ID? = nil
    
    // 複数行を選択できるようにするには次のプロパティを宣言します
    //@State private var selectedStudent = Set<Student.ID>()
    
    var body: some View {
        
        // Table の引数に selection: を追加します
        Table(students, selection: $selectedStudent) {
            TableColumn("Name", value:\.name)
            TableColumn("Grade", value:\.grade)
            TableColumn("Score") { item in Text(String(item.score))}
        }
        .frame(minWidth: 500, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        .onDisappear(){ NSApplication.shared.terminate(self) }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
    

項目をソートできるようにする

View.swift

Xcode では、ContentView.swift になります。App.swift(プロダクト名App.swift)は、前述と同じです。


/*
struct Student と private var students を
struct ContentView の外(グローバルの位置)に出しています。
*/

import SwiftUI

struct Student: Identifiable {
    let name: String
    let grade: String
    let score: Int
    let id = UUID()
}

private var students = [
    Student(name: "Bob", grade: "B", score: 75),
    Student(name: "Dave", grade: "D", score: 55),
    Student(name: "Andy", grade: "A", score: 90),
    Student(name: "Cathy", grade: "C", score: 60)
]

struct ContentView: View {

    // 単一行を選択できるようにするには、次のプロパティを宣言します
    @State private var selectedStudent:Student.ID? = nil
    
    // 複数行を選択できるようにするには、次のプロパティを宣言します
    //@State private var selectedStudent = Set<Student.ID>()
    
    // ソートできるようにするには、次のプロパティを宣言します
    // 末尾の score は、name でも、grade でも良いです、動作は同じです
    @State private var sortOrder = [KeyPathComparator(\Student.score)]
    
    var body: some View {
        
        // Table の引数に sortOrder: を追加します
        Table(students, selection: $selectedStudent, sortOrder: $sortOrder) {
            TableColumn("Name", value:\.name)
            TableColumn("Grade", value:\.grade)
            TableColumn("Score") { item in Text(String(item.score))}
        }
        // 次の .onChange モディファイアがソートを実行しています
        .onChange(of: sortOrder) { students.sort(using: $0) }
        .frame(minWidth: 500, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        .onDisappear(){ NSApplication.shared.terminate(self) }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
    

Name と Grade をクリックすると、項目が昇順、降順にソートされます。score のところは、残念ながらクリックに反応しません。

項目の表示をボールド(太字)にする

Xcode では、ContentView.swift になります。App.swift(プロダクト名App.swift)は、前述と同じです。

View.swift


/*
struct Student と private var students を
struct ContentView の外(グローバルの位置)に出しています。
*/

import SwiftUI

struct Student: Identifiable {
    let name: String
    let grade: String
    let score: Int
    let id = UUID()
}

private var students = [
    Student(name: "Bob", grade: "B", score: 75),
    Student(name: "Dave", grade: "D", score: 55),
    Student(name: "Andy", grade: "A", score: 90),
    Student(name: "Cathy", grade: "C", score: 60)
]

struct ContentView: View {

    // 単一行を選択できるようにするには、次のプロパティを宣言します
    @State private var selectedStudent:Student.ID? = nil
    
    // 複数行を選択できるようにするには、次のプロパティを宣言します
    //@State private var selectedStudent = Set()
    
    // ソートできるようにするには、次のプロパティを宣言します
    // 末尾の score は、name でも、grade でも良いです、動作は同じです
    @State private var sortOrder = [KeyPathComparator(\Student.score)]
    
    var body: some View {
        
        // Table の引数に sortOrder: を追加します
        Table(students, selection: $selectedStudent, sortOrder: $sortOrder) {
            
            // Name と Score 項目をボールドに変えています
            TableColumn("Name" ) { item in Text(item.name).bold() }
            TableColumn("Grade", value:\.grade)
            TableColumn("Score") { item in Text(String(item.score)).bold() }
        }
        // 次の .onChange モディファイアがソートを実行しています
        .onChange(of: sortOrder) { students.sort(using: $0) }
        .frame(minWidth: 500, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
        .onDisappear(){ NSApplication.shared.terminate(self) }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
    

項目の表示をボールドしたカラム(列)は、ヘッダをクリックしてもソートできません。表示内容をクロージャーで呼び出すとソートできなくなるみたいです。




41003 visits
Posted: Jul. 28, 2022
Update: Jul. 30, 2022

ホーム   目次