質問: UIImage(data: self.imageData! as Data) return testCell } ```
回答: 簡単に言うと、簡単ではありません...。 * 非同期処理をうまく使うコツは「完了を待つ」のではなく、「完了してから実行されるところで結果を使う」ように頭を切り替えることです。 あなたのご質問の文面から言うと、「4の処理を遅延させ」るのではなく、「3.URLにアクセスして画像を取得」が「完了してから実行されるところで結果(その画像)を使う」と言うことになります。 つまり頭を切り替えて、取得した画像は完了ハンドラー(あなたのコードの`{ error in ... }`)の中で使えば良いと言うことになります。 もちろん`collectionView(_:cellForItemAt:)`の結果は画像取得が完了するまで返さないなんてことはできませんから、仮の画像を入れたCellをすぐに返すと言う必要があります。 * Cellは再利用されるのでその考慮も必要です `UITableViewCell`や`UICollectionViewCell`のインスタンスは出来るだけ再利用するように動きますので、画像の取得が完了した時には、その画像を設定したかったCellはもう再利用されて別の用途に使われているかもしれません。 (たまに`UITableView`を使った画面を高速にスクロールすると、本来とは違う画像が表示されるアプリがあったりしますが、この辺の考慮が足りない実装になっている可能性が高いと思われます。) 何らかの方法で、画像を設定したかったCellが再利用されていないことを確認しないといけません。 以上のような内容を踏まえてあなたのコードを書き直すと、次のような感じになります。NCMBを簡単に試すことはできないので、コンパイルが通る、程度のチェックしかしていませんから、このような流れになる、という雰囲気を理解するためのものと思ってください。 ``` func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { // Cell はストーリーボードで設定したセルのID let testCell:UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) // Tag番号を使ってImageViewのインスタンス取得 <- ここで「生成」するのではない let imageView = testCell.contentView.viewWithTag(1) as! UIImageView // クラスのNCMBObjectを作成⇒guard letを使えば後の`?`や`!`が不要になる guard let obj = NCMBObject(className: "image") else { //NCMBが利用できない場合の画像もアプリ内に用意する imageView.image = UIImage(named: "Unavailable") //たとえ何かの異常があってもnon-nilのUICollectionViewCellを返さないとコンパイルできない return testCell } //Loading用の仮イメージをアプリ内に用意して、ロード中はそちらを表示させる imageView.image = UIImage(named: "Loading") // objectIdプロパティを設定 obj.objectId = "xxxxxxxxxxxx" //画像ローディングの管理IDを作成する(ユニークな値であれば何でも良い、arc4random()は実用的に十分) let loadingId = Int(Int32(bitPattern: arc4random())) //<-32 bit環境でも動くようにIntに変換している //管理IDをCellにも設定、tagは他用途に使う可能性もあるのでできれば専用カスタムCellにloadingId属性を持たせた方が良い testCell.tag = loadingId // 設定されたobjectIdを元にデータストアからデータを取得 obj.fetchInBackground { error in if let error = error { // 取得に失敗した場合の処理 print(error) } else { // 取得に成功した場合の処理 // (例)取得したデータの出力 let imageUrlString = obj.object(forKey: "imageUrl") as! String print(imageUrlString) //Cellは再利用されるので、load完了した時にはもう別用途に使われているかもしれないからそのチェック guard testCell.tag == loadingId else { print("Cell seems to be reused") return } //外部から取得した文字列はURLとして不正かもしれないので、それもチェックしておく guard let url = URL(string: imageUrlString) else { print("Invalid imageUrl") return } //イメージデータのロードも完了ハンドラーの中でやってしまう(mappedIfSafeは外部URLからのloadには使用しない) let imageData: Data do { imageData = try Data(contentsOf: url) } catch { print("Error: can't create image.") return } //取得したデータをUIImageに変換、これも失敗する可能性があるのでguard-letでチェック guard let image = UIImage(data: imageData) else { print("Invalid imageData") return } //ここまで無事に終了したら目的のセルに画像を設定する DispatchQueue.main.async { imageView.image = image //Swiftの配列はスレッドセーフではないので配列の更新は必ずメインスレッドで行うことにした方が安全 if self.imageUrlArray.count <= indexPath.row { self.imageUrlArray.append(contentsOf: (self.imageUrlArray.count...indexPath.row).map{_ in ""}) } //非同期処理は順不同で終了するので、結果を配列の最後に入れても正しい順序にはならない self.imageUrlArray[indexPath.row] = imageUrlString } } } //Loadが完了したら画像を設定するよ、という準備が済んだら(Loading画像の設定された)Cellを返す return testCell } ``` 細かい部分あれこれ手直しが必要かもしれませんが、あなた自身の環境と目的に合わせて修正しながらお試しください。 画像の遅延ロードをサポートするようなサードパーティ製のライブラリもあるようです(残念ながら私は使ったことがないので詳しくはわかりません)ので、そう言ったものを調べてみるのも良いかもしれません。