質問編集履歴

7

追記

2020/09/15 00:52

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
File without changes
test CHANGED
@@ -188,6 +188,416 @@
188
188
 
189
189
 
190
190
 
191
+ ## 追記
192
+
193
+
194
+
195
+ ```
196
+
197
+ // tableView内にて、定義されています。
198
+
199
+ let userRepo = repo[indexPath.row]
200
+
201
+ ```
202
+
203
+
204
+
205
+ ```
206
+
207
+ // 以下、全体のSearchRootVCです。
208
+
209
+ import UIKit
210
+
211
+
212
+
213
+ class SearchRootVC: UITableViewController, UISearchBarDelegate {
214
+
215
+
216
+
217
+ @IBOutlet weak var searchBar: UISearchBar!
218
+
219
+
220
+
221
+ var repoToPass: Int = 0
222
+
223
+
224
+
225
+ var task: URLSessionTask?
226
+
227
+ var repo: [[String: Any]] = []
228
+
229
+
230
+
231
+ override func viewDidLoad() {
232
+
233
+ super.viewDidLoad()
234
+
235
+ setupTableView()
236
+
237
+ tableViewBgImage()
238
+
239
+ }
240
+
241
+
242
+
243
+ // 以下3つ、元から用意されているsearchBar関数名なので、変更NG。
244
+
245
+ func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
246
+
247
+ searchBar.text = ""
248
+
249
+ searchBar.autocapitalizationType = .none // 検索時、先頭を小文字で始める
250
+
251
+ return true
252
+
253
+ }
254
+
255
+
256
+
257
+ func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
258
+
259
+ task?.cancel()
260
+
261
+ }
262
+
263
+
264
+
265
+ func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
266
+
267
+ // キーボード非表示
268
+
269
+ searchBar.resignFirstResponder()
270
+
271
+
272
+
273
+ // MARK: - 不用意なIUOを削除。
274
+
275
+ // let query = searchBar.text!
276
+
277
+ guard let query = searchBar.text, query.isEmpty == false else {
278
+
279
+ print("検索文字がない")
280
+
281
+ return
282
+
283
+ }
284
+
285
+
286
+
287
+ // word, completion, errorHandler 3つの引数をメソッドに渡す。
288
+
289
+ task = SearchAPI.getRandomRepoUrlSession(query, completionHandler: { items in
290
+
291
+ self.repo = items
292
+
293
+ DispatchQueue.main.async {
294
+
295
+ // UIを更新する処理
296
+
297
+ self.tableView.reloadData()
298
+
299
+ }
300
+
301
+ }, errorHandler: { error in
302
+
303
+ // オプショナルチェイニング
304
+
305
+ debugPrint(error?.localizedDescription ?? "")
306
+
307
+ })
308
+
309
+ }
310
+
311
+
312
+
313
+ // MARK: - オプショナルチェイニング
314
+
315
+ // 値があるとアンラップ。但し、それに続くプロパティやメソッドの戻り値は「オプショナル型」になる。(-> nilの場合 nil を返すため)
316
+
317
+ // nilの場合 nil を返す。それに続く処理をすべてキャンセル (-> なので安全)
318
+
319
+
320
+
321
+ // MARK: - ??
322
+
323
+ // 「??」... nil結合演算子 (nil-coalescing)
324
+
325
+ // A ?? B ... Aに値があるとAをアンラップ。Aが nil だとBを返す。
326
+
327
+
328
+
329
+ func setupTableView() {
330
+
331
+ // delegateは、delegatorに処理を委譲された側
332
+
333
+ searchBar?.delegate = self
334
+
335
+ }
336
+
337
+
338
+
339
+ // MARK: - TableView で背景画像を表示
340
+
341
+ func tableViewBgImage() {
342
+
343
+ let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.width, height: self.tableView.frame.height))
344
+
345
+ let image = UIImage(named: "bg_right1")
346
+
347
+
348
+
349
+ imageView.image = image
350
+
351
+ imageView.alpha = 0.8
352
+
353
+
354
+
355
+ self.tableView.backgroundView = imageView
356
+
357
+ }
358
+
359
+
360
+
361
+ // MARK: - // Segueが実行される前に呼び出される
362
+
363
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
364
+
365
+ // Segueの識別子確認
366
+
367
+ if segue.identifier == Segues.toProfileDetail {
368
+
369
+ // 遷移先ViewCntrollerの取得
370
+
371
+ if let detailVC = segue.destination as? ProfileDetailVC {
372
+
373
+ // 値の設定
374
+
375
+ detailVC.selectedUser = self
376
+
377
+ }
378
+
379
+ }
380
+
381
+ }
382
+
383
+ }
384
+
385
+
386
+
387
+
388
+
389
+ // extension
390
+
391
+
392
+
393
+ extension SearchRootVC {
394
+
395
+
396
+
397
+ override func numberOfSections(in tableView: UITableView) -> Int {
398
+
399
+ return 2 // 検索バー + アイテム
400
+
401
+ }
402
+
403
+
404
+
405
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
406
+
407
+ return repo.count
408
+
409
+ }
410
+
411
+
412
+
413
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
414
+
415
+
416
+
417
+ // dequeueReusableCellで、セルを再利用。
418
+
419
+ // nilを返さない為、オプショナルバインディングは不要。
420
+
421
+
422
+
423
+ // MARK: - セルの再利用
424
+
425
+ let cell = tableView.dequeueReusableCell(withIdentifier: "RepositoryCell", for: indexPath)
426
+
427
+
428
+
429
+ let userRepo = repo[indexPath.row]
430
+
431
+
432
+
433
+ // MARK: - nil回避しつつ、nilの場合は「--None--」を表示
434
+
435
+ if let userName = userRepo[ApiKey.userName] as? String {
436
+
437
+ cell.textLabel?.text = userName
438
+
439
+ } else {
440
+
441
+ cell.textLabel?.text = "--None--"
442
+
443
+ }
444
+
445
+ // カスタムクラスの参照を修正。(-> detailTextLabelの表示)
446
+
447
+ if let language = userRepo[ApiKey.language] as? String {
448
+
449
+ cell.detailTextLabel?.text = language
450
+
451
+ } else {
452
+
453
+ cell.detailTextLabel?.text = "--None--"
454
+
455
+ }
456
+
457
+
458
+
459
+ let cellSelectedBgView = UIView()
460
+
461
+ cellSelectedBgView.backgroundColor = #colorLiteral(red: 0.07544863205, green: 0.2731321021, blue: 0.6389395622, alpha: 0.2611033818)
462
+
463
+ cell.selectedBackgroundView = cellSelectedBgView
464
+
465
+ return cell
466
+
467
+ }
468
+
469
+
470
+
471
+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
472
+
473
+ repoToPass = indexPath.row
474
+
475
+ performSegue(withIdentifier: Segues.toProfileDetail, sender: self)
476
+
477
+ }
478
+
479
+ }
480
+
481
+
482
+
483
+ // MARK: - Cellの備考 (registerとは?)
484
+
485
+ // register() ... 用意したviewをcellのテンプレートとして登録するメソッドであり、cellの再利用に必要。
486
+
487
+ // Right Detail を表示は、コード不可 StoryBoard可なので、register()は使用しない。
488
+
489
+
490
+
491
+ // MARK: - クラスメソッドとして定義
492
+
493
+ class SearchAPI {
494
+
495
+ // staticとして宣言すると、クラスのインスタンス化が不要。
496
+
497
+ static func getRandomRepoUrlSession(_ query: String, completionHandler completion: @escaping ([[String: Any]]) -> (), errorHandler: @escaping (Error?) -> ()) -> URLSessionTask? {
498
+
499
+
500
+
501
+ // MARK: - 下記の部分はメソッドに渡す前に処理しておく
502
+
503
+ // let word = searchBar.text!
504
+
505
+ // guard let word = searchBar.text, word.isEmpty == false else {
506
+
507
+ // print("検索文字がない")
508
+
509
+ // return
510
+
511
+ // }
512
+
513
+
514
+
515
+ let repositoryUrl = githubBaseUrl + query
516
+
517
+
518
+
519
+ guard let url = URL(string: repositoryUrl) else { return nil }
520
+
521
+
522
+
523
+ let task = URLSession.shared.dataTask(with: url) { (data, responce, error) in
524
+
525
+
526
+
527
+ guard error == nil else {
528
+
529
+ // MARK: - 処理をクロージャに任せる
530
+
531
+ errorHandler(error)
532
+
533
+ return
534
+
535
+ }
536
+
537
+
538
+
539
+ guard let data = data else { return }
540
+
541
+
542
+
543
+ // try!は、例外が発生したときにはクラッシュするので修正。(-> エラーが起こり得ないケースでのみ使用可)
544
+
545
+ // try?で例外を安全に無視できるが、エラーを表示するため do-catch を使用。
546
+
547
+
548
+
549
+ do {
550
+
551
+ let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
552
+
553
+ if let items = json?["items"] as? [[String: Any]] {
554
+
555
+ // MARK: - 処理をクロージャに任せる
556
+
557
+ completion(items)
558
+
559
+ // MARK: - 以下の処理はクロージャに任せる
560
+
561
+ // self.repo = items
562
+
563
+ // // DispatchQueue で一つ以上のタスクを管理し、async で複数のAPIの非同期通信を実行。
564
+
565
+ // DispatchQueue.main.async {
566
+
567
+ // // UIを更新する処理
568
+
569
+ // self.tableView.reloadData()
570
+
571
+ // }
572
+
573
+ }
574
+
575
+ } catch {
576
+
577
+ // MARK: - 処理をクロージャに任せる
578
+
579
+ errorHandler(error)
580
+
581
+ }
582
+
583
+ }
584
+
585
+ task.resume()
586
+
587
+
588
+
589
+ return task
590
+
591
+ }
592
+
593
+ }
594
+
595
+
596
+
597
+ ```
598
+
599
+
600
+
191
601
  質問は以上です。
192
602
 
193
603
  お時間あるときに、ご返信頂けましたら幸いです????

6

書式の改善。

2020/09/15 00:52

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
@@ -1 +1 @@
1
- アイテムの数だけ セルが表示されるか、UnitTestした
1
+ UnitTestにて、変数が参照できな【Swift】
test CHANGED
File without changes

5

書式の改善。

2020/09/14 23:50

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
File without changes
test CHANGED
@@ -156,7 +156,7 @@
156
156
 
157
157
 
158
158
 
159
- -extension内で定義されているからか、定数`userRepo`を参照できず
159
+ - extension内で定義されているからか、定数`userRepo`を参照できず
160
160
 
161
161
  `// Value of type 'SearchRootVC' has no member 'userRepo'`のエラーがでますが、
162
162
 
@@ -168,7 +168,7 @@
168
168
 
169
169
 
170
170
 
171
- そもそも、以下の考え方が間違っていたら、
171
+ - そもそも、以下の考え方が間違っていたら、
172
172
 
173
173
  別の正しいテストコードを教えて頂きたいです。
174
174
 

4

書式の改善。

2020/09/14 23:33

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
File without changes
test CHANGED
@@ -152,9 +152,39 @@
152
152
 
153
153
 
154
154
 
155
+ ## 試したこと
155
156
 
156
157
 
157
158
 
159
+ -extension内で定義されているからか、定数`userRepo`を参照できず
160
+
161
+ `// Value of type 'SearchRootVC' has no member 'userRepo'`のエラーがでますが、
162
+
163
+
164
+
165
+ `let userRepo = repo[indexPath.row]`をextensionの外に定義しようとすると
166
+
167
+ `indexPath`が無い為、できません。
168
+
169
+
170
+
171
+ そもそも、以下の考え方が間違っていたら、
172
+
173
+ 別の正しいテストコードを教えて頂きたいです。
174
+
175
+
176
+
177
+
178
+
179
+ ```swift
180
+
181
+ // ダミー アイテム
182
+
183
+ let testItems = ["item-1", "item-2"]
184
+
185
+ vc.userRepo = testItems
186
+
187
+ ```
158
188
 
159
189
 
160
190
 

3

書式の改善。

2020/09/14 23:33

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
File without changes
test CHANGED
@@ -52,7 +52,7 @@
52
52
 
53
53
  let testItems = ["item-1", "item-2"]
54
54
 
55
- vc.userRepo = testItems
55
+ vc.userRepo = testItems // Value of type 'SearchRootVC' has no member 'userRepo'
56
56
 
57
57
 
58
58
 

2

書式の改善。

2020/09/14 23:21

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
File without changes
test CHANGED
@@ -50,7 +50,7 @@
50
50
 
51
51
  // ダミー アイテム
52
52
 
53
- let testItems = [[String: Any]]
53
+ let testItems = ["item-1", "item-2"]
54
54
 
55
55
  vc.userRepo = testItems
56
56
 

1

書式の改善。

2020/09/14 23:20

投稿

kazuki_user
kazuki_user

スコア147

test CHANGED
File without changes
test CHANGED
@@ -46,15 +46,13 @@
46
46
 
47
47
  let vc = SearchRootVC()
48
48
 
49
- var repo: [[String: Any]] = []
50
-
51
49
 
52
50
 
53
51
  // ダミー アイテム
54
52
 
55
- let testItems = [[String: Any]] // Expected member name or constructor call after type name
53
+ let testItems = [[String: Any]]
56
54
 
57
- vc.repo.count = testItems
55
+ vc.userRepo = testItems
58
56
 
59
57
 
60
58