_mitty
@_mitty

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

関数の返り値を初期化する方法について

解決したいこと

swiftUIを勉強中の者です。

ForEach文でTextビューを並べる際に、並べた回数をカウントしておきたかったため、以下に載せるサンプルコードのようなカウントアップ用の関数を作成しました。

以下コードで期待していた動き方は、
①”処理A”で、counter関数により1〜5までカウント

②counter関数の返り値である変数cntを一旦初期化

③”処理B”でも、counter関数により1〜5までカウント
でした。

しかし、処理Bでは処理Aでカウントしていた数の続きからカウントされて6〜10とカウントされてしまいます。

これは、なぜでしょうか。
ご存知の方、ご教授をお願いいたします。

該当するソースコード

//Xcodeのバージョン Version 12.3 (12C33)
//Swiftのバージョン Apple Swift version 5.3.2


import SwiftUI

//カウントアップ用の関数
var mode: Int = 0
var cnt: Int = 0
func counter(mode: Int) -> Int {//
    switch mode {
    case 0:
        cnt += 0
    case 1:
        cnt += 1
    case 2:
        cnt = 0
    default:
        print("error")
    }
    return cnt
}


struct ContentView: View {
    var body: some View {
        VStack {
            //■処理A■
            //Textビューを1枚ずつ5回並べ、並べる度にcounter関数を呼び出して返り値の変数cntを表示
            //(1〜5と表示されたテキストボックスが並びます)
            HStack(spacing: 10) {
                ForEach(0..<5) { i in
                    Text("\(counter(mode: 1))")//
                        .frame(width: 30, height: 30)
                        .border(Color.black, width: 1)
                }
            }
            //■カウンター初期化■
            //数字のカウントに用いたcounter関数の返り値を0に戻したいために、このようにしています。
            //他に良い初期化の方法があればアドバイスいただけると幸いです。
            Text("\(counter(mode: 0))")
                .frame(width: 30, height: 30)
                .foregroundColor(.black)
            Text("\(cnt)")

            //■処理B■
            //Textビューを1枚ずつ5回並べ、並べる度にcounter関数を呼び出して返り値の変数cntを表示
            //(6〜10と表示されたテキストボックスが並びます)
            //変数cntを0に戻しているので、処理Aと同様に1〜5と表示させたいのですが、
            //処理Aの続きからカウントされてしまいます。 なぜでしょうか?
            HStack(spacing: 10) {
                ForEach(0..<5) { i in
                    Text("\(counter(mode: 1))")//
                        .frame(width: 30, height: 30)
                        .border(Color.black, width: 1)
                }
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
0

2Answer

やりたいことがよくわからないのですが ForEach から渡される値を利用してはいかがでしょうか。

VStack {
    ForEach(0 ..< 5) { i in
        Text("\(i + 1)")
    }
    ForEach(0 ..< 5) { i in
        Text("\(i + 1)")
    }
}
1Like

Comments

  1. @_mitty

    Questioner

    ご回答ありがとうございます。
    読み返すと質問内容が分かりにくくなっていることに気づきました。
    ちょうど質問を更新させていただきましたので、再度アドバイスいただけないでしょうか?
    関数の返り値を初期化する方法について、という方が正しいかもしれません。
  2. 期待した動作にならないのは、ビューが上から順に描画されるとは限らないからです。
    アドバイスするなら、まず自分でカウントするアプローチをやめたほうが良いですね。
  3. @_mitty

    Questioner

    ご回答ありがとうございます。
    必ずしもコード上の順番通りでは無いのですね。
    上記のコードは、質問用のコードのため、本来わさわざ使う必要のない関数を使ったおかしなコードになってしまっている点はご容赦下さい。
    重ねての質問で恐縮ですが、上のコードでcounter関数の返り値を0に戻して使う方法はあるでしょうか?

    また、描画のタイミングを設計者が想定することは難しいでしょうか?
    アドバイスいただいた点を踏まえて色々確認してみると、どうやら上のコードでは、
    1:■カウンター初期化■
    2:■処理A■
    3:■処理B■
    の順番で実行されているようです。
    宜しくお願いします。
  4. 処理AとBとでカウントする変数を分けたらリセットせずに済むのではないでしょうか。

    描画の詳細を隠蔽できるのがSwiftUIのメリットの一つですから、それを捨ててまで何をしたいのかを教えていただければ何か言えることもあるかもしれません。
  5. @_mitty

    Questioner

    他の言語と比較すると、メリットとして捉えられる部分でもあるんですね。

    この質問を経てやりたかったことは、簡易的なカレンダーの作成です。

    具体的には、
    月の最初の日と最後の日が、月曜日〜日曜日のどこで始まりどこで終わるかは予め計算しておく
    5行7列のセルを並べながら、変数をカウントアップして日付も入れる
    といった処理です。

    どうしてもと言うほどではないかもしれませんが、例えば特定の日だけカレンダーのデザインが変わるような仕様にしたい場合、日付をカウントしておく変数があった方が、ForEachから渡される値(i = 0〜4, j = 0~6)より便利で、後々応用が利くと考えたためです。

    勉強用とはいえ、中々原始的なやり方かもしれません。。
    他の一般的でスマートなやり方があれば紹介いただきたいのですが、それとは別に、なぜ本コードのcounter関数が初期化できないのか知っておきたく質問させていただいております。

    上記のコードでは、同じ機能の関数を2つ用意するのが早いのですが、
    例えば、同じ仕組みで3月カレンダーから4月カレンダーに変えるとき、
    counter関数をリセットし、1から日付をカウントすることになるかと思います。

    このとき、3/31までカウントして、カウントしていた数字が引き継がれて、32からカウントされてしまうのでは困るのですが、どうしたら良いでしょうか?

    アドバイス、ありがとうございます。
    引き続き何かヒント頂けますと幸いです。
    よろしくお願い致します。

シンタックスハイライトが効かないので新しくコメントします。
その例でいうと、日付が何日から何日までかを計算するだけでなく、その日付の一覧をつくるところまでやってください。

// 表示したい日付の一覧
let dates: [Date] = ...

// 表示に使うデータ
let rows: [[Date]] = /* 日付の一覧を変換して6x7の配列にする */

// UIに表示する
var body: some View {
    VStack {
        ForEach(rows, id: \.self) { (columns: [Date]) in
            HStack {
                ForEach(columns, id: \.self) { (date: Date) in
                    // 適当に表示する
                    Text("\(DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .none))")
                }
            }
        }
    }
}

特定の日だけ見た目を変えたいときは、データ構造の変更などが考えられそうですね。

struct Item: Hashable {
    let date: Date
    let isSpecial: Bool
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(date)
    }
}

let items: [Item] = ...
let rows: [[Item]] = ...

var body: some View {
    VStack {
        ForEach(rows, id: \.self) { (columns: [Item]) in
            HStack {
                ForEach(columns, id: \.self) { (item: Item) in
                    let text = DateFormatter.localizedString(from: item.date, dateStyle: .short, timeStyle: .none
                    if item.isSpecial {
                        // 特別な日は赤くしよう
                        Text("\(text)").foregroundColor(.red)
                    } else {
                        Text("\(text)")
                    }
                }
            }
        }
    }
}

表示したいデータを用意する処理と、実際にそれを表示する処理は分けて考えるようにしてください。

追記:rowとcolumnが逆だったので修正

1Like

Comments

  1. カウントのことを完全に無視していて申し訳ないんですが、正直なところ金槌で木を切るにはどうしたら良いかと聞かれているような感じですごく答えづらいんですよね…。
  2. @_mitty

    Questioner

    いえ、とんでもございません。
    予め表示データの配列を作っておく方法がずっと良さそうですね。
    表示したいデータを用意する処理と、実際にそれを表示する処理を分けるべきとのアドバイスが私の中でしっくりきました。
    改めて考えるとwoxtu様が困惑されるのも無理はないかと思います。
    お付き合いいただきありがとございます。

    そしてすみません、
    載せていただいたコードについてですが、いくつか理解できない点があります。
    特にItem構造体が何をしているコードで、hashableプロトコルはどういった時に使うプロトコルなのでしょうか?
    hashableに関しては、このような文献を見つけましたが、結局このプロトコルに準拠しないとどう困るのか分からずといったレベルです。
    https://qiita.com/KatagiriSo/items/029a9883b78825d5c06a

    また、例えばString型やInt型は分かりますが、items: [Item]のような”:[構造体名]”となる書き方をほとんど習得できておりません。
    その辺りを言語化していただけるととても助かります。

    重ね重ねすみませんが、非常に勉強になっております。
    よろしくお願い致します。
  3. - `Item` 構造体が何をしているか
    日付とフラグを組み合わせた新しい型を定義しています。
    https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html

    - `Hashable` プロトコルはいつ使うか
    `ForEach` が `Hashable` を要求しているので準拠させています。フレームワークなどの要求に応じて準拠させる以外ではあまり使わないと思います。

    - `[構造体名]` とは何か
    `Array<構造体名>` の短縮形で配列を表します。
    https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html#ID108
  4. @_mitty

    Questioner

    ご回答ありがとうございます。

    返信いただいた内容を解釈して、私の言葉で言い直すとこのようになるのですが、いかがでしょうか?(誤りでなければ、ただ言い換えてるだけですが。。)

    ① items: [Item]は、itemsという名のItem型の値で、Date型と Bool型の値がセットになったもの
     (複数の関連した値を1つにまとめて、新たな値として扱うことができるようにしたもの)
    ② rows: [[Item]]は、上のitemsをさらに格納するための配列

    Hashableプロトコルに関しては、まだ理解するには他の知識が足りていないような気がしていますので、一旦これ以上の深追いは控えたいと思います。。笑
  5. ①少し惜しくてItem型に関してはその通りですがitemsはその配列ですね
    ②そうです
  6. @_mitty

    Questioner

    ありがとうございます。
    大変助かりました。

Your answer might help someone💌