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

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

ただいまの
回答率

88.60%

TableViewに今日の日付を先頭にして呼び出したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 205

前提・実現したいこと

いつも大変お世話になっております!
TableViewにsectionで日付を昇順で並べています。
起動したその日が2020/8/30とした場合、その日付のsectionがTableViewの一番上に来るようにします。
その日付が配列に存在するときは問題なく機能しましたが、配列に存在しない場合、次の予定が一番上に来るように実装したいのです。
イメージ説明

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

下記のコードのように、もし今日の日付が配列の中の日付に一致しなかった場合は、
filterを使って本日以降の日付を新しく配列を作り入れます。
その始めにくる日付の配列番号が想定する日付になるので番号を取り出せたらと思うのですが、
記載したスクショの通り フィルター後の予定日時配列 が一度にfilteringされない為かnilになってしまい、
番号が上手く取り出せません。

もっと賢いやり方があるのかもしれませんが、どなたかご教授頂ければ大変たすかります。
何卒よろしくお願いいたします。

該当のソースコード

 fileprivate let gregorian: Calendar = Calendar(identifier: .gregorian)
    fileprivate lazy var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
    }()



       var dateArray = [String]()
        for num in 0..<editDic.count{
            dateArray.append(editDic[num].date)
        }
        /****重複を消す****/
        let duplicateDAOS:NSOrderedSet = NSOrderedSet(array: dateArray)
        let stringArray = duplicateDAOS.array as! [String]
        let scheduleArray = stringArray.sorted { ( lDate, rDate) -> Bool in
            return lDate < rDate
        }
        print("予定日時配列: ",scheduleArray)


        let date = Date()
        let today = dateFormatter.string(from: date)
        print("今日の日付: ",today)

        let section = scheduleArray.firstIndex(of: today)
        if section != nil{
            let indexPath = IndexPath(row: 0, section: section ?? 0)
            DispatchQueue.main.async {
                self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
            }
        }else if section == nil{
            let filteredScheduleArray = scheduleArray.filter({ element in element > today })
            print("フィルター後の予定日時配列: ",filteredScheduleArray)

            //let nextScheDate = filteredScheduleArray[0]
            //let section = filteredScheduleArray.firstIndex(of: nextScheDate)
            let indexPath = IndexPath(row: 0, section: section ?? 0)
            DispatchQueue.main.async {
                self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
            }
        }
        tableView.reloadData()
}

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

//let nextScheDate = filteredScheduleArray[0]
//let section = filteredScheduleArray.firstIndex(of: nextScheDate)
これら2行をコメントアウトすると以下の写真のように順番に配列に足されていくことが確認できますが、一番始めは[]からになってます。

イメージ説明

例の2行をコメントアウトしなかった場合は落ちてしまいます。

イメージ説明

全て配列に入りきってから配列番号を取得しようと試みるのですが、呼び出しの場所を変えても状態は変わらず。
改めてご教授頂ければと思います。よろしくお願いします。

修正コードはこちら

 /****重複を消す****/
        let duplicateDAOS:NSOrderedSet = NSOrderedSet(array: dateArray)
        let stringArray = duplicateDAOS.array as! [String]
        let scheduleArray = stringArray.sorted { ( lDate, rDate) -> Bool in
            return lDate < rDate
        }
        print("予定日時配列: ",scheduleArray)

        let date = Date()
        let today = dateFormatter.string(from: date)
        print("今日の日付: ",today)

        let section = scheduleArray.firstIndex(of: today)
        if section != nil{
            let indexPath = IndexPath(row: 0, section: section ?? 0)
            DispatchQueue.main.async {
                self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
            }
        }else if section == nil{

            let filteredScheduleArray1 = scheduleArray.filter({ element in element > today })
            print("配列、次の予定日時が始めに来る: ",filteredScheduleArray1)
            if filteredScheduleArray1.isEmpty == false{
                let nextSection = filteredScheduleArray1.startIndex + 1
                let indexPath = IndexPath(row: 0, section: nextSection)
                DispatchQueue.main.async {
                    self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
                }
            }else if filteredScheduleArray1.isEmpty == true{
                let filteredScheduleArray2 = scheduleArray.filter({ element in element < today })
                let preSection = filteredScheduleArray2.endIndex + 1
                print("配列、消化した日程最終日が最後に来る: ",filteredScheduleArray2)
                let indexPath = IndexPath(row: 0, section: preSection)
                DispatchQueue.main.async {
                    self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
                }
            }
        }
        tableView.reloadData()
    }

イメージ説明

********************************************************
解決致しました!
TsukubaDepotさんのリンクを参照しました。
https://teratail.com/questions/288703
コード記載は****マークで囲ってあります。
解決したコード

    var schedule = [Date: [schedules]]()
    var dateOrder = [Date]()
    var editDic1 = [schedules]()
    var editDic2 = [schedules]()
    var editDicAll = [schedules]()
    var editDic = [schedules]()
   

 func combineInfosToSingleArray(editDic:[schedules]){
        self.editDic.removeAll()
        self.editDicAll = editDic1 + editDic2
        editDic = self.editDicAll.sorted { ( ldate, rdate) -> Bool in
            return ldate.date < rdate.date
        }
        ****ここにcompletionを入れて、firebaseから読み込んだ2つの違うデータを
                       一つの配列に入れてから、次の処理へ移行するようにしました***
        setGrouping(editDic: editDic, completion: {
            self.displaySectionOnTop(editDic: self.editDic)
        })
        *************************************************
        tableView.reloadData()
    }

    func displaySectionOnTop(editDic:[schedules]){
        var dateArray = [String]()
        for num in 0..<editDic.count{
            dateArray.append(editDic[num].date)
        }
        /****重複を消す****/
        let duplicateDAOS:NSOrderedSet = NSOrderedSet(array: dateArray)
        let stringArray = duplicateDAOS.array as! [String]
        let scheduleArray = stringArray.sorted { ( lDate, rDate) -> Bool in
            return lDate < rDate
        }
        print("スケジュール配列: ",scheduleArray)

        let date = Date()
        let today = dateFormatter.string(from: date)
        if let index = scheduleArray.firstIndex(of: today){
            let indexPath = IndexPath(row: 0, section: index)
            DispatchQueue.main.async {
                self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
            }
        }else{
            print("本日 (\(today)) 予定はありませんでした。")

            let filteredScheduleArray = scheduleArray.filter({ element in element > today })
            if let nextScheDate = filteredScheduleArray.first{

                let index = filteredScheduleArray.firstIndex(of: nextScheDate)
                let getNextSection = scheduleArray.count - filteredScheduleArray.count
                let section = getNextSection + index!
                let indexPath = IndexPath(row: 0, section: section)
                DispatchQueue.main.async {
                    self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
                    print("次の直近の予定日は \(nextScheDate) で、要素番号は \(section) です。")
                }
            }else{
                let filteredScheduleArray = scheduleArray.filter({ element in element < today })
                if let lastDate = filteredScheduleArray.last{
                    let section = filteredScheduleArray.firstIndex(of: lastDate)
                    let indexPath = IndexPath(row:0,section: section!)
                    DispatchQueue.main.async {
                        self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
                        print("直近の予定はありませんでした。")
                        print("前回の予定は \(lastDate) で、要素番号は \(section!) です。")
                    }
                }else{
                    print("登録されている予定は 0 件です")
                }
            }
        }
        tableView.reloadData()
    }
****こちらの関数で@escaping()->()と,下記にDispatchQueue.main.async{completion()}を追加しました****
 func setGrouping(editDic:[schedules], completion: @escaping()->()){

        schedule = Dictionary(grouping: editDic){ project ->  Date in
            let date = dateFormatter.date(from: project.date)
            return date ?? Date()
        }
        .reduce(into:[Date:[schedules]]()) { dic, tuple in
            dic[tuple.key] = tuple.value.sorted {  $0.date < $1.date}
        }
        dateOrder = Array(schedules.keys).sorted{ $0 < $1}
       ******こちら********
      DispatchQueue.main.async {
            completion()
        }
    ******************
    }
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • TsukubaDepot

    2020/09/02 19:04

    まず確認ですが、私が回答として付けた内容の方は確認されましたでしょうか。
    そちらは意図通りに動いたのか、それとも違うのかについて「回答に」コメントいただければと思います。

    また、Firestore を使われているということですが、データが届くタイミングというのは考慮されていますでしょうか。

    Firestore そのものは使ったことがありませんが、問い合わせを行った後、クロージャ内でデータが取れるようになるまでには時間がかかるため、非同期処理になっています。

    したがって、2つのデータを別々に撮ってきた場合でも、大抵の場合はどちらかのデータしか取得できていないことがあるため、両方のデータが揃うまで、何らかの方法で待つ必要があります。

    問題を簡潔にするためには、Firestore から2つのデータを取得し、それを任意のタイミングで取得したときに、「辻褄が合った」データになっているのか確認するごく簡単なアプリを作ることかと思います。

    ある程度プログラムが大きくなってきた後に、ある機能を追加すると、問題が発生しても何が原因かわかりにくくなる場合もあります。そういう時に有効なのは、まず必要最低限の機能を実現する簡単なアプリを組み、それに少しづつ機能を追加していくことかと思います。

    コメントでは「playgoundではしっかりと動いてくれる」とありますので、おそらく非同期処理のあたりに原因があるのではないでしょうか。

    キャンセル

  • TsukubaDepot

    2020/09/02 19:06

    https://teratail.com/questions/288703
    上記の質疑にて、Firestoreにおける非同期処理について回答しています。
    クロージャ内部で操作した配列が、クロージャ外部では意図したように参照できないという問題(追加されるはずのデータが追加されない)という問題です。
    これが参考になるのではないでしょうか。

    キャンセル

  • SadajiroOkuno

    2020/09/02 20:08

    TsukubaDepotさんいつもありがとうございます!
    処理が間に合わず先に進んでしまうことがある事を知りませんでした。
    リンク先参考にさせて戴いてます。引き続きこちらで進捗具合を伝えさせてください。

    キャンセル

回答 1

checkベストアンサー

+1

ブラッシュアップの余地はあるかと思いますが、わかっている範囲で実現するのであればこのような感じでしょうか。

Playground で確認しました。

import Foundation

// 4つの条件を切り替えて実験する
//var dateArray = [String]()
//var dateArray = ["2020-08-26", "2020-08-30", "2020-08-26"]
var dateArray = ["2020-08-31", "2020-08-30", "2020-08-26"]
//var dateArray = ["2020-08-30", "2020-09-01", "2020-09-02", "2020-09-29", "2020-09-30"]

var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.locale = Locale(identifier: "en_US_POSIX")
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter
}()

/****重複を消す****/
let duplicateDAOS = NSOrderedSet(array: dateArray)
let stringArray = duplicateDAOS.array as! [String]
let scheduleArray = stringArray.sorted { ( lDate, rDate) -> Bool in
    return lDate < rDate
}
print("登録された予定日一覧: ", scheduleArray)

let date = Date()
let today = dateFormatter.string(from: date)
print("今日の日付: ",today)

// OrderdSet を使っているので、表示するときに再度フィルタリングする必要はない(既に重複データは削除されているから)

//if scheduleArray.contains(today) {
//    // 今日の予定があったときの処理
//    print("本日の予定が見つかりました ", scheduleArray.filter { $0.contains( today )} )
//}

if let index = scheduleArray.firstIndex(of: today) {
    // 今日の予定があったときの処理
    print("本日の予定が見つかりました。要素番号は \(index) で、日付は \(scheduleArray[index]) です。")
} else {
    // 今日の予定が無かったときの処理
    print("本日 (\(today)) 予定はありませんでした。")

    let filteredScheduleArray = scheduleArray.filter({ element in element > today })

    if let nextScheDate = filteredScheduleArray.first {
        // 明日以降、何らかの予定が入っている場合
        // つまり、「本日以降という条件でフィルタリングした配列に最初の要素がある場合
        // 上記の条件をオプショナルバインディングで判断
        let section = filteredScheduleArray.firstIndex(of: nextScheDate)
        print("次の直近の予定日は \(nextScheDate) で、要素番号は \(section!) です。")

    } else {
        // 今日以降の処理が無かった場合の処理
        // 予定全体に最後の要素があるか否かをオプショナルバインディングで判断
        if let lastDate = scheduleArray.last {
            // 本日より前の直近の予定日を表示
            print("最後の直近の予定日は \(lastDate) です。")
        } else {
            // 全く予定がながったときの処理
            print("登録されている予定は 0 件です")
        }
    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/09/02 19:37

    少ない情報量で大変失礼しました。
    dataArrayの確認、加えて上記で教えて頂いたリンク先の内容を参考にさせて戴きます
    ご教授本当にありがとうございます!

    キャンセル

  • 2020/09/03 04:23

    TsukubaDepotさん
    たった今解決致しました!
    もっといいコードがあると思いますが、ひとまず上記にコードを記載しました。
    送って頂いたリンク https://teratail.com/questions/288703
    を参考にfirebaseの呼び出しが終わり配列に入ったのち、今回の回答頂いた処理を記載したところ、思った通りに今日の日付がある時、ない時のsectionの動きが機能しました!
    本当にありがとうございます。まだまだ不慣れな私にわかり易く説明頂いて感謝しております。自分もなるべくわかりやすい説明で理解していただけるように心がけて行きたいと思います。何卒よろしくお願いします。

    キャンセル

  • 2020/09/03 04:45

    解決したようでよかったです。
    Firestore など、クラウドを使った処理は概ね非同期(ネットワークのスピードによって反応が異なるため)なので、それを意識した処理が必要となります。
    また、問題の切り分け方法も重要となってきますので、それに慣れてくると解決も早くなるのではないでしょうか。

    キャンセル

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

  • ただいまの回答率 88.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る