まえがき
iPadのplaygroundsを使用して、swiftUIの勉強をしている初心者です。
今回の日記は、
NavigationViewを使いこなしたくて試行錯誤していたらNavigationStackの存在に気付いてしまった
というところまでの僕の作業記録となります。
この記事に含まれる技術的な内容は、誤りを含んでいる可能性があります。
僕が日記として勉強記録をつける目的としては以下のようなものがあります。
- 作業の内容や目的を明確にする
- 作業中参考にした記事などを、作業内容と合わせて保管する
- 初心を忘れないため、理解するまでの過程を記録として残す
本記事では前回の続きとなる作業を致します。
↓↓↓前回↓↓↓
NavigationView内を充実させてみる
今回達成したいこと
- NavigationLinkの配置をいい感じにする
- Listの機能を使ってみる
- 配列から呼び出してみる
今、こうなってる↓ので
左側の部分を、こういう感じ↓に、したい。
前回の作業後、次に参考にしたい記事などをぱらぱらと集めてみた。
今回はこれを元にNavigationLinkをそれっぽい感じに配置したい。
その1 構想を練る
現時点の中身
SecondaryのViewは以下の通り。
struct Multiple7View: View {
@State private var count :Int = 0
@State private var msg :String = "7の倍数やねん!!!!"
@State private var displaymsg :String = ""
var body: some View {
VStack {
Image(systemName: "globe.asia.australia")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Spacer()
Text("7の倍数お知らせ君")
Spacer()
Text(displaymsg)
Text(String(count))
.font(.largeTitle)
Button("count") {
withAnimation{
count += 1
if(count % 7 == 0){
displaymsg = msg
}else {
displaymsg = ""
}
}
}
.buttonStyle(.bordered)
.font(.title)
Spacer()
Button("reset"){
withAnimation{
count = 0
displaymsg = ""
}
}
.buttonStyle(.bordered)
.font(.title2)
Spacer()
Spacer()
}
}
}
3の倍数と7の倍数、今のところこの2つしか作ってないのだけれど
これをListにどう格納して、どう呼び出してやろうか…
呼び出すSecondaryについても少し形を変えてあげないといけない。
僕のプラン
実行可能かどうかはさておき、こういう形でなら実現できそうかな?と思っているプラン
- Listに使うためのIdentifiableなstructを定義してあげる
- 汎用性の高いViewを新設する
- PrimaryのViewで、Listを使う
- NavigationLinkに「」のtitleを渡して、新設された汎用性の高いViewに紐づける
表現方法がルー大柴みたいになってきた…表現的にこれで合っているのだろうか。
要素に持たせるのは数値…ここで宣言する予定のnumだけでいいのはわかっているんだけど、まずは理解しないといけないから呼び出しをシンプルに(入れ子の数を減ら)したい。
うん。呼び出しがたくさん出てくる気がする…
そろそろ設計図みたいなのを書いた方が良さそうかな…
その2 構造体を定義する(命名に大苦戦)
やることは決まってるんだ。
struct 構造体: Identifiable {
let num: Integer
let title: String
let comment: String
let id = UUID()
}
let 変数親 = [
構造体(num:3,title:"3の倍数数える君",comment:"3の倍数やで〜"),
構造体(num:7,title:"7の倍数数える君",comment:"7の倍数やで〜")
]
そう。命名。命名マジやばい。
アスカとかシンジとかじゃダメ…?(ダメです)
一応僕のゆるふわ知識の中に、「変数の命名にはある程度推奨される規則がある」というものがインプットされてる。
命名する前に、まず色々調べてみることにした。
参考にしたもの
Swiftではキャメルケースを使用します。
変数、定数: ローワーキャメルケースを使用。(camelCase, someVariable, someContentsのように最初の単語の頭が小文字で、それ以降の単語は頭文字を大文字にする)
型: キャメルケースを使用。(CamelCase, SomeVariable, SomeContentsのように最初の単語の頭文字から単語ごとに頭文字を大文字にする)
ふむ。NanntokanoKouzoutaiとkouzoutaisArrayみたいな感じ…?
Swiftは型セーフな言語なので、変数定義の際に必ず何かしらの型に決まり、その変数には明示しなくとも型の情報が持たれることになります。
そのため、たとえローカル変数であっても、型名を変数名として使用するのは避け、何が入っている変数なのかという情報を与えるほうがベターです。
ただ、UI部品の命名に関してはその次第ではなく、Apple標準ライブラリに倣い型名をSuffixとしてつけるのが一般的です。そうすることで、命名の重複を避けるという目的もあります。
kouzoutaisArrayは頭痛が痛い感じでアホくさなのでやめようね!ってことやな。
シンプルにkouzoutaisでええぞ、と。
それで、「UI部品は」「サフィックス」ってことは、えーと、つまり
ViewPrimaryじゃなくてPrimaryViewとか、SecondaryViewとかにしてあげようねってことか。
ViewPrimaryだと、「ViewをPrimaryする部品」みたいに受け取られちゃうのか。
記事を読んでいて「外部引数」「内部引数」ってなんだ…?となるなどしたので一応こちらについても確認。
ほへえ〜!関数へ渡す引数にこんな感じでネームプレートつけられるんや…むしろswiftにおいては仕様上マストな機能っぽいな。
アンダーバーで省略も出来るっぽいけど、「非推奨!わざわざ明示せざるを得ないように作ってんだから黙って書いてくれ!」という圧を感じる。
他の言語にもこういう機能あるんかな。あとで調べてみよーっと。
英語弱者、chatGPT頼み
なんとなーく理解したところで、あんまりいい案が出てこない…。
僕が今作ってるnの倍数数える君の構造体はどう命名すべきなんや!!
nの倍数がMultiples of nなのはわかるが、どうしたもんか…
chatGPT君に聞いてみた。(会話は長いので折りたたみ)
僕「swiftで簡単なコードを書いていて、命名にすごく悩んでいる構造体(Identifiable)と変数名があるんだけど、意見をくれないかな。
nの倍数を教えてくれるアプリを作っていて、NavigationViewのListと、SecondaryのView内でその変数を使用したいと思っているんだけど…
struct 構造体: Identifiable {
let num: Integer
let title: String
let comment: String
let id = UUID()
}
let 変数親 = [
構造体(num:3,title:"3の倍数数える君",comment:"3の倍数やで〜"),
構造体(num:7,title:"7の倍数数える君",comment:"7の倍数やで〜")
]
」
以下chatGPTの回答
おお〜、倍数アプリ!面白そうだし、Swiftらしく構造体できれいに作ってるね!
命名で悩むの、すごく分かる。特に日本語っぽいコードってニュアンスが難しいんだよね。
まず、命名を見て感じたことからざっくばらんに意見を言わせてもらうね。
■ 構造体名:「構造体」→ MultiplierInfo とか MultiplierItem はどう?
構造体 ってのはちょっと汎用すぎる&Swiftのキーワード感が強いから、
中身から分かる名前をつけた方が読みやすいかな〜と思う。
たとえば:
struct MultiplierItem: Identifiable {
let num: Int
let title: String
let comment: String
let id = UUID()
}
「倍数を扱うアイテム」って意味で MultiplierItem
もっとゆる〜くするなら BaisuKuns みたいなネタ寄せ命名もアリ(笑)
■ 変数名:「変数親」→ multipliers や baisuList が自然
「親」っていうのがどこから来てるかにもよるけど、リストで複数あるって意味なら
以下のような名前がいいかも:
- multipliers(シンプル・英語)
- baisuList(日本語混じり)
- counterConfigs(カウンター的な使い方なら)
- characters(「3の倍数数える君」みたいにキャラっぽくしたいなら)
■ まとめるとこんな感じ:
struct MultiplierItem: Identifiable {
let num: Int
let title: String
let comment: String
let id = UUID()
}
let multipliers = [
MultiplierItem(num: 3, title: "3の倍数数える君", comment: "3の倍数やで〜"),
MultiplierItem(num: 7, title: "7の倍数数える君", comment: "7の倍数やで〜")
]
言語のトーン(カジュアル寄せ or しっかり英語)ってプロジェクトの雰囲気にもよるから、
もし「もっとふざけた命名でいきたい」とか「英語は極力避けたい」みたいな方針があれば、そっちに合わせたネーミングも考えるよ!
どういう雰囲気にしたいとかある?
以上、chatGPTの引用終わり
chatGPT、優秀すぎんか?めっちゃおもろいんやが。 あと、毎回「いい着眼点だね!」とか「きれいに作ってるね!」とか褒めてくれるの、普通に嬉しい。
とりあえず、
構造体→MultiplierItem
変数→multipliers
を採用しよう。ありがとうchatGPT。
struct MultiplierItem: Identifiable {
let num: Int
let title: String
let comment: String
let id = UUID()
}
let multipliers = [
MultiplierItem(num: 3, title: "3の倍数数える君", comment: "3の倍数やで〜"),
MultiplierItem(num: 7, title: "7の倍数数える君", comment: "7の倍数やで〜")
]
その2 SecondaryのViewを新設する
汎用性の高いViewを新設する。
元のViewを変えてあげるだけで良さそう。
struct MultiplierView: View{
@State private var count :Int = 0
@State private var displaymsg :String = ""
let multiplier: MultiplierItem
var body: some View{
VStack{
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Spacer()
Text(multiplier.title)
Spacer()
Text(displaymsg)
Text(String(count))
.font(.largeTitle)
Button("count") {
withAnimation{
count += 1
if(count % multiplier.num == 0){
displaymsg = multiplier.comment
}else {
displaymsg = ""
}
}
}
.buttonStyle(.bordered)
.font(.title)
Spacer()
Button("reset"){
withAnimation{
count = 0
displaymsg = ""
}
}
.buttonStyle(.bordered)
.font(.title2)
Spacer()
Spacer()
}
}
}
よし、行けた。このセクションは難なく突破。
まぁ元々仕上がってはいたからね…
その3 PrimaryのViewにListを入れ込む
さて。問題の箇所。
Listを使用するにあたって、List各行のパーツを別で作ってやらないといけないっぽい。
なくてもいけるんかな…?思うままに一回書いてみるか。
リスト単体ならこんな感じか。
struct PrimaryView: View {
let multipliers: MultiplierItem
var body: some View {
//このViewの内部をNavigationViewにしてあげる?
//このあとVStackじゃなくてNavigationLinkを使う?
List(multipliers) { multiplier in
VStack(alignment: .leading) {
Text(multiplier.title)
.foregroundColor(Color.gray)
}
}
}
}
だからたぶん、これをこんな感じに書き換えて…
struct PrimaryView: View {
let multiplier: MultiplierItem
var body: some View {
NavigationView {
List(multipliers) { multiplier in
NavigationLink(destination: MultiplierView(multiplier: MultiplierItem)) {
Text(multiplier.title)
.foregroundColor(Color.gray)
}
}
}
}
}
う、うごけ〜!!
知ってた。
Initializer 'init (_:rowContent:)' requires that 'Multiplierltem' conform to'RandomAccessCollection'
Cannot convert value of type 'Multiplierltem.Type' to expected argument type 'Multiplierltem'
1個目のエラーはなんだ…?
らんだむあくせすこれくしょん…? Identifiableが機能してない系…?
2個目は…型がおかしいよ…??
エラーからの学び UUIDについて掘り下げて理解する
Initializer 'init (_:rowContent:)' requires that 'Multiplierltem' conform to'RandomAccessCollection'
参考にしたサンプルコードをいろいろ見返してみて、盛大な見落としに気付いた。
import Foundation
よくみたらimport swiftUI
じゃなかった。このフレームワークは一体どちら様…?
えぇ〜っ?!こんなに機能がついて、import Foundation
の1行だけでいいんですか?!お得!!!!
UUIDもこのフレームワークの機能らしい。
そらimportしてない僕が悪いわ、ごめんな。
(でもなんかFoundationの機能とswiftUIの機能が重複してそうなところもあるな…?一旦今は後回しでいいか。)
UUIDについて、おまじない程度の認識で進めてきたけどもう少し掘り下げて知っておく必要も出てきたか…?
var uuid: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
Returns the UUID as bytes
うーんと…?
Getting UUID Values
var uuidString: String
Returns a string created from the UUID, such as “E621E1F8-C36C-495A-93FC-0C247A3E6E5F”
生成物みて気付いた。見たことあるぞこれ!!!
あーーこれUUID君の生成物か!!なるほどね!!!!一意に定める値!!あーー!!!あーーー!!!
UUID君、javascriptとかでも動的な要素つけるのに使われてるやつだ!!!!
要素指定する時にすっごい厄介なやつだ!!!!!
君だったのか!!!!
struct ContentView: View {
let multiplier: MultiplierItem
var body: some View {
NavigationView {
List(multipliers) {
//この下の行でまだエラー吐く
in NavigationLink(destination: MultiplierView(multiplier: MultiplierItem)) {
Text(multiplier.title)
.foregroundColor(Color.gray)
}
}
}
}
}
よし、次はこいつらを倒すか…
エラーからの学び コロンの前後に配置するもの
関数の呼び出しだけなんか変なんだよな。
コロンは馴染みがあまりなくてピンと来ないから、構造体の定義とかするときはときはなんとなく「As」として読み替えてた。
struct MultiplierItem: Identifier
//「構造体」の「マルチプライヤーアイテム」は「Identifier」です
みたいな。
でも引数の中で出てくるコロンはこんな感じに読めないんだよな。
関数まだ使ってないけどこんな感じ…
func(ID: "12345"){}
//関数に「ID」である「12345」を渡す
たぶん、宣言するときのコロンと引数として渡す時のコロンが、僕の中でごっちゃになってるんだよな。
一旦整理してみた。
文脈 | コロンの意味 | 左側 | 右側 | 例文 |
---|---|---|---|---|
型宣言 | 変数の型を示す | 識別子(名前) | 型 | (宣言)「識別子名」は「型」です |
構造体・クラス定義 | 継承・準拠 | 作る型名 | 親やプロトコル | 「型名」は「プロトコル」に準拠します |
関数定義・呼び出し | 引数と値の対応 | 引数名(ラベル) | 渡す値 | 関数を使うので「引数名」として「値」を渡します |
どこにいるコロンなのかで分けて頭に入れないといけないな。
というわけで
NavigationLink(destination: MultiplierView(multiplier: MultiplierItem)) {}
MultiplierView(multiplier: MultiplierItem)
がおかしいんだ。
MultiplierViewに「MultiplierItem」として「multiplier」を渡します
が正しいから
NavigationLink(destination: MultiplierView(MultiplierItem: multiplier)) {}
こうだ!!
……変わらず。
Listの使い方が悪いんかな…?
NavigationViewのDuplicatedについに気付く
必死に探しまくってたら、次の記事に出会った僕。
iOS16からはDeprecatedとなって置き換わったのがこのNavigationStackです
えっ……えっ?!?!置き換わっ…
ドキュメントは?!?!
おい!!真っ赤っかやんけ!!!!!!!NavigationLinkの方しかドキュメント読んでなかったわ!!!!!!
NavigationLinkについて調べてた時によくNavigationStackって見かけてそっ閉じしてたんだよね僕!だからかーーー!!!!あーー!!!
まぁ…一旦みなかったことにして、一度綺麗に仕上げてからNavigationStackに置き換えていこう…うん…そうしよう…
MyApp側のエラーを処理する
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
//Missing argument for parameter 'multiplier' in call
}
}
}
エラー文が「ちょっと男子ィ〜!!multiplier渡してあげないとContentviewこまってるじゃん!!」って言ってるんだよな…なんだこれ…
あ、もしかして。
struct ContentView: View {
let multiplier: MultiplierItem //なんやこいつ
var body: some View {
NavigationView {
List(multipliers) {multiplier
in NavigationLink(destination: MultiplierView(multiplier: multiplier)) {
Text(multiplier.title)
.foregroundColor(Color.gray)
}
}
}
}
}
なんかよくわからんところによく分からんもの宣言してるせいで引数として受け取ってないし値もないんですが?って怒られてる?!?!
なるほどな。値が何もセットされてない変数ってのが存在しない世界線なのか。
不要な1行だったので消した。
あっ?!待って?!プレビューが仕事してる!!!!!しかもちょうそれっぽい!!!!!
出来上がったものがこちらです
import SwiftUI
import Foundation
struct MultiplierItem: Identifiable {
let num: Int
let title: String
let comment: String
let id = UUID()
}
let multipliers = [
MultiplierItem(num: 3, title: "3の倍数数える君", comment: "3の倍数やで〜"),
MultiplierItem(num: 7, title: "7の倍数数える君", comment: "7の倍数やで〜")
]
struct ContentView: View {
var body: some View {
NavigationView {
List(multipliers) {multiplier
in NavigationLink(destination: MultiplierView(multiplier: multiplier)) {
Text(multiplier.title)
.foregroundColor(Color.gray)
}
}
}
}
}
struct MultiplierView: View{
@State private var count :Int = 0
@State private var displaymsg :String = ""
let multiplier: MultiplierItem
var body: some View{
VStack{
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Spacer()
Text(multiplier.title)
Spacer()
Text(displaymsg)
Text(String(count))
.font(.largeTitle)
Button("count") {
withAnimation{
count += 1
if(count % multiplier.num == 0){
displaymsg = multiplier.comment
}else {
displaymsg = ""
}
}
}
.buttonStyle(.bordered)
.font(.title)
Spacer()
Button("reset"){
withAnimation{
count = 0
displaymsg = ""
}
}
.buttonStyle(.bordered)
.font(.title2)
Spacer()
Spacer()
}
}
}
初めてショートカットでGIF変換してみたけど画質死んでない???
なんとなく、ぽちぽちしたら3と6の時にコメントでてるなーが伝わればいいか…
GIF変換もちょっと調べておきます。
わーいなんかうまいこといったー!!
今回の学びまとめ
変数、定数: ローワーキャメルケース
型: キャメルケース
コロンは文脈ごとでいい感じに読み換えるべし。
UUID君はjavascriptとかでも見かけるあの子
Foundationフレームワークをimportして使おうね。
NavigationViewはDeprecated。
NavigationStackを使おうね。
Viewなどの中で変数を宣言したとき、引数の中身が空の状態が許されない
let num :Int = 0 //このタイミングで値をセットしてるからよい
let num :int //値がセットされてないので引数
次回の展望
- NavigationViewで構成したけど、NavigationStackに置き換える
- Listのプロパティをもう少し見てみて、もうちょい充実させる