noteカタログ2

SwiftUI UI部品カタログ 後編

SwiftUI UI部品カタログ 前編 の続きです。
1 Textビュー、2 TextFieldビュー、3 SecureFieldビュー、4 Imageビューは前編をご覧ください。

今回は様々なUI部品の使い方をPlaygroundで動くサンプルで確認します。
いろいろなビューと関連する型の効果のカタログです。

【2020年8月4日追記:
  ● Xcode 11.3プレイグラウンドの問題は Xcode 11.6では解消していることを確認しました。
  ● 公式ドキュメントに記事執筆時になかった解説が追加されていることを確認しました。】

※この記事ではSwift言語(最新の5.1)の基本的な知識を前提にしています。コード内のキーワードや書式などの不明点は Swift5初級ガイドなどを参照してください。

※SwiftUIについて全体的なことは『SwiftUI最初の一歩』、コードの基本とビューについては『SwiftUIの文法 その1 View』を参照してください

毎月札幌でiOSアプリ作りをアシストするセミナーをやっています。1時間にわたるセミナーの全内容を、物理的に参加できない方のためにnote上で公開します。

お知らせ
電子書籍『Swift5初級ガイド』をAppleのブックストアから出しました。サンプルは無料です。MacでもiPadでもiPhoneでも読めます。
WWDC2020で発表されたSwift 5.3に対応した第6版がダウンロード可能です。(ご購入済みの場合は無料アップデートです)
(2020年7月4日に第6版にアップデートしました)
ブックストアから一度購入すると今後のアップデートは無料で読めます。

6宣伝store

iOSアプリ作りをアシストするセミナーは今後も月一回のペースで続ける予定です。(2020年3月以降COVID-19感染拡大防止のため休止しています)
詳細は connpass.com の 札幌Swift でご確認ください。そして機会があればぜひ参加してください。
アプリ作りやプログラミング教育に関連する話題は 札幌Swift のfacebookページ で発信しています。

・画像クリックで拡大表示できます
・画像を拡大表示中は画像の左右をクリックで画像だけを順に表示できます
・ソースコード部分は左右にスクロールできます
動作確認にはXcode は 11.3.1 と Swift Playgrounds 3.2 を使っていています。
サンプルはすべてXcodeのiOS用プレイグラウンド書類用コードです。(iPadのPlaygroundsでも直接入力し実行できます)

iPadのPlaygroundsで実行する場合は各ページの『"結果"を有効にする』をオフにしてください

この記事の最後の有料部分にあるリンクから完全なサンプルをダウンロードできます。


5 Buttonビュー

ボタンはコントロール類で最も頻繁に使われるもののひとつです。
ボタン操作に対応するアクションでアラートを表示するサンプルです。

// 02-65 Button
struct Sample02View: View {
  @State private var showingAlert = false
  
  var body: some View {
     Button(action: {
        self.showingAlert = true
     }) {
        Text("ボタン")
           .font(.title)
     }
     .background(Color.orange)
     .alert(isPresented: $showingAlert) {
        Alert(title: Text("ボタンが押されました"))
     }
  }
}

このサンプルではボタンの範囲を確認するために背景を .background(Color.orange) でオレンジ色にしています。

画像10

init(action:label:)
ボタンのイニシャライザの一つです。

// Buttonイニシャライザ
init(action: @escaping () -> Void, @ViewBuilder label: () -> Label)

引数は二つです。
一つ目の action はボタン操作に対応する処理をクロージャで設定します。

二つ目の label はボタンのラベルをクロージャで設定します。
トレイリングクロージャの書き方で引数の()から出して書くことが多いです。
サンプルでもトレイリングクロージャで書いています。

サンプルではボタンのアクションで @State 属性の初期値falseの showingAlert をtrue にしています。
その結果 .alert(isPresented: $showingAlert) { がアラートを表示します。

Button(action: {
   self.showingAlert = true
})
このコードはButtonイニシャライザのactionに設定するクロージャが @escaping 属性のために self. が必要になります。

5-1 アイコン付きラベルのButtonビュー

Button イニシャライザの label 引数は @ViewBuilder 属性付きです。
次のサンプルのように上記と同じイニシャライザで複数のビューを設定できます。

// 02-66 Button ラベルにアイコンと文字
struct Sample02View: View {
  @State private var showingAlert = false
  let backColor: Color = .clear   //.orange

  var body: some View {
     Button(action: {
        self.showingAlert = true
     }) {
        Image(systemName: "heart.fill")
        Text("ボタン")
     }
     .font(.title)
     .padding()
     .background(backColor)
     .alert(isPresented: $showingAlert) {
        Alert(title: Text("ボタンが押されました"))
     }
  }
}

こちらはラベルにSF Symbolsのアイコン表示を追加しています。

画像11

トレイリングクロージャのButton イニシャライザの label 引数にTextビューの前に、Image(systemName: "heart.fill")を加えています。
HStackと同じように横に並びます。
縦に並べたい場合にはVStackを使います。


6 Toggleビュー

SwiftUIのToggleビューはiOSではトグルスイッチの外見です。
オンとオフ二つの状態を切り替えます。
Toggleには複数のイニシャライザがありますが、ここではひとつだけ使っています。
次のサンプルではForm内に二つのToggleを並べています。

// 02-67 Toggle ラベルにアイコンと文字
struct Sample02View: View {
  @State var flag: Bool = false
  @State var isNormal: Bool = true

  var body: some View {
     Form {
        Toggle(isOn: $flag){
           Image(systemName: "tram.fill")
           Text("ラベル引数")
        }
//         .font(.title)

        Toggle("タイトル文字列", isOn:$isNormal)
     }
  }
}

このサンプルコードのiPadでの実行結果です。

画像12

init(isOn:label:)イニシャライザ
引数が二つのイニシャライザです。

// Toggle イニシャライザ
init(isOn: Binding<Bool>, @ViewBuilder label: () -> Label)

一つ目の isOn 引数はToggleの状態に対応するBool型変数のバインディングを渡します。
変数の値がトグルの初期状態になります。(サンプルでは上はオフの状態で、下がオンの状態)

二つ目の label 引数はラベルをクロージャで設定します。
トレイリングクロージャの書き方で引数の()から出して書くことが多いです。
サンプルでもトレイリングクロージャで書いています。
@ViewBuilder 属性付きなのでサンプルの一つ目のToggleのように複数のビューを設定できます。


7 Pickerビューの三つのスタイル

UIKitのピッカーはiOSデバイスではメニューに変わる操作を担当する場合が多いコントロールです。
SwiftUI(iOSで使う場合)では三つの表示スタイルを選ぶことができます。

最初のサンプルはデフォルトの表示スタイルです。
スタイルを指定しない場合iOSでは NavigationView が必要です。

// 02-68 Picker スタイル未指定 iOSではNavigationで選択
struct Sample02View: View {
  @State private var selectedColor = 1
  let colors = ["赤", "緑", "青"]

  var body: some View {
     NavigationView{
        Form {
           Section(header: Text("セグメンテッドコントロール 選択:\(selectedColor)").font(.headline)) {
              Picker(selection: $selectedColor, label: Text("アイテム1 色選択")) {
                 Text(self.colors[0]).tag(0)
                 Text(self.colors[1]).tag(1)
                 Text(self.colors[2]).tag(2)
              }
           }
        }
     }
  }
}

iPadでの実行例
iPadで実行するとスプリットビューのような表示になります。(iPadを縦長にした状態の上半分のキャプチャー画面です)

画像13

左側の『アイテム1色選択』を選択すると選択中の項目にチェックが付きます。

画像14

Xcodeのプレイグラウンドでは1度目だけ画面が切り替わります。
同じピッカーを選択しても2度目からは画面が切り替わらない問題があります。(このためXcodeのプレイグラウンドでは選択を変更できません)

【2020年8月4日追記:Xcode 11.3のこの不具合は Xcode 11.6では解消しています】

iPadでの実行は『"結果"を有効にする』をオフにしてあれば、問題ありません。

init(selection:label:content:)イニシャライザ
Picker には複数のイニシャライザがありますが、ここでは init(selection:label:content:)イニシャライザを使っています。

// init(selection:label:content:)イニシャライザ
init(selection: Binding<SelectionValue>, 
label: Label, 
@ViewBuilder content: () -> Content)

引数は三つです。
最初の selection引数はピッカーの状態に対応するバインディングを渡します。
Hashableプロトコルに準拠していれば利用可能です。このサンプルでは整数です。

二つ目のlabel引数はラベル表示です。
LabelはViewです。
@ViewBuilder属性ではないのでビューを一つだけ指定するかHStackなどで組み合わせます。

三つ目のcontentはピッカーに表示し選択操作させる内容です。
@ViewBuilder属性なので10個までは、そのまま並べて書くことができます。
.tag(n)でそれぞれの選択項目を区別します
それぞれのtagで設定する値は同じではいけません。
selection引数で区別できるtagが必要です。

7-1 SegmentedPickerStyleのピッカー

二つ目のサンプルはSegmentedPickerStyleです。
iOSではセグメンテッドコントロールの外見になります。
iOSのHIG(Human Interface Guidelines)ではセグメントの数は5個以内にするべきとされています。

// 02-69 Picker スタイルSegmentedPickerStyle
struct Sample02View: View {
  @State private var selectedColor = 0
  let colors = ["赤", "緑", "青"]

  var body: some View {
     NavigationView{
        Form {
           Section(header: Text("セグメンテッドコントロール \(selectedColor)").font(.headline)) {

              Picker(selection: $selectedColor, label: Text("色選択")) {
                 Text(self.colors[0]).tag(0)
                 Text(self.colors[1]).tag(1)
                 Text(self.colors[2]).tag(2)
              }
              .pickerStyle(SegmentedPickerStyle())

           }
        }
     }
  }
}

どのセグメントを選んだかをセクションヘッダーに表示するサンプルです。

画像15

このサンプルは Xcode のプレイグラウンドでは正しく再表示されません。
(切り替えた結果を表示するヘッダーが更新されません)
各行のQuick Lookを確認するとそちらは更新されています。
Live View再表示の問題と考えられます。

【2020年8月4日追記:Xcode 11.3のこの不具合は Xcode 11.6では解消しています】


7-2 WheelPickerStyleのピッカー

iOSで一般にピッカーと呼ばれるスタイルです。
SwiftUIでは .pickerStyle(WheelPickerStyle()) を設定しなければこのスタイルで表示されません。

// 02-70 Picker スタイルWheelPickerStyle
struct Sample02View: View {
  @State private var selectedProduct = 0
  let products = ["Mac", "iPod", "touch", "iPhone", "TV", "Watch"]

  var body: some View {
     Form {
        Section(header: Text("ピッカー  \(products[selectedProduct])").font(.headline)) {

           Picker(selection: $selectedProduct, label: Text("")) {
              ForEach(0..<6) {
                 Text(self.products[$0]).tag($0)
              }
/*   Xcode用            // ForEachを使うのが普通
              Text(self.products[0]).tag(0)
              Text(self.products[1]).tag(1)
              Text(self.products[2]).tag(2)
              Text(self.products[3]).tag(3)
              Text(self.products[4]).tag(4)
              Text(self.products[5]).tag(5)
              */
           }
           .pickerStyle(WheelPickerStyle())

        }
     }
  }
}

iPadでの実行例(ホイールを回してTVを選んだ状態)です。

画像16

このサンプルは Xcode のプレイグラウンドでは正しく再表示されません。
★切り替えた結果を表示するヘッダーが更新されません
★ForEachの実行にログを多数出力します。
Xcode のプレイグラウンドではForEach部分をコメントにし、『code用』の6行のコメントを外して実行してください。

【2020年8月4日追記:Xcode 11.3のこの不具合は Xcode 11.6では解消しています。上記の対策は不要で問題なく動作することを確認しました。】


8 DatePickerビュー

DatePickerは日時の入力専用のピッカーです。
たくさんのイニシャライザがあります。
表示は設定アプリの言語と地域の設定により変わります。
このサンプルではひとつのイニシャライザだけ使っています。
サンプルは当日までの日付を選択する例です。

// 02-71 DatePickerのサンプル
struct Sample02View: View {
  var dateFormatter: DateFormatter {
     let formatter = DateFormatter()
     formatter.dateStyle = .long
     return formatter
  }

  @State private var birthDate = Date()

  var body: some View {
     Form {
     Section(header: Text("日付ピッカー(過去の日付用)").font(.headline)) {
        DatePicker(selection: $birthDate, in: ...Date(), displayedComponents: .date) {
           Text("日付を選択")
        }

        Text("入力日付:\(birthDate, formatter: dateFormatter)")
        }
     }
  }
}

iPadでの実行画面
(Xcodeのプレイグラウンドでは表示とフォーマットが英語対応になります)
(アプリでは言語対応すれば対応言語で自動表示されます)

画像17

「日付を選択」をタップするとピッカーが開きます。
(日付は実行した当日の日付になります)

画像18

DatePickerはドキュメントにイニシャライザが12も載っています。

// init(selection:in:displayedComponents:label:)
init(selection: Binding<Date>, 
in range: PartialRangeThrough<Date>, 
displayedComponents: DatePicker<Label>.Components = [.hourAndMinute, .date], 
@ViewBuilder label: () -> Label)

ドキュメントに引数の説明はありません。

【2020年8月4日追記:執筆時(2020年2月)にはなかったDatePickerの全イニシャライザのドキュメントに引数の説明(英文)が追加されていることを確認しました】

サンプルで利用している範囲で説明します。

最初の引数 selection はDate型のバインディングです。
ピッカーに表示するデフォルト日時を設定できます。

二つ目の引数 in は日付の範囲を指定します。
このイニシャライザでは PartialRangeThrough<Date> です。
サンプルでは in: ...Date(), として過去から現在の日付(日時)までを指定しています。
指定日時以降を設定可能なイニシャライザもあります。

三つ目の引数 displayedComponents は日付だけを選択するか、時刻を選択するかを設定します。
サンプルでは .date で年月日の選択にしています。
この引数を省略すると[.hourAndMinute, .date]となり日付と時分を選択可能になります。

4つ目の引数 label は ラベルです。
@ViewBuilder属性なので10個まで複数のビューをそのまま並べて書くことができます。
サンプルでは Text("日付を選択") をトレイリングクロージャで設定しています。

8-1 Date型とDateFormatter型

Date型
Date型はFoundationフレームワークの日時を扱う型です。
引数なしのイニシャライザ Date() は現在の日時を返します

このサンプルでは日付のみを利用していますが、時刻情報も含んでいます。

DateFormatter型
DateFormatter型もFoundationフレームワークです。
多言語対応の日時表示(時刻を文字列に変換する/文字列を日時に変換する)を扱います。

let formatter = DateFormatter()
formatter.dateStyle = .long

Formatter型を継承したclassなので定数でもプロパティを変更できます。
定数formatterにデフォルトのDateFormatterインスタンスを代入し、フォーマットを .long にしています。
.short では『2020/02/17』、.full では『2020年2月17日 月曜日』と表示します。
言語や地域は設定アプリでデバイスで共通に設定したものを使い、コードで指定できるのは詳しい(.full)か簡略表示(.short)かだけになります。

またtimeStyleプロパティもあります。
formatter.timeStyle = .short とすると14:40のフォーマットで時刻を日付に続けて表示します。


9 Sliderビュー

Sliderビューは実数の値を最小から最大の範囲で調整します。
範囲外の数値には設定できません
トグルスイッチとともに直感的なUIです。
Sliderにも複数のイニシャライザがありますが、ここのサンプルでは二つだけ使っています。

// 02-72 Slider
struct Sample02View: View {
  @State var sliderValue: Double = 10.0
  @State var brightness: Double = 0.5

  var body: some View {
     Form {
        Section(header: Text("スライダー \(sliderValue)").font(.headline)) {
           Slider(value:$sliderValue, in:0...50) {
              Text("設定")
           }
        }

        Section(header: Text("明るさ \(brightness)").font(.headline)) {
           Slider(value:$brightness,
                 minimumValueLabel: Image(systemName: "sun.min"),
                 maximumValueLabel: Image(systemName: "sun.max")) {
              Text("明るさ")
           }
        }
     }
  }
}

iPadでの実行画面です。(Xcodeのプレイグラウンドでは再表示が正しく行われません)

【2020年8月4日追記:Xcode 11.3のこの不具合は Xcode 11.6では解消しています】

画像19


init(value:in:onEditingChanged:label:)
ラベル表示付きイニシャライザです。【iOSではラベルは画面に表示されません】

// Sliderビューイニシャライザ
init<V>(value: Binding<V>, 
in bounds: ClosedRange<V> = 0...1, 
onEditingChanged: @escaping (Bool) -> Void = { _ in }, 
@ViewBuilder label: () -> Label)
 where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint

最初の引数 valueは実数のバインディングです。
Sliderの初期値設定と変更後の値として利用できます。

二つ目の引数 in は最小から最大の範囲です。
省略可能です。省略するとデフォルトの範囲(0.0から1.0)になります。

三つ目の引数 onEditingChanged はSliderの値変更に対応する処理をクロージャで設定します。
省略可能です。省略するとデフォルトでは何も処理しません。

最後の引数 label はラベルのビューを設定します。
iOSではラベルを設定していも画面に表示されませんでした
VoiceOverの読み上げに利用されるかと思い試しましたが、私の設定ではラベルまでは読み上げませんでした。

9-1 最小最大にラベル付きのSliderビュー

init(value:in:onEditingChanged:minimumValueLabel:maximumValueLabel:label:)
最小と最大にラベルを表示するイニシャライザです。

// 最小と最大にラベルを表示する
init<V>(value: Binding<V>, 
in bounds: ClosedRange<V> = 0...1, 
onEditingChanged: @escaping (Bool) -> Void = { _ in }, 
minimumValueLabel: ValueLabel, 
maximumValueLabel: ValueLabel, 
@ViewBuilder label: () -> Label)
 where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint

最初の引数 valueは実数のバインディングです。
Sliderの初期値設定と変更後の値を利用できます。

二つ目の引数 in は最小から最大の範囲です。
省略可能です。省略するとデフォルトの0.0から1.0になります。

三つ目の引数 onEditingChanged はSliderの値変更に対応する処理をクロージャで設定します。
省略可能です。省略するとデフォルトでは何も処理しません。

4つ目の引数 minimumValueLabel はSlider最小値側に表示するラベルです。
ValueLabelもビューです。
サンプルは明るさ調整なので太陽アイコンをSF Symbolsで表示しました。

五つ目の引数 maximumValueLabel はSlider最大値側に表示するラベルです。
ValueLabelもビューです。

最後の引数 label はラベルのビューを設定します。
iOSではラベルを設定していも画面に表示されませんでした。


10 Stepperビュー

Stepperビューは最小から最大の範囲で値を増減します。
範囲外の数値には設定できません。
外観はプラットフォームにより変わります。

ここから先は

16,258字 / 9画像 / 1ファイル
この記事のみ ¥ 500

今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。