見出し画像

iPhone アプリを自分でつくる 14.


今回の内容: 数値の入力 1.

今回は数値を入力して計算式を実行します

Project の作成

Project の名前を VisCalc として 作成していきます。
Project の作成方法は第8回 「SwiftUI の準備をする」を参照願います。


アプリの概要

下のようなアプリを作成します。

仕様
数値を入力するボックスが2箇所ある①
ボックスにフォーカスするとナンバーキーが表示されて入力できる②
2箇所に数値を入力した後に計算記号をタップすると計算結果が表示される③
Memory ボタンをタップすると上に数値が表示される④
次の計算を実行して必要に応じMemory ボタンで計算結果を追加する⑤


注意: 過去にこの練習用アプリと類似した名前を語る表計算ソフトがあったようですが、当方とは一切関わりがございませんのでご注意ください。


アプリの設定

アプリの設定でMinimum Deployments を17.x にしてください。
設定方法の詳細は第9回「Navigator について」の項を確認願います。


作成を進めるにあたっての方針

今回のアプリは計算機ですので、同じような部品が多く並びます。
つまり足す、引く、掛ける、割るなどの計算と結果を表示する部品がコードでずらっと並んできます。
そこにデザインを設定するコードもいっしょにすると見通しが悪く読みにくくなります。そこで、
①骨組みとなる計算式を中心に最後まで作成する
②骨格の段階でファイルを別にして、そこで各部のデザインを見る
③全体の調整を行う。
このようなイメージを持って進めていきたいと思います。


TextField テキストフィールド

数値も文字の入力と同じようにTextField() テキストフィールド で入力するボックスを画面に表示させます。
文字の時にTextField をセットしたようにTextFieldの文字を途中まで打って引数のパターンを確認します。
その中でvalue を引数にとるものをクリックします。①
すると

TextField( , value: , format: .number)


と表示されています。②
イニシャライズの仕方は引数ラベルを使用しないでLocalizedStringKey(String と考えて結構です)とvalueラベルでバインディング<V> 渡すということになります。 <V>というのはValue 型を表しています。


バインディングを受け止めるため @State var amount をセットしますが
2つの数値入力フィールドを作成するので、amountA、amountB としましょう。

プロパティに private をつけていますが、これはProject にある他の型(構造体やクラスなど)から誤って変更しないためのものです。
この後、構造体を分けていくと構造体にやり取りをしてもらうことになるので、private を一部取っていきます。
private は(私自身が)意識するために書いています。書きながらエラーがでたら、「ああ、ここにはつけれないな」とか理解できるのでつけれると思ったら付けるようにはしています。
が、付けるのを忘れたりとか(面倒くさくなってしまったりとか)で、きちんとした対応になっていないかもしれません。
統一感がないと思いますがどうぞよろしくお願いします。

import SwiftUI

struct ContentView: View {
    @State private var amountA: Double = 0.0
    @State private var amountB: Double = 0.0

    var body: some View {
        TextField("Number", value: $amountA, format: .number)
        TextField("Number", value: $amountB, format: .number)
    }
}

#Preview {
    ContentView()
}

そうすると画面の左端に小さく 0の表示がされました。

TextField() の引数にvalue を使用して format: .number としています。
もうひとつの方法として、ここを文字で受け付けてそれを数値に直して計算して再度文字として表示させるのもありですが、その場合必要な処理が多くなります。
文字を数値に変換するときには(Swift がエラーを避けるため)Optional 型で返すのでその対応も必要です。(逆方向はOptional ではありませんが)

今回の方法は、テキストフィールドの枠には文字を記入することはできてもenter すると、はじかれるのでその処理を作成するが必要ない、ということになります。
またtext: 引数の場合は、空文字やスペースが入ってしまいましたが、数値の場合はカラ数値のようなものはありません。0を消してenter しても 0が入るので、その対応も必要ありません。(それが気になるときもありますが)

Quick Help を見てみると、TextField(value: , format: ) を使用してユーザーネーム・フィールドを設定し、Stringが入るのだけれど format: をカスタムフォーマットを効かせて適正なフォーマットの文字が入ったかチェックする機能をもたせる例が書かれています。

表示されたものだとデザインがイメージし難いので、少し調整してみます。

    var body: some View {
        TextField("Number", value: $amountA, format: .number)
            .textFieldStyle(.roundedBorder)
            .font(.title)
            .frame(width: 300)
            .padding(8)
        
        TextField("Number", value: $amountB, format: .number)
            .textFieldStyle(.roundedBorder)
            .font(.title)
            .frame(width: 300)
            .padding(8)
    }
}

\() ストリングインターポレーション


今度はこれが数字を受け付けるのか確認のためにも表示してみます。
bodyの内容に下記のようにText() 表示を加えました。

        VStack {
            Text("\(amountA)")
                .font(.title.weight(.bold))
                .frame(width: 300)
            
            Text("\(amountB)")
                .font(.title.weight(.bold))
                .frame(width: 300)
            
            TextField("Number", value: $amountA, format: .number)
                .textFieldStyle(.roundedBorder)
                .font(.title)
                .frame(width: 300)
                .padding(8)

            TextField("Number", value: $amountB, format: .number)
                .textFieldStyle(.roundedBorder)
                .font(.title)
                .frame(width: 300)
                .padding(8)
        }

Text("\(amountA)") は文字列 "  " の中に \() を使用して変数を表示しています。
"\n" などバックスペースを使用して意味を持たせる方法をエスケープシーケンスといいますが、"\(変数)" のように文字列リテラル(" ")の中に変数を表現することをString Interpolation  ストリングインターポレーションといいます。
各言語は書きやすくかつ読みやすいString Interpolationの方法をそれぞれ工夫しています
少し覗いてみるのも面白いですよ。


Text("\(double)")

Text(String(double))

Text("\(amountA)") のようにしたのは、Text() は引数を文字列しか取らないためです。
print() では、()の内容を Any 型に変換(何型でもAny型に適合していれば受けつけてくれる:つまり何型でも良い)してくれるので何でも表示してくれましたが、TextではString 型しかとれません。

Text表示を見てみると表示は6桁の小数点で表されています。

さすがにこれでは困ります。
ではSwift で桁数を調整すれば良いでしょうか?
Text(String(amountA)) でどうでしょうか。②
良い感じにはなりましたが、カンマ区切りも欲しいところです。

            Text(String(amountA))
                .font(.title)
                .frame(width: 300)
            
            Text(String(amountB))
                .font(.title)
                .frame(width: 300)

今度はNumberFormatter クラスを使用してみます。
SetFormat構造体をつくり、.formatNum() メソッドでフォーマットされたString(数値)を受け取り Text(amountAString) で桁数も表示させることができました。③

struct SetFormat {
    var number: Double
    
    func formatNum() -> String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        if let formattedNumber = formatter.string(from: NSNumber(value: number)) {
            return formattedNumber
        }
        return "Not number"
    }
}


// ContentView {  var body 内

            let amountAString = SetFormat(number: amountA).formatNum()
                        
            Text(amountAString)
                .font(.title)
                .frame(width: 300)

NumberFormatter 

Locale


上記で使用している NumberFormatter クラスは NSNumber オブジェクトを扱うものである、とQuick Help で確認できます。
この方法を使うとカスタマイズ性が高めることができます。
例えば、端末に設定されている地域に合わせて通貨記号やカンマ記号などを変えることができます。
この国や地域に合った設定のことをLocale(ロケール)と呼びます。
下にロケール設定の例を記入しました。
右のCanvas 表示の1行目は日本、2行目はドイツです。
通貨で表示される桁数が変わります。日本は小数点一桁で四捨五入、
ドイツ(ユーロ)は3桁目を四捨五入しているようです。
またドイツでは日本の. (小数点、ピリオド)は  , (カンマ)のように反対で表されるのが確認できますでしょうか。

私たちの端末は、国:日本、言語:日本語 などのロケールが登録されています。
国によっては国:カナダ、言語:英語またはフランス語 などのように選択が複数にわたる場合があります。
そのために、ロケール設定では ja_JP などの簡単な文字でいろいろな選択ができるようになっています。
またText() の引数の一つは LocalizedStringKeyとなっていますが、ProjectのNational(国)のページのところに "pen" : "鉛筆" と書いて置くと、端末が日本語設定の場合は'pen' を'鉛筆' と表示させるような多国語に対応させることもできます。
(ProjectページのLocalization 項にファイルを持たせることでできます)
個人のアプリで通常はロケールを意識することは少ないかもしれませんが、外国では日付や時間の表記方法がさまざまあったりなど、日本以外でも見てもらいたい場合には確認が必要かもしれません。


double.formatted()

String(format: "%.3f")

え〜、何でしたでしょうか?

そうです、最終的に数値表記をどうするかですね。もちろん。
今回は、下記のようにします。④

            Text(amountA.formatted())
                .font(.title)
                .frame(width: 300)
            
            Text(amountB.formatted())
                .font(.title)
                .frame(width: 300)


他にも  String(format: "%.3f") のような小数点設定があったり、少しづつ表現が違ってくるので試してみると良いかと思います。
そして、この簡単なコードでカンマ表示と小数点表示、0のときは 0の表示になるという狙った表示が得られたのでこれにしました。

数字の配置関係はあとで対応します。


計算結果を受け止めるプロパティを作成します。

    @State private var calculatedAmount: Double = 0.0

次は下の方に演算子を横並びにして、計算できるようにします。

            HStack {
                Button {
                    calculatedAmount = amountA + amountB
                } label: {
                    Image(systemName: "plus")
                }
                Button {
                    calculatedAmount = amountA - amountB
                } label: {
                    Image(systemName: "minus")
                }
                Button {
                    calculatedAmount = amountA * amountB
                } label: {
                    Image(systemName: "multiply")
                }
                Button {
                        calculatedAmount = amountA / amountB
                } label: {
                    Image(systemName: "divide")
                }
            }

そしてText(amountA)、Text(amountB) の下にText(calculatedAmount) をつけて下記のようになりました。

import SwiftUI

struct ContentView: View {
    @State private var amountA: Double = 0.0
    @State private var amountB: Double = 0.0
    
    @State private var calculatedAmount: Double = 0.0

    var body: some View {
        VStack {
            Text(amountA.formatted())
                .font(.title)
                .frame(width: 300)
            
            Text(amountB.formatted())
                .font(.title)
                .frame(width: 300)
            
            Text(calculatedAmount.formatted())
                .font(.largeTitle)
            
            TextField("Number", value: $amountA, format: .number)
                .textFieldStyle(.roundedBorder)
                .font(.title)
                .frame(width: 300)
                .padding(8)

            TextField("Number", value: $amountB, format: .number)
                .textFieldStyle(.roundedBorder)
                .font(.title)
                .frame(width: 300)
                .padding(8)
            
            HStack {
                Button {
                    calculatedAmount = amountA + amountB
                } label: {
                    Image(systemName: "plus")
                }
                Button {
                    calculatedAmount = amountA - amountB
                } label: {
                    Image(systemName: "minus")
                }
                Button {
                    calculatedAmount = amountA * amountB
                } label: {
                    Image(systemName: "multiply")
                }
                Button {
                        calculatedAmount = amountA / amountB
                } label: {
                    Image(systemName: "divide")
                }
            }
        }
    }
}

#Preview {
    ContentView()
}

これで骨組みの70%ぐらいできました。


計算もしてみました。計算も問題ないようです。

と、ここで問題が見つかりました。

0での割り算で無限大の記号が出てしまいました。
基本的には「0では割れない」ということになっていると思いますので、
計算式を修正します。

                Button {
                    if amountB != 0 {
                        calculatedAmount = amountA / amountB
                    }
                } label: {
                    Image(systemName: "divide")
                }

これで、もし amountB が0 でなかったら計算する。それ以外のとき(つまりamountB が0 のとき)ではなにもしない(つまり0のまま)と設定できました。
このようなケースでは、guard を使うと 「0は嫌、もしその時は退出するから」という方法も良いですね。

                Button {
                    guard amountB != 0 else { return }
                        calculatedAmount = amountA / amountB
                } label: {
                    Image(systemName: "divide")
                }

単にこれだけの計算を適当に並べるだけでもコードがずらっと並んでしまい、この先が思いやられます。

なので、まとめることができるものは、まとめていきましょう。
まずテキストフィールドをひとつの構造体にして、バインディングプロパティを受け取るようにしました。body 内は先ほどのコピペをして受け取る変数を$amount にしています。

struct NumberField: View {
    @Binding var amount: Double
    var body: some View {
        TextField("Number", value: $amount, format: .number)
            .textFieldStyle(.roundedBorder)
            .font(.title)
            .frame(width: 300)
            .padding(8)
    }
}

そしてContentView のbody 内ではNumberField(amount: $amountA) のようにしました。

// ContentView 
            NumberField(amount: $amountA)
            
            NumberField(amount: $amountB)

NumberFieldファイル作成

そうしてNumberField構造体を別ファイルに移します。
ファイル名はNumberField、SwiftUI でファイルを作成します。


移す方法が分からない方は第12回「ファイルを分割する」項をご確認ください。
#Previewには引数を.constant で適当な値を設定します。

#Preview {
    NumberField(amount: .constant(123))
}

いまPreview を設定したところですが、今度はPreview にContentView を表示させましょう。下記のようにしてください。

#Preview {
    ContentView()
//    NumberField(amount: .constant(123))
}

そうするとここで全体のview を確認しながらテキストフィールドをデザインすることができます。

まず数値を後ろの方に移動したいと思います。
.multilineTextAlignment(.trailing) で移動することができました。下図①
フィールドの幅を狭くして他のview が横に入るようにしました。下図②

        TextField("Number", value: $amount, format: .number)
            .textFieldStyle(.roundedBorder)
            .font(.title)
            .multilineTextAlignment(.trailing)
            .frame(width: 200)
            .padding(8)

これで数値をいれてみます。上のフィールドには 111111 を下には888888 と入れてみます。そうすると、文字幅の関係で桁数が違って見えてしまいます。下図③


そのため、フォントは       .font(.title.monospacedDigit()) を使用します。④
monospacedDigit() はフォントサイズについてフォントを等幅フォント(とうはばフォント)にする修飾子です。デジタル時計表示やカウント表示などで数字が変化する場合にぴょこぴょこしないように使われます。     

        TextField("Number", value: $amount, format: .number)
            .font(.title.monospacedDigit())
            .textFieldStyle(.roundedBorder)
            .font(.title)
            .multilineTextAlignment(.trailing)
            .frame(width: 200)
            .padding(8)

CalculationsView ファイル作成

次は上にセットしてある3つの数値の部分をひとまとまりにします。
CalculationsView ファイルとします。上図⑤
今度はプロパティが3つになりましたが、先ほどのテキストフィールドと違い、数値をもらって自分が更新されるだけとなります。自分自身の中で変更したり、ContentView に返送したりしない通常の変数(stored プロパティ)でOKです。
Preview にも 適当な数値を入力していったんデザインを見てください。
こちらも先ほどのような .constant() は必要なくなりますね。

import SwiftUI

struct CalculationsView: View {
    var amountA: Double
    var amountB: Double
    var calculatedAmount: Double

    var body: some View {
        Text(amountA.formatted())
            .font(.title)
            .frame(width: 300)
        
        Text(amountB.formatted())
            .font(.title)
            .frame(width: 300)
                    
        Text(calculatedAmount.formatted())
            .font(.largeTitle)    }
}

#Preview {
    CalculationsView(amountA: 10, amountB: 20, calculatedAmount: 30)
}

ContentViewにCalculationsView() を記入します。下記および上記⑥

// ContentView
        VStack {
            CalculationsView(amountA: amountA, amountB: amountB, calculatedAmount: calculatedAmount)
            
            NumberField(amount: $amountA)
            
            NumberField(amount: $amountB)
            
            HStack {

CalculationsView のPreviewを ContentView() に修正する

// CalculationsView 
#Preview {
    ContentView()
//    CalculationsView(amountA: 10, amountB: 20, calculatedAmount: 30)
}

では CalculationsView の Text(amountA ~ )と Text(amountB ~ ) のfontを下記のように .monospaceDigit() を追加します。
Text(calculatedAmount ~) はもともとサイズが違うのでそのままでOKです。

        Text(amountA.formatted())
            .font(.title.monospacedDigit())
            .frame(width: 300)
        
        Text(amountB.formatted())
            .font(.title.monospacedDigit())
            .frame(width: 300)

今度は Text(amountB ~) の左側に + - x などの記号を最終的に入れたいので HStack で囲んで Image(systemName: "square") とします。
"square" は仮のイメージです。

        HStack {
            Image(systemName: "square")
                .font(.headline)
                .foregroundStyle(.gray)
                .frame(height: 40)
            
            Text(amountB.formatted())
                .font(.title.monospacedDigit())
                .frame(width: 300)
        }

位置を直しておきます。
またText(amountA ~ ) とText(amountB ~ ) は色を少し落とすためにグレーにしておきます。
また数値表示を右端に寄せておきます。

        Text(amountA.formatted())
            .font(.title.monospacedDigit())
            .foregroundStyle(.gray)
            .frame(width: 290, height: 40, alignment: .trailing)

        HStack {
            Image(systemName: "square")
                .font(.headline)
                .foregroundStyle(.gray)
                .frame(height: 40)
            
            Text(amountB.formatted())
                .font(.title.monospacedDigit())
                .foregroundStyle(.gray)
                .frame(width: 250, height: 40, alignment: .trailing)
        }
        .frame(width: 290, alignment: .trailing)

Text(calculatedAmount ~ ) も右端に寄せます。文字は通常の色、padding() は微調整します。
また上の区分と分けるために Divider() をつけておきます。

                        Divider()
                            .padding(.horizontal)

                        Text(calculatedAmount.formatted())
                            .font(.largeTitle)
                            .frame(width: 290, alignment: .trailing)
                            .padding(8)
                            .padding(.bottom, 16)

ここまでで位置が少しまとまってきました。


OperatorsView ファイル作成


次は演算記号をファイルにまとめます。

ボタンは計算式で計算して結果をContentView に渡すのでバインディングさせます。②
Preview は仮に constant() で入力します。③

import SwiftUI

struct OperatorsView: View {
    @Binding var amountA: Double
    @Binding var amountB: Double
    @Binding var calculatedAmount: Double
    
    var body: some View {
        HStack {
            Button {
                calculatedAmount = amountA + amountB
            } label: {
                Image(systemName: "plus")
            }
            Button {
                calculatedAmount = amountA - amountB
            } label: {
                Image(systemName: "minus")
            }
            Button {
                calculatedAmount = amountA * amountB
            } label: {
                Image(systemName: "multiply")
            }
            Button {
                guard amountB != 0 else { return }
                    calculatedAmount = amountA / amountB
            } label: {
                Image(systemName: "divide")
            }
        }
    }
}

#Preview {
    OperatorsView(amountA: .constant(5), amountB: .constant(2), calculatedAmount: .constant(10))
}

Preview を下のコードのように ContentView に切り替える。

// OperatorsView 
#Preview {
    ContentView()
//    OperatorsView(amountA: .constant(5), amountB: .constant(2), calculatedAmount: .constant(10))
}

Button のアクションと修飾まで記入しました。
最後にまとめられるかもしれませんが、形を整えるところまでやってみました。

import SwiftUI

struct OperatorsView: View {
    @Binding var amountA: Double
    @Binding var amountB: Double
    @Binding var calculatedAmount: Double
    
    var body: some View {
        HStack {
            Button {
                calculatedAmount = amountA + amountB
            } label: {
                Image(systemName: "plus")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40)
                    .background(.blue)
                    .clipShape(Circle())
                    .shadow(radius: 2.0)
            }
            .padding(8)

            Button {
                calculatedAmount = amountA - amountB
            } label: {
                Image(systemName: "minus")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40)
                    .background(.red)
                    .clipShape(Circle())
                    .shadow(radius: 2.0)
            }
            .padding(8)

            Button {
                calculatedAmount = amountA * amountB
            } label: {
                Image(systemName: "multiply")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40, height: 40)
                    .background(.green)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .shadow(radius: 2.0)
            }
            .padding(8)

            Button {
                guard amountB != 0 else { return }
                    calculatedAmount = amountA / amountB
            } label: {
                Image(systemName: "divide")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40, height: 40)
                    .background(.orange)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .shadow(radius: 2.0)
            }
            .padding(8)
        }
    }
}

#Preview {
    ContentView()
//    OperatorsView(amountA: .constant(5), amountB: .constant(2), calculatedAmount: .constant(10))
}

Toggle

ContentView ファイルに戻ります。
NumberField() の左に Cボタン(クリアボタン)をおきます。
それぞれのフィールドをクリアするボタンです。
(Canvas で実行するとクリアのタイミングがうまくいかないかもしれませんが、
 ある程度デザインが決まったらシミュレーターを使用します。

    var body: some View {
        VStack {
            CalculationsView(amountA: amountA, amountB: amountB, calculatedAmount: calculatedAmount)
            
            HStack {
                Button {
                    amountA = 0
                    calculatedAmount = 0
                } label: {
                    Text("C")
                }
                NumberField(amount: $amountA)
            }
            HStack {
                Button {
                    amountB = 0
                    calculatedAmount = 0
                    
                } label: {
                    Text("C")
                }
                NumberField(amount: $amountB)
            }
            OperatorsView(amountA: $amountA, amountB: $amountB, calculatedAmount: $calculatedAmount)
        }
    }

NumberField の左にもう一つボタンを置きます。
フィールドにある数値のプラス・マイナスを切り替えるトグルボタンです。
まず .isNegativeAプロパティと .isNegativeB プロパティをセットします。

    @State private var isNegativeA = false
    @State private var isNegativeB = false

次に先ほどセットしたクリアボタンの右側にトグルボタンがくるようにします。
このボタンもCanvas だとひっかかりがある感じがします。

            HStack {
                Button {
                    amountA = 0
                    calculatedAmount = 0
                } label: {
                    Text("C")
                }
                
                Button {
                    isNegativeA.toggle()
                    amountA = amountA > 0 ? -(amountA) : abs(amountA)
                } label: {
                    Image(systemName: "minus")
                }
                
                NumberField(amount: $amountA)
            }         
            
            HStack {
                Button {
                    amountB = 0
                    calculatedAmount = 0
                    
                } label: {
                    Text("C")
                }
                
                Button {
                    isNegativeB.toggle()
                    amountB = amountB > 0 ? -(amountB) : abs(amountB)
                } label: {
                    Image(systemName: "minus")
                }
                
                NumberField(amount: $amountB)
            }


まとめ

いかがでしたでしょうか?
今回はTextField() で value: ~ format: というパターンがあるということと、ロケール設定がどういうものかということが理解できれば十分です。
コードについては、わかるところを増やしていければ良いぐらいの感じでOK です。
まだまだ、続きますから!

 次回第15回内容  数値の入力 2. です。
          よろしくお願いします。



今回の最後のタイミングでのコード全文

ContentView ファイル

import SwiftUI

struct ContentView: View {
    @State private var amountA: Double = 0.0
    @State private var amountB: Double = 0.0
    
    @State private var calculatedAmount: Double = 0.0
    @State private var symbol = "square"

    @State private var isNegativeA = false
    @State private var isNegativeB = false
    
    var body: some View {
        VStack {
            CalculationsView(amountA: amountA, amountB: amountB, calculatedAmount: calculatedAmount)
            
            HStack {
                Button {
                    amountA = 0
                    calculatedAmount = 0
                } label: {
                    Text("C")
                }
                
                Button {
                    isNegativeA.toggle()
                    amountA = amountA > 0 ? -(amountA) : abs(amountA)
                } label: {
                    Image(systemName: "minus")
                }
                
                NumberField(amount: $amountA)
            }
                        
            HStack {
                Button {
                    amountB = 0
                    calculatedAmount = 0
                    
                } label: {
                    Text("C")
                }
                
                Button {
                    isNegativeB.toggle()
                    amountB = amountB > 0 ? -(amountB) : abs(amountB)
                } label: {
                    Image(systemName: "minus")
                }
                
                NumberField(amount: $amountB)
            }
            
            OperatorsView(amountA: $amountA, amountB: $amountB, calculatedAmount: $calculatedAmount)
        }
    }
}

#Preview {
    ContentView()
}

NumberField ファイル

import SwiftUI

struct NumberField: View {
    @Binding var amount: Double
    
    var body: some View {
        TextField("Number", value: $amount, format: .number)
            .font(.title.monospacedDigit())
            .textFieldStyle(.roundedBorder)
            .font(.title)                     // font は monospaceDigit() なのでこの .title は消してください。
            .multilineTextAlignment(.trailing)
            .frame(width: 200)
            .padding(8)
    }
}

#Preview {
    ContentView()
//    NumberField(amount: .constant(123))
}

CalculationsView ファイル

import SwiftUI

struct CalculationsView: View {
    var amountA: Double
    var amountB: Double
    var calculatedAmount: Double

    var body: some View {
        Text(amountA.formatted())
            .font(.title.monospacedDigit())
            .foregroundStyle(.gray)
            .frame(width: 290, height: 40, alignment: .trailing)

        HStack {
            Image(systemName: "square")
                .font(.headline)
                .foregroundStyle(.gray)
                .frame(height: 40)
            
            Text(amountB.formatted())
                .font(.title.monospacedDigit())
                .foregroundStyle(.gray)
                .frame(width: 250, height: 40, alignment: .trailing)
        }
        .frame(width: 290, alignment: .trailing)

        Divider()
            .padding(.horizontal)

        Text(calculatedAmount.formatted())
            .font(.largeTitle)
            .frame(width: 290, alignment: .trailing)
            .padding(8)
            .padding(.bottom, 16)
    }
}

#Preview {
    ContentView()
//    CalculationsView(amountA: 10, amountB: 20, calculatedAmount: 30)
}

OperatorsView ファイル

import SwiftUI

struct OperatorsView: View {
    @Binding var amountA: Double
    @Binding var amountB: Double
    @Binding var calculatedAmount: Double
    
    var body: some View {
        HStack {
            Button {
                calculatedAmount = amountA + amountB
            } label: {
                Image(systemName: "plus")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40)
                    .background(.blue)
                    .clipShape(Circle())
                    .shadow(radius: 2.0)
            }
            .padding(8)

            Button {
                calculatedAmount = amountA - amountB
            } label: {
                Image(systemName: "minus")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40)
                    .background(.red)
                    .clipShape(Circle())
                    .shadow(radius: 2.0)
            }
            .padding(8)

            Button {
                calculatedAmount = amountA * amountB
            } label: {
                Image(systemName: "multiply")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40, height: 40)
                    .background(.green)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .shadow(radius: 2.0)
            }
            .padding(8)

            Button {
                guard amountB != 0 else { return }
                    calculatedAmount = amountA / amountB
            } label: {
                Image(systemName: "divide")
                    .font(.title.weight(.bold))
                    .padding()
                    .foregroundStyle(.white)
                    .shadow(radius: 0.5)
                    .frame(width: 40, height: 40)
                    .background(.orange)
                    .clipShape(RoundedRectangle(cornerRadius: 10))
                    .shadow(radius: 2.0)
            }
            .padding(8)
        }
    }
}

#Preview {
    ContentView()
//    OperatorsView(amountA: .constant(5), amountB: .constant(2), calculatedAmount: .constant(10))
}

現段階での表示画面

今回は以上です

いいなと思ったら応援しよう!

MasaOno
サポートいただければ、さらに活動を広げることができます!