質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

3759閲覧

SwiftUI のビュー内における代入

TsukubaDepot

総合スコア5086

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2020/01/09 06:29

前提・実現したいこと

SwiftUI のビュー内での再代入がうまくできません。

self.foo += 1

という記述はコンパイルではエラーがでませんが、別の行でエラーがでてしまいます。
この行をコメントアウトすると、別の行で出ていたエラーもなくなるので、根本的な原因は代入を行っている行ではないかと疑っているところですが、解決方法が見当たりません。

具体的にやりたいことはカレンダーの作成なのですが、ループ内で条件判定を行なった上、フラグを立てたり、変数の値を変えたりしたいと考えています。

アルゴリズム自身は問題がないことを確認済みで、Playgroundで動くことを確認の上、コードを少しづつ移植しているところでした。

代入ではありませんが、ビュー内部で条件分岐や for 文も使えないようなので、SwiftUIの制限なのかとも思っています。

ちなみに、条件分岐は Group {} で囲むことで回避できること、for は ForEach で回避できることを知り、解決できました。

Cでの記述についてはそこそこ理解可能です。C++もコードをざっと眺める程度であれば理解できますが、Swiftは最近学習し始めました。

発生している問題・エラーメッセージ

\'Int\' is not convertible to \'CGFloat\' ---------------------------------------- SchemeBuildError: Failed to build the scheme "PrintMonth" 'Int' is not convertible to 'CGFloat' Compile /Users/.../Documents/Xcode/iOS/PrintMonth/PrintMonth/ContentView.swift: /Users/.../Documents/Xcode/iOS/PrintMonth/PrintMonth/ContentView.swift:132:80: error: 'Int' is not convertible to 'CGFloat' .background(RoundedRectangle(cornerRadius: 5)

該当のソースコード

ソースコードはかなり長いので、該当部分と直接関係ありそうな部分だけ抜粋します。

SwiftUI(Xcode

1struct printMonth: View { 2 @State var year: Int 3 @State var month: Int 4 5 var firstDayFlag: Bool = true 6 var d: Int = 1 7 8 var body: some View { 9 HStack{ 10 ForEach( 0 ..< 7){ week in 11 // 日付を表示するループ 12 VStack{ 13 Group{ 14 if self.firstDayFlag && week < dayOfWeek(year: self.year, month: self.month, day: 1) { 15 Text("") 16 Text("") 17 } else if self.d > getLastDay(year: self.year, month: self.month) { 18 Text("") 19 Text("") 20 } else { 21 // 以下の二行をコメントアウトするとコンパイルが通る 22 self.firstDayFlag = false 23 self.d += 1 24 25 // 日付の表示 26 Text("(row * 7 + week + 1)") 27 .font(.subheadline) 28 .padding(-15) 29 30 // 本文 31 Text("") 32 } 33 } 34 } 35 .foregroundColor(.black) 36 .multilineTextAlignment(.trailing) 37 .frame(width: self.w, height: 50) 38 .background(RoundedRectangle(cornerRadius: 5) // エラーが出る行 39 .frame(width: self.w) 40 .foregroundColor(Color(red: 0.95, green: 0.95, blue: 0.95))) 41 } 42 } 43} 44

試したこと

前述の通り、代入している行を消すとビルドエラーもなくなり、プレビューも正常に表示されます。

補足情報(FW/ツールのバージョンなど)

Xcode 11.3

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

SwiftUIは、エラー原因となる箇所と全然関係なさそうなところにエラーメッセージが表示されるので、原因を調べるのが難しいですが、

swift

1// 以下の二行をコメントアウトするとコンパイルが通る 2self.firstDayFlag = false 3self.d += 1

推察通り、これがエラーの原因だと思います。
この行にはいくつかの問題があります。

まず、HStack/VStack/Groupの{ 〜 }の中にはSwiftの文を自由に書くことができません。基本的にこの中に書けるのは最大10個までのViewを並べるか、表示するViewをif文の条件で切り分けることだけです。詳しくは@ViewBuilderや関数ビルダの説明を読んでください。

Buttonのaction引数やonAppearのようなクロージャの中であれば、自由にSwiftの構文を書くことができます。ただし、クロージャの中で(正確には離脱するクロージャの中で)構造体プロパティの値を更新することはできません。そのため、更新する予定のある変数は@StateをつけてPropertyWrapperで定義する必要があります。

しかし、今回使用しているfirstDayFlagdは、カレンダーを表示するためのループ制御用の変数で、ループ処理を終えたら不要になるものだと思いますので、状態変数として保存すべきものではありません。

また、ForEach(0..<7)は、
https://developer.apple.com/documentation/swiftui/foreach

A structure that computes views on demand from an underlying collection of of identified data.

と説明されている通り、必要に応じて(on demand)Viewを算出する構造体です。
カレンダー表示で1画面に収まるデータを表示するなら、0から6まで順番に実行されるかもしれませんが、画面からはみ出て結局表示する必要がないと判断された範囲の行はもしかしたら実行されないかもしれません。また、再描画が必要になった時に、一部の範囲だけもう一度実行されるかもしれません。そのため、 ForEachを通常のfor文のように最初から最後まで順番に全てループする前提で使ってはいません。

今回の場合、firstDayFlagdを使わず、次のようにForEachから渡される週番号(row)と曜日番号(week)だけを使って処理できるはずです。

struct printMonth: View { @State var year: Int @State var month: Int var body: some View { HStack() { ForEach(0..<7) { week in VStack() { ForEach (0..<6) { row in if self.getDate(row, week) < 1 { Text("") } else if self.getDate(row, week) > self.getLastDay(year: self.year, month: self.month) { Text("") } else { Text("(self.getDate(row, week))") } Text("") } } } } } func getDate(_ row: Int, _ week: Int)->Int { return row*7 + week + 1 - dayOfWeek(year: year, month: month, day: 1) } }

投稿2020/01/10 14:44

TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TsukubaDepot

2020/01/13 04:02

お返事が遅くなりまして申し訳ございません。 ご指摘を参考にコードを修正したところ、カレンダーの表示ができるようになりました。 ソースコードを端折ったため定義を省いた関数の戻り値まで推測していただいてコード修正案をご提示いただいたことに、ただひたすら感謝する次第です。 UIKit と Storyboard の開発がどうもピンとこなくて何度も挫折していた折、SwitftUIを紹介した良書と巡り合い、(ある意味)余計なことを考えなくても見栄えのあるインタフェイスを開発できると喜びながら、習作としてカレンダーアプリを作ってみようとやっていた際にこのトラブルに見舞われました。 SwiftUIでの記述に制限はあるものの、そこは基本的なアルゴリズムの工夫で処理すればよいとわかったので、習作をいくつか作ってみたり、Githubなどで参考になりそうなコードを拝見させていただいて体得していきたいとおもいます。 ありがとうございました。 #SwiftUIは、GUI記述も含めてかなり見通しの利く記述だとおもえますので、他の方が書いたコードも割合理解しやすい言語だな、とは思っています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問