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

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

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

TableView(UITableView)とは、リスト形式で表示するコントロールで、ほとんどのアプリに使用されています。画面を「行」に分けて管理し、一般的には各行をタップした際に詳細画面に移動します。

Swift

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

Q&A

解決済

1回答

1405閲覧

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

SadajiroOkuno

総合スコア4

TableView

TableView(UITableView)とは、リスト形式で表示するコントロールで、ほとんどのアプリに使用されています。画面を「行」に分けて管理し、一般的には各行をタップした際に詳細画面に移動します。

Swift

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

0グッド

0クリップ

投稿2020/08/30 10:51

編集2020/09/02 19:08

前提・実現したいこと

いつも大変お世話になっております!
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() }     ****************** }

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

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

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

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

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

TsukubaDepot

2020/08/31 01:02

問題はおそらく、 「今日の日付は8月30日、登録されている予定は 8月26日のみ」 という条件のときに、 「一体どの予定を先頭に持ってきたいのか」 ということが定義されていないことが原因(少なくともコードを読む限り)ですが、このような条件の場合はどのように表示したいと思われているのでしょうか。
SadajiroOkuno

2020/08/31 07:27

TsukubaDepotさん いつも大変お世話になってます!今回もおつきあいありがとうございます! 盲点でした。。。>「今日の日付は8月30日、登録されている予定は 8月26日のみ」 条件を追加して落ちることはなくなりましたが、isEmptyでの条件が原因なのか、やはり立ち上げ時に反応せず。修正追加コード上げますので、閲覧して貰えればありがたいです。
TsukubaDepot

2020/08/31 07:36

「反応しない」というのがどのような状態なのかもう少し書いていただくといいかと思います。 反応しない、という表現は「フリーズする」「動いているが思っているような表示がでない」など、解釈の余地が色々ありますので、具体的に「何ができないのか、それは自分の想定とどのように異なっているのか」を記述していただけないでしょうか。
TsukubaDepot

2020/08/31 07:37

ちなみに、フィルタ操作に関連するコードだけ抜き出し、Playground あたりで動作を確認してみるのも一つの方法かと思います。 UI表示に関係ない部分を除き、本質的なコードだけにすることで、問題点や対策が絞りやすくなるのではないでしょうか。
SadajiroOkuno

2020/08/31 07:38

追記  今、26日のsectionまで動きました! 写真を追加したのですが、やはり一度でfilterできなく一番始めの回で配列に並んだ日付が反映されてしまいます。
SadajiroOkuno

2020/08/31 08:30

入れ違いで失礼しました。 説明不足に大変恐縮しております。 改めて、 当pageが展開されたタイミングでtableViewのcell、sectionの動作について、自分が思い描いているのは以下4つです。 ①"今日の日付がある場合、今日の日付のsectionをtableViewの先頭に持ってくる。" ②"今日の日付がない場合、次の予定日付であるsectionをtableViewの先頭に持ってくる。" ③"その次の日付及び先の日程もない場合、既に消化した日程である日付、今日の日付に一番近い日付であるsectionをtableViewの先頭に持ってくる。" ④"どれも該当しないときはtableViewは空白になり、「まだ予定はありません」というコメントが出るようにします。" ④は今回とは別の問題ですので省略と致しまして。 今のところ自分が思う問題点は、 filterを使った時に一度に配列に並んで入ってくれないことが原因だと感じています。 Playgroundを使うと問題なくfilteringされるので、その原因を解くためにグローバルスコープで let filteredScheduleArray1 = [String]( ) let filteredScheduleArray1 : [String] = [ ] ローカルスコープ内で同じように定義しても、デバッグエリアに表示されるfilteredScheduleArray1の配列は[ ]から始まります。 filterの使い方を調べてもこのような内容のことは書いておらず。息詰まっております。
SadajiroOkuno

2020/08/31 08:35

またも行き違いで失礼しました。今御回答して頂いた内容を確認中です!
SadajiroOkuno

2020/09/02 09:55

TsukubaDepotさん ご回答及び時間をとって試行して頂いて本当にありがとうございます! と同時に返事が遅くなったことに大変恐縮しております。 2日間、頂戴したコードで試しながら色々工夫したものの。上記で思う②③の動きになってくれません。 playgoundではしっかりと動いてくれるんですが。 引き続き質問させてください。 関係ないのかもしれませんが、このスケジュールのtableViewには二つの違うデータ郡をfirestoreから呼び出しております、それを一つの配列に入れてから日程だけを取り出し重複を取り消し、昇順に並べてから今回の処理を施しております。今回のテーマに関係ないと思いそれ以外のコードは割愛したつもりなんですが、そのどこかが今回の処理を阻んでいるのでしょうか。お忙しいとは思います、タイミングよき時にご返信頂ければ幸いです!よろしくお願いします。
TsukubaDepot

2020/09/02 10:04

まず確認ですが、私が回答として付けた内容の方は確認されましたでしょうか。 そちらは意図通りに動いたのか、それとも違うのかについて「回答に」コメントいただければと思います。 また、Firestore を使われているということですが、データが届くタイミングというのは考慮されていますでしょうか。 Firestore そのものは使ったことがありませんが、問い合わせを行った後、クロージャ内でデータが取れるようになるまでには時間がかかるため、非同期処理になっています。 したがって、2つのデータを別々に撮ってきた場合でも、大抵の場合はどちらかのデータしか取得できていないことがあるため、両方のデータが揃うまで、何らかの方法で待つ必要があります。 問題を簡潔にするためには、Firestore から2つのデータを取得し、それを任意のタイミングで取得したときに、「辻褄が合った」データになっているのか確認するごく簡単なアプリを作ることかと思います。 ある程度プログラムが大きくなってきた後に、ある機能を追加すると、問題が発生しても何が原因かわかりにくくなる場合もあります。そういう時に有効なのは、まず必要最低限の機能を実現する簡単なアプリを組み、それに少しづつ機能を追加していくことかと思います。 コメントでは「playgoundではしっかりと動いてくれる」とありますので、おそらく非同期処理のあたりに原因があるのではないでしょうか。
TsukubaDepot

2020/09/02 10:06

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

2020/09/02 11:08

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

回答1

0

ベストアンサー

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

Playground で確認しました。

Swift

1import Foundation 2 3// 4つの条件を切り替えて実験する 4//var dateArray = [String]() 5//var dateArray = ["2020-08-26", "2020-08-30", "2020-08-26"] 6var dateArray = ["2020-08-31", "2020-08-30", "2020-08-26"] 7//var dateArray = ["2020-08-30", "2020-09-01", "2020-09-02", "2020-09-29", "2020-09-30"] 8 9var dateFormatter: DateFormatter = { 10 let formatter = DateFormatter() 11 formatter.locale = Locale(identifier: "en_US_POSIX") 12 formatter.dateFormat = "yyyy-MM-dd" 13 return formatter 14}() 15 16/****重複を消す****/ 17let duplicateDAOS = NSOrderedSet(array: dateArray) 18let stringArray = duplicateDAOS.array as! [String] 19let scheduleArray = stringArray.sorted { ( lDate, rDate) -> Bool in 20 return lDate < rDate 21} 22print("登録された予定日一覧: ", scheduleArray) 23 24let date = Date() 25let today = dateFormatter.string(from: date) 26print("今日の日付: ",today) 27 28// OrderdSet を使っているので、表示するときに再度フィルタリングする必要はない(既に重複データは削除されているから) 29 30//if scheduleArray.contains(today) { 31// // 今日の予定があったときの処理 32// print("本日の予定が見つかりました ", scheduleArray.filter { $0.contains( today )} ) 33//} 34 35if let index = scheduleArray.firstIndex(of: today) { 36 // 今日の予定があったときの処理 37 print("本日の予定が見つかりました。要素番号は (index) で、日付は (scheduleArray[index]) です。") 38} else { 39 // 今日の予定が無かったときの処理 40 print("本日 ((today)) 予定はありませんでした。") 41 42 let filteredScheduleArray = scheduleArray.filter({ element in element > today }) 43 44 if let nextScheDate = filteredScheduleArray.first { 45 // 明日以降、何らかの予定が入っている場合 46 // つまり、「本日以降という条件でフィルタリングした配列に最初の要素がある場合 47 // 上記の条件をオプショナルバインディングで判断 48 let section = filteredScheduleArray.firstIndex(of: nextScheDate) 49 print("次の直近の予定日は (nextScheDate) で、要素番号は (section!) です。") 50 51 } else { 52 // 今日以降の処理が無かった場合の処理 53 // 予定全体に最後の要素があるか否かをオプショナルバインディングで判断 54 if let lastDate = scheduleArray.last { 55 // 本日より前の直近の予定日を表示 56 print("最後の直近の予定日は (lastDate) です。") 57 } else { 58 // 全く予定がながったときの処理 59 print("登録されている予定は 0 件です") 60 } 61 } 62}

投稿2020/08/31 08:17

TsukubaDepot

総合スコア5086

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

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

SadajiroOkuno

2020/09/02 10:19

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 section = filteredScheduleArray.firstIndex(of: nextScheDate) 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() } 回答頂いた内容を使わせていただきました! カレンダーの予定に今日と同じ日付がある場合はtableViewのTopにその日付セクションが表示され意図した動きになってくれます。しかし、今日の日付がない場合は先の予定があるにも関わらず先の予定のセクションがTopに来てくれません。 そして、今日の日付より過去の予定が存在してもそのsectionがTopに来てくれません。 tableViewにはただ重複、並び替え、グルーピングされた日付が昇順に並びます。
TsukubaDepot

2020/09/02 10:26

上記のコードですが、そもそもどのようなデータが来るという想定で試されたのでしょうか。 Playgorund で試した時にはうまくいった、ということであれば、そのためしたときのデータだと期待通りの結果がでたんだと推測しています。 しかし、今回実行する環境で試した時にはうまくいかない、ということであれば、これらの処理に「入る直前」のデータがどのようなデータになっているのか、つまりdateArrayの中身はどのようになっいるのかしっかり確認しないことには、行き当たりばったりの対応になってしまうためうまくいかなくても当然ですし、そのデータを知り得ない他の回答者も解答ができないと思います。 まずは、dateArray のデータとしてどのようなデータが届くのかしっかり確認し、それは期待通りなのか確認されてみてはいかがでしょうか。
SadajiroOkuno

2020/09/02 10:37

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

2020/09/02 19:23

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

2020/09/02 19:45

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問