🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Xcode

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

Swift

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

Q&A

解決済

2回答

1573閲覧

Swift Dictionary(grouping by )を使ってセクション分けしたTableViewのコードの読み方

maplemee

総合スコア16

Xcode

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

Swift

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

0グッド

0クリップ

投稿2021/02/08 05:07

Swift初学者です。
こちらの記事を参考にソースコードのみ載っていたので、「スケジュールを日付別に時間順に並べる」ということができたのですが、所々コードを調べても分からない箇所があり完全な理解に至らず現在苦戦しております。

全体のコードが以下になります。

Swift4

1import UIKit 2 3struct Plan { 4 let name: String 5 let detail: String 6 let date: Date 7} 8 9class MyViewController : UIViewController { 10 11 var schedules = [Date: [Plan]]() 12 var dateOrder = [Date]() 13 14 private var formatter: DateFormatter { 15 let f = DateFormatter() 16 f.dateStyle = .long 17 f.timeStyle = .none 18 f.locale = Locale(identifier: "ja_JP") 19 return f 20 } 21 22 private var timeFormatter: DateFormatter { 23 let f = DateFormatter() 24 f.dateStyle = .none 25 f.timeStyle = .short 26 f.locale = Locale(identifier: "ja_JP") 27 return f 28 } 29 30 override func viewDidLoad() { 31 super.viewDidLoad() 32 33 prepare() 34 35 let tv = UITableView(frame: self.view.frame) 36 tv.dataSource = self 37 tv.delegate = self 38 view.addSubview(tv) 39 } 40 41 private func prepare() { 42 let f = DateFormatter() 43 f.locale = Locale(identifier: "en_US_POSIX") 44 f.dateFormat = "yyyy/MM/dd HH:mm" 45 46 let plans = [ 47 Plan(name: "休み", detail: "実家で過ごす", date: f.date(from: "2018/01/01 09:00")!), 48 Plan(name: "外出", detail: "初詣", date: f.date(from: "2018/01/03 10:00")!), 49 Plan(name: "仕事", detail: "社内会議", date: f.date(from: "2018/01/03 17:30")!), 50 Plan(name: "外出", detail: "買い出し", date: f.date(from: "2018/01/15 19:00")!), 51 Plan(name: "仕事", detail: "Team Meeting", date: f.date(from: "2018/01/15 15:30")!), 52 Plan(name: "休み", detail: "家族で水族館", date: f.date(from: "2018/01/17 08:00")!), 53 Plan(name: "休み", detail: "飲み会", date: f.date(from: "2018/01/17 20:00")!) 54 ] 55 56 schedules = Dictionary(grouping: plans) { plan -> Date in 57 f.dateFormat = "yyyy/MM/dd" 58 let s = f.string(from: plan.date) 59 return f.date(from: s)! 60 } 61 .reduce(into: [Date: [Plan]]()) { dic, tuple in 62 dic[tuple.key] = tuple.value.sorted { $0.date < $1.date } 63 } 64 65 // 日付順を保持するための配列 66 dateOrder = Array(schedules.keys).sorted { $0 < $1 } 67 } 68 69} 70 71extension MyViewController: UITableViewDataSource { 72 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 73 let targetDate = dateOrder[section] 74 return schedules[targetDate, default: []].count 75 } 76 77 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 78 let reuseIdentifier = "cell" 79 80 var cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) 81 if cell == nil { 82 cell = UITableViewCell(style: .subtitle, reuseIdentifier: reuseIdentifier) 83 } 84 85 let targetDate = dateOrder[indexPath.section] 86 guard let plan = schedules[targetDate]?[indexPath.row] else { 87 return cell! 88 } 89 90 cell!.textLabel?.text = plan.name 91 92 let time = timeFormatter.string(from: plan.date) 93 cell!.detailTextLabel?.text = "(plan.detail) (time)予定" 94 return cell! 95 } 96} 97 98extension MyViewController: UITableViewDelegate { 99 100 func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 101 102 let targetDate = dateOrder[section] 103 104 let label = UILabel() 105 label.backgroundColor = .lightGray 106 label.textColor = .white 107 108 label.text = formatter.string(from: targetDate) 109 110 return label 111 } 112 113 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 114 return 60.0 115 } 116 117 func numberOfSections(in tableView: UITableView) -> Int { 118 return schedules.keys.count 119 } 120} 121

実行するとこのようになります。

◆◆コードを読み進めていて難しいと感じている箇所◆◆

配列に入った構造体の値で辞書をDate型で宣言

var schedules = Date: [Plan] 

以下が値に使われている構造体

struct Plan {

let name: String let detail: String let date: Date

}

⇨ここまでは構造体と辞書の宣言という表面的な理解ですが、あまり馴染みのない構造体が辞書の値に直接入っており(加えて配列で)最初の段階で応用の効いた?コードだと読むのに挫折しかけました。

schedules = Dictionary(grouping: plans) { plan -> Date in

f.dateFormat = "yyyy/MM/dd" let s = f.string(from: plan.date) return f.date(from: s)! }

⇨ここで先ほど宣言した辞書をGroupingしたDictionaryとして具体的に生成しているみたいです。**grouping:**の対象となる値を構造体Planを配列に入れたplansに指定しています。

let plans = [

Plan(name: "休み", detail: "実家で過ごす", date: f.date(from: "2018/01/01 09:00")!), Plan(name: "外出", detail: "初詣", date: f.date(from: "2018/01/03 10:00")!), Plan(name: "休み", detail: "飲み会", date: f.date(from: "2018/01/17 20:00")!) ]

⇨その後に記述されているクロージャもあまり馴染みがなく、planを引数名に指定して戻り値をDate型指定してどうやらplan.date=plans=Plan構造体のdateプロパティの値を「年月日」に整える処理をしているというに読みました。

.reduce(into: Date: [Plan]) { dic, tuple in

dic[tuple.key] = tuple.value.sorted { $0.date < $1.date } }

⇨辞書のkeyを使ってようやく辞書らしい?文法が出てきましたが、大枠ではreduceを使ってバラバラの値を再定義し直しているようです。ここでタプルの配列が出てくる意味がよくわかりません・・・m(_ _)m 検索して見つかった記事にtuple変数という表記は出てくるのですが詳しい解説は見つかりませんでした。

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

let targetDate = dateOrder[section] return schedules[targetDate, default: []].count }

⇨ようやく馴染みのあるtableviewに今まで定義してきたコードを落とし込む集大成の部分です。日付順を保持した配列「dateOrder = Array(schedules.keys).sorted { $0 < $1 }」をtableviewのsectionのindexpath(0,1,2,3・・・)で全てピックアップして定数targetDateに入れ込む。その後、返り値でschedules[targetDate, default: []]として使用しているところまで普通の読み方で追いかけたのですが、default:が今いちよくわかりませんでした。Apple公式には「defaultValue・・・key辞書に存在しない場合に使用するデフォルト値」とありましたが、schedulesを定義する段階で表面的な理解のまま進めており、いざ使う段階でさらに何が起きてるのか追いつけてない状態です(説明が下手ですみません)。

let targetDate = dateOrder[indexPath.section]

guard let plan = schedules[targetDate]?[indexPath.row] else { return cell! }

⇨cellの内容を定義する部分で全体的にそこまで難しくは感じませんでしたが、schedules[targetDate]?[indexPath.row]のsectionとrowの間に使われている「?」がどういう意味なのか単純に引っかかりました。

拙い日本語と説明文ですみません。またコード全体的な質問になり、長い質問になっております。
こちらで知恵を拝借させていただけると幸いですm(_ _)m

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

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

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

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

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

MasakiHori

2021/02/08 10:12

四則演算ができない状態で微分積分を習得しようとしているような状態なので、まずはSwiftの基本から覚えましょう
guest

回答2

0

ベストアンサー

⇨ここまでは構造体と辞書の宣言という表面的な理解ですが、あまり馴染みのない構造体が辞書の値に直接入っており(加えて配列で)最初の段階で応用の効いた?コードだと読むのに挫折しかけました。

構造体は Swift の基本なので、馴染みがないのであればまず構造体そのものと、構造体の配列をしっかり身に付けましょう。plans が構造体の配列になってるので、まずセクション分けせずに plans を table view に表示してみると良いのでは。(前回の質問で TsukubaDepot さんが二次元配列を使って回答されてましたが、あれも本来は構造体の配列を活用すべきだと思います。)


次に、var schedules = [Date: [Plan]]() ですが、これはキーが Date 型、値が Plan 構造体の配列になっています。日付を入れるとその日の予定が (1 日の予定は複数あり得ますから) 配列になって返ってきます。この辺の型をしっかり読めるようになることも大事ですね。

Dictionary(grouping:by:) は、いくつか簡単な例を試してみると良いでしょう。

swift

1let array = ["a", "ab", "abc", "b", "bc", "c"] 2 3let dic1 = Dictionary(grouping: array) { str in str.count } 4print(dic1) // ["abc"], 1: ["a", "b", "c"], 2: ["ab", "bc"]] 5 6let dic2 = Dictionary(grouping: array) { str in str.first! } 7print(dic2) // ["c"], "b": ["b", "bc"], "a": ["a", "ab", "abc"]]

dic1 は array の各要素の長さを求めて、

要素"a""ab""abc""b""bc""c"
長さ123121

その結果に基づいてグループ分けした辞書を作ります。

キー
1["a", "b", "c"]
2["ab", "bc"]
3["abc"]

dic2 は array の各要素の先頭の文字を求めて、

要素"a""ab""abc""b""bc""c"
先頭の文字"a""a""a""b""b""c"

その結果に基づいてグループ分けした辞書を作ります。

キー
"a"["a", "ab", "abc"]
"b"["b", "bc"]
"c"["c"]

辞書は順不同なことにご注意ください。また、{ plan -> Date in} の部分は trailing closure と言って、Dictionary(grouping:by:) の by 引数となります。

次の reduce ですが…、reduceは可読性が悪くループで置き換え可能なので使うべきではない - Qiita と思います。今回のコードでは、以下の mapValues と同じ結果になるはずです。つまり、値の配列それぞれを、時刻順にソートしています。

swift

1 .mapValues { value in 2 value.sorted { $0.date < $1.date } 3 }

という訳で、schedules は最終的に次のような辞書になります。

キー
2018/01/01[Plan(name: "休み", detail: "実家で過ごす", date: 2018/01/01 09:00)]
2018/01/03[Plan(name: "外出", detail: "初詣", date: 2018/01/03 10:00), Plan(name: "仕事", detail: "社内会議", date: 2018/01/03 17:30)]
2018/01/15[Plan(name: "仕事", detail: "Team Meeting", date: 2018/01/15 15:30), Plan(name: "外出", detail: "買い出し", date: 2018/01/15 19:00)]
2018/01/17[Plan(name: "休み", detail: "家族で水族館", date: 2018/01/17 08:00), Plan(name: "休み", detail: "飲み会", date: 2018/01/17 20:00)]

⇨ようやく馴染みのあるtableviewに今まで定義してきたコードを落とし込む集大成の部分です。日付順を保持した配列「dateOrder = Array(schedules.keys).sorted { $0 < $1 }」をtableviewのsectionのindexpath(0,1,2,3・・・)で全てピックアップして定数targetDateに入れ込む。

「全てピックアップ」ではありません。tableView(_:,numberOfRowsInSection:) は指定されたセクション番号に項目がいくつあるかを返すメソッドで、一回の呼び出しでは一つのセクションに関する問い合わせが来ます。dateOrder は [2018/01/01, 2018/01/03, 2018/01/15, 2018/01/17] な配列なので、section が 0 の場合は targetDate は 2018/01/01 になります。

次の return ではまず schedules から 2018/01/01 の予定の配列、つまり [Plan(name: "休み", detail: "実家で過ごす", date: 2018/01/01 09:00)] を取り出してその長さ、つまりその日の予定の個数 1 を返します。section が 1 〜 3 の場合は同様にして 2 を返します。

その後、返り値でschedules[targetDate, default: []]として使用しているところまで普通の読み方で追いかけたのですが、default:が今いちよくわかりませんでした。

辞書の値にアクセスする場合、キーが存在しない値だったらどうするかという問題があります。例えば 2018/01/02 には予定がありませんよね。その場合にはデフォルト値として空の配列を返すというのが default: [] の意味になります。(実際には dateOrder には 2018/01/02 は存在しないので、targetDate が 2018/01/02 になることはありませんが、コンパイラはそんな事情は知らないので。)

⇨cellの内容を定義する部分で全体的にそこまで難しくは感じませんでしたが、schedules[targetDate]?[indexPath.row]のsectionとrowの間に使われている「?」がどういう意味なのか単純に引っかかりました。

上では default: [] が使われてましたが、ここでは使ってません。この場合、辞書の値にアクセスした結果はオプショナルと言って、nil という何もないことを表す値かもしれません。で、? は nil だった場合にはその後ろ ([indexPath.row]) を無視して nil にする、という意味になります。さらに guard let なので、else { return cell } の中身が実行されます。


以上、長々と解説してみましたが、たぶんまだまだ分からないことだらけだと思います。今回のソースは Swift のさまざまな言語機能を活用した応用篇なので、「質問への追記・修正の依頼」欄にも書かれている通り、まずは Swift の基礎を学ぶのが良いと思います。その際、ネットの記事だけでは体系的な知識を得るのは難しいので、ちゃんとした文法書を読むことをお勧めします。

英語が読めるなら、Apple が出している Swift の本 を読むのが一番良いでしょう。無料だし、Apple Books 版もあります。
日本語がよければ、詳解 Swift 第5版 または [増補改訂第3版]Swift実践入門 が良いと思います。

投稿2021/02/09 00:59

hoshi-takanori

総合スコア7899

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

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

maplemee

2021/02/10 04:12

基礎的な質問にも関わらず、丁寧な回答を付けてくださり大変感謝です...。 またお恥ずかしながら、冒頭から完全に間違えて質問しておりましたm(_ _)m ([Date: [Plan]]() ですが、これはキーが Date 型、値が Plan 構造体で日付がキーになっていたのですね。 そのあとのDictionary(grouping:by:) も事例付きで解説していただき、とてもよく分かりました。 そして空の存在(オプショナル)は知ってはいたのですが、コードを考える上でまだまだ無視して考える悪い癖があり、今回の解説のコメントでdefault: []及び?(nil)さらに guard let ~ else { return cell } も含めて空の存在を念頭にコードを読む・考える勉強になりました。 詳解 Swift 第5版 は購入して持っておりましたが、初学者の自分には学習量も多く載ってる言葉もまだ理解できなかった為あまり開くこともなく本棚に置いたままでしたが、今後はこちらの本で解決できないか読み返して活かしていこうと思います。ありがとうございました。
hoshi-takanori

2021/02/10 05:32

細かいツッコミですみませんが、[Date: [Plan]] は「キーが Date で値が [Plan] すなわち『Plan の配列』の辞書」です。「キーが Date で値が Plan の辞書」は [Date: Plan] になります。また、配列や辞書の型の後ろに () をつけると空 (要素 0 個) のインスタンスになります。つまり、[Plan] は Plan の配列の型そのもの、[Plan]() は Plan の空の配列、[Date: [Plan]] は辞書の型、[Date: [Plan]]() は辞書の空の値、です。 オプショナルも超重要です。昔の言語 (Objective-C とか) にはこれがなくて、あると思い込んでいたオブジェクトが実は存在しないことによるクラッシュの原因になってました。今でも ? や ! で無視することも可能ですが、if let や guard let を適切に使うことは大切です。 詳解 Swift はお持ちだったんですね。確かに、文法書だけ読んでも無味乾燥で読み進めるのが辛いと思いますが、今回の質問のような具体的な事例を思い浮かべて、実際に Playground で実験しながら読めば徐々に頭に入ってくると思います。がんばってください。
maplemee

2021/02/20 00:52

ご教授と具体的なアドバイスまでして下さり、ありがとうございます m(_ _)m
guest

0

基礎知識の方をもう一度勉強しようと思います。

投稿2021/02/09 00:30

maplemee

総合スコア16

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問