
iPhone アプリを自分でつくる 157.
今回の内容: 《番号探しゲーム》をつくる 6.
ランダムに並んだ番号を順にタップするゲーム《EyeSpec》をつくっています。1番から20番(くらい)までの番号をできるだけ早くタップしていくというだけのものです。
目指すデザインは第152回《番号探しゲーム》をつくる 1. をご覧ください。
前回は、ゲームの状態に応じたボタン機能をセットして、ゲームの流れをつくることができました。
もう少し進めていきましょう。
今回はゲームが終了したときの alert表示を用意したいと思います。
きっかけをつくるBool型のプロパティをshowComplete としてセットしておきます。
@State var showComplete = false
struct ContentView: View {
@State private var playStatus = PlayStatus.standby
@State private var numbers = Array(1...4)
@State private var nextNumber = 1
@State private var showComplete = falseそして、このshowComplete がtrue になったら表示される .alert() をセットします。
.alert() の書式には様々ありますが、基本的な形でセットをしてみます。
ここでは、.alert() のClosure にボタンを配置して、ゲームを reset()するようにしました。
また、message には、"…秒でクリア" としておき、タイマーをセットした時に変数を入れる準備をしておきます。
※ 終了時のCompleted ボタンにセットした reset() は使われないことになりますが、特に不都合にならないので、そのままにしておきます。
} label: {
Text(playStatus.rawValue)
}
.alert("ミッション クリア!", isPresented: $showComplete) {
Button("Next") {
reset()
}
} message: {
Text("...秒でクリア おめでとう! ")
}
}
func numberTapped(_ number: Int) {そして、最後に正しいボタンがタップされたら、showComplete = true にしたいので、numberTapped() メソッドに加えます。
func numberTapped(_ number: Int) {
if playStatus == .inPlay {
if number == nextNumber && number == numbers.count {
playStatus = .completed
showComplete = true
} else if number == nextNumber {これでゲームをしてみましょう!

これでアラート表示は大丈夫そうです。
ここからは .alert() メソッドの使い方について調べてみます。
先ほど書いた.alert() のところで Quick Help を表示すると、
alert(_:isPresented:actions:message:)
との記載がありますが、ここで、使用する引数名がわかります。
その下の四角い枠内には、
func alert<A, M>( ~ ) -> some View where A: View, M: View
とあり、このメソッドの型が確認できます。
※ これが意味するのは、「引数~を受けてView に適合したA型とM型を返す」型です。これだけだとよくわかりませんが心に止めておきます。

alert(_:isPresented:actions:message:) と書いてあるカッコの中の最初のアンダーバー_ は、引数名は書かないで良いということですね。
①そこには、LocalizedStringKey つまり String が入ることがわかります。
その String がタイトルになります。
"ミッション クリア!"です。
② isPresented には、バインディングしたBool値をセットします。
isPresented: $showComplete
この $showComplete が true になったとき、つまり最後の番号ボタンがタップされたときにアラートが表示されるように設定しています。
しかし、コード内にfalse のタイミングはセットしていません。なぜか?
説明に「ユーザーがアラートの中のいずれかのボタンをタップすると、(バインディングされた値が)false にセットされ表示が消される」と書いてあるように、false に戻すのはアラートメソッドの仕事の一つとして設定されています。
③ actions: には () -> A とあります。
引数に () -> Void とか () -> Int とか、クロージャをセットするということですが、この'A' はアクションを返すことを意味しています。
そして、'A'は View 型であることが先に書かれていたので、アクションを持つView 型がクロージャに入ることがわかります。
アクションを持つView 型と言えば、Button() ですね。
さらに、ここには @ViewBuilder ということが加えられています。
このクロージャには、複数のボタンをセットできるようになっており、その際に、@ViewBuilder が配置を調整してくれることになっています。
{ Button("Next") { reset() } }
今までButton と言えば、下の例のように Button { アクション } label: { テキスト } という書き方を使ってきました。
Button {
reset()
} label: {
Text("Next")
.font(.title)
.foregroundStyle(.green)
}このような記入をしても動きます。ただし、.font(.title) になりませんし、文字スタイルも .green 色になりません。
スタイルの修飾は働かず全部通常の文字となるのです。
なので、結局は Button("Next") { reset() } と同じになので、コードが短いこちらを使用することになります。
これについては、次のmessage に関して、この類の説明があります。
④ message: には () -> M とあります。
action: と同様、クロージャをセットするということですが、この'M' はメッセージを返すことを意味しています。
Swift ではクロージャが2つ続くときには、このようなラベルがつきます。Button { } label: { } のように、.alert( ) { } message: { } となります。
View型でのメッセージということなので、Text() となります。
ただし、説明に'Only unstyled text is supported for the message' とあります。つまり、装飾していないテキストだけを受け付けていますということです。いくらフォントの大きさや色をセットしても無視されるのです。
@ViewBuilder が仕事をしてくれますが、ここではaction: のようにView を2つ配置するということはなく、例えば、if 文やOptional のテキストなどの場合、その時々の条件にあった配置をするためです。
アラートではスタイルなどが、とてもコントロールされています。
オフィス関連のアプリであれば、このような統一されたレスポンスが重要だと思いますが、今回はゲームなのでもう少し自由でも良い感じがしますね。
その場合は、アラートではなくて自作したView を表示するようにします。

アラートのいろいろなセット方法
アラートにアクションやメッセージを入れなくても、タイトルと'OK'ボタンを持つButton() が Default で作成されます。
お知らせの場合これだけでも十分な場合もあります。

メッセージだけを入れても、タイトルとメッセージと'OK'ボタンを持つButton() が Default で作成されます。
お知らせの場合これだけでも良い場合もあります。

ボタンを2つ以上セットすることができます。
例では空白になっていますが実際には必要に応じてButton() のアクションを記入します。

Button() に役割を表す、role: .destructive をセットすると、赤文字で注意を促す表示がされます。
削除などのメソッドを使う場合に使われます。
※ 参考例では、空白になっていますが、実際にはdelete() などの処理をクロージャに記入します。

Button() のrole: に.destructive をセットすると、キャンセルボタンを配置せずとも、自動的にCancel ボタンが配置されます。
削除だけのボタンがある状態はよろしくない、という配慮かと思います。

3つのボタンを入れた場合の配置。
状況によっては Picker() や Menu()、 .contextMenu などの使用を検討して良いかもしれません。

ログイン用の枠を作ることができます。
下の例では、TextField() でメールアドレスを、SecurityField() で文字を隠すパスワード入力用の枠をつくることができます。

まとめ
ゆっくりですが、進めています。
次回第158回内容
「番号探しゲーム」7. です。
main ファイル
import SwiftUI
@main
struct EyeSpecApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}ContentView ファイル
import SwiftUI
enum PlayStatus: String {
case standby = "Start"
case inPlay = "Reset"
case completed = "Complete!"
}
struct ContentView: View {
@State private var playStatus = PlayStatus.standby
@State private var numbers = Array(1...4)
@State private var nextNumber = 1
@State private var showComplete = false
var body: some View {
ForEach(numbers, id: \.self) { number in
Button {
numberTapped(number)
} label: {
Text("\(number)")
}
}
Text("Next: \(nextNumber)")
Button {
switch playStatus {
case .standby:
gameStart()
case .inPlay:
reset()
case .completed:
reset()
}
} label: {
Text(playStatus.rawValue)
}
.alert("ミッション クリア!", isPresented: $showComplete) {
Button("Next") {
reset()
}
} message: {
Text("...秒でクリア おめでとう! ")
}
}
func numberTapped(_ number: Int) {
if playStatus == .inPlay {
if number == nextNumber && number == numbers.count {
playStatus = .completed
showComplete = true
} else if number == nextNumber {
nextNumber += 1
} else {
// 番号を間違えた場合のコード
}
} else if playStatus == .standby {
// ゲームが始まっていない場合のコード
}
}
func gameStart() {
numbers = numbers.shuffled()
playStatus = .inPlay
}
func reset() {
nextNumber = 1
numbers = numbers.sorted()
playStatus = .standby
}
}
#Preview {
ContentView()
}以上です