質問編集履歴
6
誤字
title
CHANGED
File without changes
|
body
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
## お願い
|
2
2
|
|
3
|
+
本問は解決致しましたので、
|
3
4
|
[**こちら**](https://teratail.com/questions/291397)に質問を移動しました。
|
4
5
|
|
5
6
|
|
5
誤字
title
CHANGED
File without changes
|
body
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## お願い
|
2
|
+
|
3
|
+
[**こちら**](https://teratail.com/questions/291397)に質問を移動しました。
|
4
|
+
|
5
|
+
|
1
6
|
## やりたいこと
|
2
7
|
|
3
8
|
テキスト文字列を`Constants.swift`(モデル)に保存して、
|
4
SearchRootVCの追記
title
CHANGED
File without changes
|
body
CHANGED
@@ -102,5 +102,189 @@
|
|
102
102
|
// 以下、省略。
|
103
103
|
```
|
104
104
|
|
105
|
+
```
|
106
|
+
// SearchRootVC
|
107
|
+
|
108
|
+
import UIKit
|
109
|
+
|
110
|
+
class SearchRootVC: UITableViewController, UISearchBarDelegate {
|
111
|
+
|
112
|
+
@IBOutlet weak var searchBar: UISearchBar!
|
113
|
+
|
114
|
+
var repoToPass: Int!
|
115
|
+
|
116
|
+
var task: URLSessionTask?
|
117
|
+
var repo: [[String: Any]] = []
|
118
|
+
|
119
|
+
override func viewDidLoad() {
|
120
|
+
super.viewDidLoad()
|
121
|
+
setupTableView()
|
122
|
+
tableViewBgImage()
|
123
|
+
}
|
124
|
+
|
125
|
+
// 以下3つ、元から用意されているsearchBar関数名なので、変更NG。
|
126
|
+
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
|
127
|
+
searchBar.text = ""
|
128
|
+
searchBar.autocapitalizationType = .none // 検索時、先頭を小文字で始める
|
129
|
+
return true
|
130
|
+
}
|
131
|
+
|
132
|
+
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
133
|
+
task?.cancel()
|
134
|
+
}
|
135
|
+
|
136
|
+
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
137
|
+
// キーボード非表示
|
138
|
+
searchBar.resignFirstResponder()
|
139
|
+
|
140
|
+
// MARK: - 不用意なIUOを削除。
|
141
|
+
// let query = searchBar.text!
|
142
|
+
guard let query = searchBar.text, query.isEmpty == false else {
|
143
|
+
print("検索文字がない")
|
144
|
+
return
|
145
|
+
}
|
146
|
+
|
147
|
+
// word, completion, errorHandler 3つの引数をメソッドに渡す。
|
148
|
+
task = SearchAPI.getRandomRepoUrlSession(query, completionHandler: { items in
|
149
|
+
self.repo = items
|
150
|
+
// DispatchQueue で一つ以上のタスクを管理し、async で複数のAPIの非同期通信を実行。
|
151
|
+
DispatchQueue.main.async {
|
152
|
+
// UIを更新する処理
|
153
|
+
self.tableView.reloadData()
|
154
|
+
}
|
155
|
+
}, errorHandler: { error in
|
156
|
+
debugPrint(error?.localizedDescription ?? "")
|
157
|
+
})
|
158
|
+
}
|
159
|
+
|
160
|
+
func setupTableView() {
|
161
|
+
// UISearchBarのdelegateプロパティに、self(=SearchRootVC)を代入。
|
162
|
+
searchBar.delegate = self
|
163
|
+
}
|
164
|
+
|
165
|
+
// MARK: - TableView で背景画像を表示。
|
166
|
+
func tableViewBgImage() {
|
167
|
+
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.tableView.frame.width, height: self.tableView.frame.height))
|
168
|
+
let image = UIImage(named: "bg_right1")
|
169
|
+
|
170
|
+
imageView.image = image
|
171
|
+
imageView.alpha = 0.8
|
172
|
+
|
173
|
+
self.tableView.backgroundView = imageView
|
174
|
+
}
|
175
|
+
|
176
|
+
// MARK: - // Segueが実行される前に呼び出される。
|
177
|
+
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
178
|
+
// Segueの識別子確認
|
179
|
+
if segue.identifier == Segues.toProfileDetail {
|
180
|
+
// 遷移先ViewCntrollerの取得
|
181
|
+
if let detailVC = segue.destination as? ProfileDetailVC {
|
182
|
+
// 値の設定
|
183
|
+
detailVC.selectedUser = self
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
|
190
|
+
// extension
|
191
|
+
|
192
|
+
extension SearchRootVC {
|
193
|
+
|
194
|
+
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
195
|
+
return repo.count
|
196
|
+
}
|
197
|
+
|
198
|
+
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
199
|
+
|
200
|
+
// dequeueReusableCellで、セルを再利用。
|
201
|
+
// nilを返さない為、オプショナルバインディングは不要。
|
202
|
+
|
203
|
+
// MARK: - セルの再利用
|
204
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "RepositoryCell", for: indexPath)
|
205
|
+
|
206
|
+
let userRepo = repo[indexPath.row]
|
207
|
+
|
208
|
+
// MARK: - nil回避しつつ、nilの場合は「None」を表示。
|
209
|
+
if let userName = userRepo[ApiKey.userName] as? String {
|
210
|
+
cell.textLabel?.text = userName
|
211
|
+
} else {
|
212
|
+
cell.textLabel?.text = "--None--"
|
213
|
+
}
|
214
|
+
// カスタムクラスの参照を修正。(-> detailTextLabelの表示)
|
215
|
+
if let language = userRepo[ApiKey.language] as? String {
|
216
|
+
cell.detailTextLabel?.text = language
|
217
|
+
} else {
|
218
|
+
cell.detailTextLabel?.text = "--None--"
|
219
|
+
}
|
220
|
+
|
221
|
+
return cell
|
222
|
+
}
|
223
|
+
|
224
|
+
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
225
|
+
repoToPass = indexPath.row
|
226
|
+
performSegue(withIdentifier: Segues.toProfileDetail, sender: self)
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
// MARK: - Cellの備忘録
|
231
|
+
// register() ... 用意したviewをcellのテンプレートとして登録するメソッドであり、cellの再利用に必要。
|
232
|
+
// Right Detail を表示は、コード不可 StoryBoard可なので、register()は使用しない。
|
233
|
+
|
234
|
+
// MARK: - クラスメソッドとして定義
|
235
|
+
|
236
|
+
class SearchAPI {
|
237
|
+
// staticとして宣言すると、クラスのインスタンス化が不要。
|
238
|
+
static func getRandomRepoUrlSession(_ query: String, completionHandler completion: @escaping ([[String: Any]]) -> (), errorHandler: @escaping (Error?) -> ()) -> URLSessionTask? {
|
239
|
+
|
240
|
+
// MARK: - 下記の部分はメソッドに渡す前に処理しておく
|
241
|
+
// let word = searchBar.text!
|
242
|
+
// guard let word = searchBar.text, word.isEmpty == false else {
|
243
|
+
// print("検索文字がない")
|
244
|
+
// return
|
245
|
+
// }
|
246
|
+
|
247
|
+
let repositoryUrl = githubBaseUrl + query
|
248
|
+
|
249
|
+
guard let url = URL(string: repositoryUrl) else { return nil }
|
250
|
+
|
251
|
+
let task = URLSession.shared.dataTask(with: url) { (data, responce, error) in
|
252
|
+
|
253
|
+
guard error == nil else {
|
254
|
+
// MARK: - 処理をクロージャに任せる
|
255
|
+
errorHandler(error)
|
256
|
+
return
|
257
|
+
}
|
258
|
+
|
259
|
+
guard let data = data else { return }
|
260
|
+
|
261
|
+
// try!は、例外が発生したときにはクラッシュするので修正。(-> エラーが起こり得ないケースでのみ使用可)
|
262
|
+
// try?で例外を安全に無視できるが、エラーを表示するため do-catch を使用。
|
263
|
+
|
264
|
+
do {
|
265
|
+
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
|
266
|
+
if let items = json?["items"] as? [[String: Any]] {
|
267
|
+
// MARK: - 処理をクロージャに任せる
|
268
|
+
completion(items)
|
269
|
+
// MARK: - 以下の処理はクロージャに任せる
|
270
|
+
// self.repo = items
|
271
|
+
// // DispatchQueue で一つ以上のタスクを管理し、async で複数のAPIの非同期通信を実行。
|
272
|
+
// DispatchQueue.main.async {
|
273
|
+
// // UIを更新する処理
|
274
|
+
// self.tableView.reloadData()
|
275
|
+
// }
|
276
|
+
}
|
277
|
+
} catch {
|
278
|
+
// MARK: - 処理をクロージャに任せる
|
279
|
+
errorHandler(error)
|
280
|
+
}
|
281
|
+
}
|
282
|
+
task.resume()
|
283
|
+
|
284
|
+
return task
|
285
|
+
}
|
286
|
+
}
|
287
|
+
```
|
288
|
+
|
105
289
|
質問は以上です。
|
106
290
|
お時間あるときに、ご返信頂けましたら幸いです????
|
3
書式の改善。
title
CHANGED
File without changes
|
body
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
テキスト文字列を`Constants.swift`(モデル)に保存して、
|
4
4
|
`ProfileDetailVC`の`setupUI()`から参照したい。
|
5
5
|
|
6
|
-
(`struct`を使用しているので、「値渡し」?というものでしょうか????)
|
7
|
-
|
8
6
|
## エラー
|
9
7
|
|
10
8
|
```
|
2
誤字
title
CHANGED
File without changes
|
body
CHANGED
@@ -28,10 +28,9 @@
|
|
28
28
|
let githubBaseUrl = "https://api.github.com/search/repositories?q="
|
29
29
|
|
30
30
|
var selectedUser: SearchRootVC!
|
31
|
+
// 以下でエラー
|
31
|
-
let repo = selectedUser.repo[selectedUser.RepoToPass]
|
32
|
+
let repo = selectedUser.repo[selectedUser.RepoToPass] // Cannot assign to property: 'selectedUser' is immutable
|
32
33
|
|
33
|
-
// static var(let)
|
34
|
-
//「静的変数」...全てのプログラムが終了するまで、一貫してその値を保持し続ける変数。 初期化は1度のみ。
|
35
34
|
|
36
35
|
struct Segues {
|
37
36
|
static let toProfileDetail = "Detail"
|
1
追記
title
CHANGED
File without changes
|
body
CHANGED
@@ -23,15 +23,38 @@
|
|
23
23
|
```swift
|
24
24
|
// Constants.swift
|
25
25
|
|
26
|
+
import UIKit
|
27
|
+
|
28
|
+
let githubBaseUrl = "https://api.github.com/search/repositories?q="
|
29
|
+
|
26
30
|
var selectedUser: SearchRootVC!
|
27
|
-
// 以下、エラー。
|
28
|
-
let repo = selectedUser.repo[selectedUser.RepoToPass]
|
31
|
+
let repo = selectedUser.repo[selectedUser.RepoToPass]
|
29
32
|
|
30
|
-
|
33
|
+
// static var(let)
|
34
|
+
//「静的変数」...全てのプログラムが終了するまで、一貫してその値を保持し続ける変数。 初期化は1度のみ。
|
31
35
|
|
36
|
+
struct Segues {
|
37
|
+
static let toProfileDetail = "Detail"
|
38
|
+
}
|
39
|
+
|
40
|
+
struct Identifiers {
|
41
|
+
static let repositoryCell = "Repository"
|
42
|
+
}
|
43
|
+
|
44
|
+
struct ApiKey {
|
45
|
+
static let language = "language"
|
46
|
+
static let stars = "stargazers_count"
|
47
|
+
static let watchers = "wachers_count"
|
48
|
+
static let forks = "forks_count"
|
49
|
+
static let issues = "open_issues_count"
|
50
|
+
static let userName = "full_name"
|
51
|
+
static let user = "owner"
|
52
|
+
static let imgUrl = "avatar_url"
|
53
|
+
}
|
54
|
+
|
32
55
|
struct repoTxt {
|
33
56
|
static let title = repo[ApiKey.userName] as? String
|
34
|
-
|
57
|
+
|
35
58
|
static let language = "Written in (repo["language"] as? String ?? "")"
|
36
59
|
static let stars = "(repo["stargazers_count"] as? Int ?? 0) stars"
|
37
60
|
static let watchers = "(repo["wachers_count"] as? Int ?? 0) watchers"
|