はじめに
この記事は、なんとなく面白そうだからiPhoneアプリ作ってみたいなと思った営業職の私が、ChatGPTさんを相棒に初アプリを完成させるまでの工程を振り返ったものです。
たぶんお見苦しいところもあると思いますが、「素人ってこの辺つまづくのね」と思っていただけたり、「SwiftUI初心者仲間みっけ!」と読み物として読んでいただけたりすれば幸いです。
実際に作ったアプリの動作動画
自分のiPhoneだけで機能が完結して、かつデータの保存機能がいらないアプリは比較的難易度が低いようだったので、「こういうアプリ欲しいとかある?」と妻に聞いたところ、
「トイレットペーパーやティッシュ買うときに、どれが割安なのか知りたい」と意見をくれたので、作ってみることにしました。
(調べたら当然先行アプリあったんですが笑)
勉強に用いた教材(動画、本など)
1.Youtube動画
https://youtu.be/EHdAqVVzAIE?si=Lm_6J42x1SrGdD82
Rikuto Satoさんの動画、めちゃくちゃわかりやすかったです。
Udemyで初心者向けの動画も買って見たんですが、Rikuto SatoさんがYoutubeで出している動画でと範囲がほとんど一緒だったので、まずはRikuto Satoさんの動画でよくね?と思ってます。
(*今後さらに学びを深めていくためにUdemyでいくつか買おうとは思ってます)
また、スタンス面でも同じくRikuto Satoさんが上げているこちらの動画が参考になりました。
https://youtu.be/n47zTmvanJA?si=HTxWouHbTxUq3ibt
基本的な文法、作り方を学んだら、まず1本簡単なアプリをリリースしてみる、という点に納得しましたので、一旦座学を切り上げて1本アプリ作ってみることにしました。結果、受け身で情報を受けていたり、軽い練習問題を解くより学びが多かったと感じましたし、なによりできあがりに近づいていっている感が楽しかったです!独学を続ける上で楽しさってめっちゃ重要ですね。
2.参考書
【Xcode14/Swift5】SwiftUIで始めるiPhoneアプリ開発入門講座: 簡単なアプリを作りながらSwiftUIに慣れよう Kindle版
Rikuto Satoさんの動画がわかりやすかったので、Amazonにでている本も買いました(unlimitedで読める上、unlimitedが3ヶ月99円だったので笑)
全20講座のうち、10講座はyoutubeで無料で公開されていて、後半10講座は本に載ってる、という作りです。
参考書の中には3000円とかのもありますが、それはまた後々でいいかなと思いました。
1000円なので内容が充実しているとは正直思わないですし、誤字も若干気になりますが、私レベルの初心者には一旦これで必要十分かなって気がしました笑
3.デザイン関係参考書 *一旦アプリを作ってみたら、あまりにもクソダサデザインになってしまったので、以前カラーリングについた本読んだなーと思い出し、それをデザインの参考にしました。クソダサデザインからイマイチアカ抜けてないデザインくらいになったかと思います笑
4.Udemy講座
https://www.udemy.com/share/10a0s23@yrxBuStu7NxJMI-n7ocbuoRonQEYrSN6mkfnuESsDbgQ1iZ6umdGYOEbyw3P7pMF/
内容としてはRikuto Satoさんの内容と重複してる部分もありますが、扱っていない内容も結構含まれているので、おすすめです。
ググったり、ChatGPTに提案してもらった箇所
プログラムの基本中の基本は上記動画、本で学び、あとは適宜ググったり、ChatGPT先生に質問してプログラム修正案をもらったりしました。
ChatGPTは無料版を利用しまし、以下のような箇所でアドバイスもらいました。
最安商品が2つ以上ある場合の具体的処理方法
最初、辞書型に[1円あたりの量、商品名]を入れてMax関数で商品名を呼び出せばいいんじゃね?と考えたんですが(辞書をMax関数使うことで最大値のキー取り出せることはググりました)、同数が2つ以上ある場合うまく機能しなかったので、ChatGPTさんに泣きつきました。
入力したプロンプト
swiftUIで3つ以上の数字を比較し、最大の数を決定する式の例を書いてください
最大値が同じ数の場合、両方のキーを取り出したいのだけどどうすべき?
ChatGPTの返答
最大の値が同じ数の場合、その数に対応するすべてのキーを取得するには、以下のように対応できます。この場合、辞書内の全てのエントリを走査し、最大の値に対応するキーを収集します。
import SwiftUI
struct ContentView: View {
var numberDictionary: [String: Int] = ["A": 10, "B": 5, "C": 8, "D": 15, "E": 15, "F": 3, "G": 20, "H": 20]
var body: some View {
let maxKeysInfo = findMaxKeysInfo(dictionary: numberDictionary)
return VStack {
Text("最大の数は: \(maxKeysInfo.maxNumber)")
Text("最大の数が現れるキー: \(maxKeysInfo.keys)")
}
}
func findMaxKeysInfo(dictionary: [String: Int]) -> (maxNumber: Int, keys: [String]) {
guard let firstEntry = dictionary.first else {
return (0, [])
}
// 辞書内の全てのエントリを走査し、最大の値に対応するキーを収集
let maxKeysInfo = dictionary.reduce((firstEntry.key, [String]())) { (result, entry) in
let (maxKey, maxKeys) = result
let (_, value) = entry
if value > dictionary[maxKey]! {
// より大きな値が見つかった場合、新しいキーで配列を初期化
return (entry.key, [entry.key])
} else if value == dictionary[maxKey]! {
// 同じ値が見つかった場合、既存のキーに追加
return (maxKey, maxKeys + [entry.key])
} else {
// それ以外の場合は変更なし
return result
}
}
return maxKeysInfo
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
この例では、findMaxKeysInfo
関数が最大の数とその数に対応する全てのキーを収集しています。プレビューには最大の数とその数に対応するキーが表示されます。例えば、上記の辞書の場合、結果は次のようになります。
最大の数は: 20
最大の数が現れるキー: ["G", "H"]
このようにして、最大の数が同じ場合に、それに対応する全てのキーを取得できます。
こちらを一旦playgroundにコピペしてみて、辞書名とかを整えたのちにプログラムの方に入れ込みました。
最安商品が2つ以上ある場合のキーのソート方法
シュミレーターをいじっていたら、
「一番安い商品は商品A、商品B」と表示して欲しいところ、
「一番安い商品は商品B、商品A」
というようにアルファベットが整ってない表示をする時がありました。あまり気持ちのいい見た目でなかったので、ソート方法を調べました。
入力したプロンプト
・costPerformanceDictionaryのキーをソートしてほしい
・Value of tuple type '(maxNumber: Double, keys: [String])' has no member 'sorted' のエラーがでます
*何回かXcodeでエラーが出てたので、下記のような指示も出しています。
ChatGPTの返答
タプルの場合、ソートの対象はタプル自体でなく、タプルの中の keys
部分です。以下は、キーでソートするための例です。
let maxKeysInfo = findMaxKeysInfo(dictionary: costPerformanceDictionary)
let sortedKeys = maxKeysInfo.keys.sorted()
for key in sortedKeys {
print(key)
}
このコードでは、maxKeysInfo.keys
でタプルから keys
部分を取り出し、それを sorted()
メソッドでソートしています。これにより、キーがソートされた状態で sortedKeys
に格納されます。
こちらも一旦playgroundにコピペしてみて、きちんとkeyがアルファベット昇順にソートされたのを確認したので、プログラムに入れ込みました。
最安商品は入力したViewの色が変わるようにする、三項演算子を使い、色はRGBで指定する
どれが安いのかわかりやすくするため、安かったViewを色変えるようにする機能をつけたくなりました。 ただ、これまでの流れの通り、
・最安商品の名前を計算で出して、 商品名 == 最安商品名 となる場合、という条件分岐でなく、商品名が最安商品を集めた辞書のキーと一致する場合という条件分岐が必要(商品A,B両方最安、となる場合があるため)
・カラーリングにもこだわってみたかったので、RGBカラーで指定したかった
という背景があるので、ちょっと複雑になりました。(本当はRGBをletとか名前をつけて宣言して、それを入れる方がクールなんでしょうけどね。。。)
Color(
red: maxKeysInfo.keys.contains(productA) ? 242/255 : 227 / 255.0,
green: maxKeysInfo.keys.contains(productA) ? 161/255 : 223 / 255.0,
blue: maxKeysInfo.keys.contains(productA) ? 166/255 : 220 / 255.0
)
すみませんこちらはプロンプトがしっちゃかめっちゃかになってたので、結果だけを。
maxKeysInfoのキーにproductAが含まれてたら前半の(red:242/255、green:161/255、blue:166/255)の色が適用され、含まれていなければ後ろの方が適用されます。
モーダル遷移画面に2つ以上の変数を持っていく場合
詳細ボタンを押すと、モーダル遷移画面に移り、商品AからDまでの1円あたりの金額詳細を表示したくなりました。
ただ、教材には1つの変数を@Bindingと$で結合させることは載ってましたが、2つ以上だとどうすればいいのかわからなくなりました。
ChatGPTの返答
SecondViewに productACostPerformance
以外の値をバインドする場合、SecondViewのプロパティもそれに合わせて変更する必要があります。以下に修正したコード例を示します。
struct SecondView: View {
@Binding var productACostPerformance: Double
@Binding var productBCostPerformance: Double
@Binding var productCCostPerformance: Double
@Binding var productDCostPerformance: Double
var body: some View {
VStack {
Text("商品Aは1円あたり\(productACostPerformance)")
Text("商品Bは1円あたり\(productBCostPerformance)")
Text("商品Cは1円あたり\(productCCostPerformance)")
Text("商品Dは1円あたり\(productDCostPerformance)")
}
}
}
そして、ContentViewでこのビューを呼び出す際に、SecondView
のインスタンスを作成する際に必要なバインディングを指定します。具体的には、以下のように修正します。
Button("詳細") {
isShowDetail = true
}
.sheet(isPresented: $isShowDetail) {
SecondView(
productACostPerformance: $productACostPerformance,
productBCostPerformance: $productBCostPerformance,
productCCostPerformance: $productCCostPerformance,
productDCostPerformance: $productDCostPerformance
)
}
ここで、productACostPerformance
, productBCostPerformance
, productCCostPerformance
, productDCostPerformance
は ContentView の @State
プロパティとして定義されているものと仮定しています。もしまだそれらのプロパティが存在していない場合は、適切に定義してください。
こちらで無事エラー解決、モーダル遷移に変数を持っていくことができました。
残課題
1.TextFieldに0スタートの数字を入れることができてしまう。
.keyboardType(.numberPad)でしているのでエラーは起きていないものの、例えば0111と打ったとき、111とならず0111として表示されてしまいます。なんかスッキリしないので修正したいものの、探した範囲、およびChatGPTさんに聞いた限りよさげな回答はきませんでした。だれかよい方法知っていましたらお教えください。
余談ですが、上記悩みをChatGPTさんに相談したら
.onChange(of: inputValue) { newValue in
// 入力時に0を防ぐ
self.inputValue = self.inputValue.trimmingCharacters(in: CharacterSet(charactersIn: "0"))
}
上記コードを提案され、組み込んでみたところ0の入力がそもそもできなくなりました笑
まぁたしかに0111、みたいな数字はできないけども笑
2.リファクタリングが全然できていない
全体的なViewを作るまではenumを使うなどしてリファクタリングもできていたんですが、最安値を辞書型に入れる、みたいな処理をする際に辞書の更新がうまくいかず、結局リファクタリングを解いたらうまくいったため、リファクタリングせずに現在に至っています。
enum でcase A,B,C,Dという風に列挙し、商品名A、priceA・・・という風にそれぞれのアルファベットに紐づいた変数も一気に作る、みたいなことすればよかったのかな?
コード読みにくいのが難点ですし、ここで満足したらレベルアップしなそうなので、一旦アプリをアップロードしたら徐々に改善していこうとおもいます。
所感
やはり実際にアプリを作ってみる過程が一番学習が進んだように感じます。
ChatGPTは答えポイとくれるし、自分にない引き出しからじゃんじゃん答えらしいものくれるのでめちゃくちゃ頼りになるんですが、それをコピペしてもうまくいかないこともぼちぼちありました。
なにより、教えてくれたプログラムの意味合いをちゃんと自分で紐解かないと全くスキルとして定着しなそうだなーと感じました。
ただ、プログラムやっててうまくいかない、ググっても近いのが出てこない というときにはめちゃくちゃ頼りになります。
一旦アイコンまで作って、AppStoreにアップロードするところまでやってみてから、プログラム修正と、次のアプリ作りに進みたいと思います。
次こんなことすればいいよーとか、アドバイスありましたらぜひよろしくお願いします!