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

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

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

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

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

Q&A

解決済

2回答

3951閲覧

【Swift】サーバーから画像を取得する関数でUIImage型がうまく渡せません

KumaChan

総合スコア37

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

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

0グッド

1クリップ

投稿2019/03/19 12:34

編集2019/03/20 03:21

iOSでサーバー上の画像データを取得し、UIImage型で返す関数を作っているのですが、うまく戻り値(UIImage)が渡せません。いま書いているコードは以下の通りです。

import UIKit class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() let pass = "https://*************/photo.jpg" if let result = getImage(pass: pass) { // ①ここだと表示されないけど… imageView.image = result } } // 画像を取得して返す func getImage(pass: String) -> UIImage? { var result = UIImage() if let url = URL(string: pass) { let request = URLRequest(url: url) let session = URLSession.shared let task = session.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in if let data = data, let image = UIImage(data: data) { result = image } } task.resume() } /* ②ここに書くと表示されます DispatchQueue.main.async { self.imageView.image = result } */ return result } }

意図としては、getImage関数に画像パスを投げ、返ってきたUIImageを表示する、というシンプルなものです。ですが、①に書いた場合は表示されず、②の位置に書いた場合にのみ表示されます。返ってきたresultを①の位置でprintすると、一応UIImage型(<UIImage: 0x0000000000>のような表示)になっています。

戻り値をVoidにして②のように関数内で書いてしまってもいいのですが、それだと関数にした意味がなく、ファイルの切り分けができなくなるので…

こんな状況ですが、何かお気付きの点がございましたら、ご教授いただけますと幸いです。
よろしくお願いいたします。

追記

ご回答をもとに、修正してみました。
imageViewを引数として関数へ渡し、クロージャ内で表示させる方法です。
これなら怪しげな戻り値を経由させることなく、処理の汎用化もできそうです。

import UIKit class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // 表示処理を呼ぶ let pass = "https://*************/photo.jpg" getImage(pass: pass, view: imageView) } // 画像を取得して表示 func getImage(pass: String, view: UIImageView) -> Void { if let url = URL(string: pass) { let request = URLRequest(url: url) let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in if let data = data, let image = UIImage(data: data) { DispatchQueue.main.async { view.image = image } } }) task.resume() } } }

今回の質問を通して、dataTaskクロージャの正しい使い方を学ぶことができました。
こんなグズグズですが、わたしと同じように悩んでいるSwift初心者の方のお役に立てましたら幸いです。

今回のまとめ**「dataTaskの完了処理はcompletionHandlerの中に書く!」**
今さらすぎてお恥ずかしい限りですが、精進してまいりたいと思います。

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

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

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

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

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

fuzzball

2019/03/20 00:24

>>②ここに書くと表示されます 本当にそこで表示されますか?場所間違ってませんか?
KumaChan

2019/03/20 01:06

②の場所で表示されますし、dataTaskの下(if let...の中)、task.resumeの上(if let...の外)でも表示されます。 ②の場所で表示されるのがそもそもイレギュラーなのでしょうか…Swift初心者なので基本的なところがわかってなくてすみません。
fuzzball

2019/03/20 01:10

②に書いたときは①をコメントアウトしてるんですよね?
KumaChan

2019/03/20 01:31

説明が至らず、すみません。②を使うときは①はコメントアウトします。
fuzzball

2019/03/20 01:36

では、 ・①は有効にして、さらに print("1:", result) を追加。 ・②は無効にして、代わりに print("2:", result) を追加。 で、出力を教えて下さい。
KumaChan

2019/03/20 01:43

1: <UIImage: 0x600003f172c0>, {0, 0} 2: <UIImage: 0x600003f172c0>, {0, 0} という結果になりました。同じ値が出ているのですが…
fuzzball

2019/03/20 01:52

すみません、一つ忘れてました。 result = image の上に print("3:", result) を追加して出力を教えて下さい。(表示される順番のままに書いて下さい) その後、print文はそのままで、①を無効、②を有効にした状態で同様に出力を教えて下さい。
KumaChan

2019/03/20 01:58

①を有効、②を無効の状態での出力は以下の通りです。 1: <UIImage: 0x600002cf0460>, {0, 0} 2: <UIImage: 0x600002cf0460>, {0, 0} 3: <UIImage: 0x600002cf0460>, {0, 0} ①を無効、②を有効の状態での出力は以下の通りです。 1: <UIImage: 0x6000032003f0>, {0, 0} 2: <UIImage: 0x6000032003f0>, {0, 0} 3: <UIImage: 0x6000032003f0>, {0, 0} いかがでしょうか…
fuzzball

2019/03/20 02:04

上は画像が表示されなくて、下は画像が表示されているのですか?
KumaChan

2019/03/20 02:10

はい。①(戻り値)では表示されず、②(関数内)では表示されます。 とはいえ②の位置もイレギュラーで、本来はdataTaskクロージャの中に書くべきなのでしょうか。
fuzzball

2019/03/20 02:12

UIImageのサイズが (0, 0) なのに画像が表示されているのがおかしいです。(出力の右端は画像サイズです)
fuzzball

2019/03/20 02:16

あ、すみません。分かりました。 >> result = image の上に print("3:", result) 上ではなく下でした。下の行に書いて、もう一度結果を教えて下さい。
KumaChan

2019/03/20 02:18

そうなんですね…こちらではなぜか画像が表示されています。 別のURLパスでもやってみましたが、やはり{0, 0}の出力で画像が表示されていました。 うーん。。。
KumaChan

2019/03/20 02:19

あ、3の値が変わりました!! 1: <UIImage: 0x60000377c380>, {0, 0} 2: <UIImage: 0x60000377c380>, {0, 0} 3: <UIImage: 0x600003770070>, {640, 640} 3の位置でのみ、画像データが正しく取得できているようです。
guest

回答2

0

方法1: KumaChanさんがやりたい方法に一番近いです。
考えられるのはreturn resultは画像が読み込まれてない段階で返ってくると思います。
そして、DispatchQueue.main.async()を忘れないでください。
なしでも動くかま、私の場合[UIImageView.image must be used from main thread only]と言う告知が出てきました。

swift

1 2 override func viewDidLoad() { 3 super.viewDidLoad() 4 5 let pass = "https://www.elastic.co/assets/bltada7771f270d08f6/enhanced-buzz-1492-1379411828-15.jpg" 6 7 getImage(pass: pass) 8 } 9 10 // 画像を取得して返す 11 func getImage(pass: String) { 12 var result = UIImage() 13 if let url = URL(string: pass) { 14 let request = URLRequest(url: url) 15 let session = URLSession.shared 16 let task = session.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in 17 DispatchQueue.main.async() { 18 if let data = data, let image = UIImage(data: data) { 19 result = image 20 self.imageView.image = result 21 } 22 } 23 } 24 task.resume() 25 } 26 } 27}

その以下のコードはただ念のために載せました。

方法2:

swift

1 let pass = "https://www.elastic.co/assets/bltada7771f270d08f6/enhanced-buzz-1492-1379411828-15.jpg" 2 3 let url = URL(string: pass) 4 5 DispatchQueue.global().async { 6 let data = try? Data(contentsOf: url!) //画像は存在するかどうかをチェックしてください: if let check / try-catch 7 DispatchQueue.main.async { 8 self.imageView.image = UIImage(data: data!) 9 } 10 }

方法3

swift

1import UIKit 2 3class ViewController: UIViewController { 4 5 @IBOutlet weak var imageView: UIImageView! 6 var result = UIImage() 7 8 override func viewDidLoad() { 9 super.viewDidLoad() 10 11 let pass = "https://www.elastic.co/assets/bltada7771f270d08f6/enhanced-buzz-1492-1379411828-15.jpg" 12 if let url = URL(string: pass) { 13 // Do any additional setup after loading the view, typically from a nib. 14 downloadImage(from: url) 15 } 16 } 17 18 func downloadImage(from url: URL) { 19 getData(from: url) { data, response, error in 20 guard let data = data, error == nil else { return } 21 DispatchQueue.main.async() { 22 self.imageView.image = UIImage(data: data) 23 } 24 } 25 } 26 27 func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) { 28 URLSession.shared.dataTask(with: url, completionHandler: completion).resume() 29 } 30} 31

投稿2019/03/20 03:08

vanderlvov

総合スコア685

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

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

KumaChan

2019/03/20 03:14

サンプルコードまで書いていただき、ありがとうございます。 おっしゃる通り、最初のコードでは画像取得前に画像表示を試みており、タイミングによって表示されたりされなかったりするようです。 今後は「なんとなく動く」ではなく「ロジカルに動く」アプリをめざしたいと思います。というか、プログラムの基本ですよね… 今後とも、よろしくお願いいたします。
guest

0

ベストアンサー

dataTask(with:completionHandler:)は非同期なので、実行(resume())した直後はまだ処理が完了していません。

getImage(pass:)に、

  • ImageViewを引数で渡す
  • 画像取得後の処理をクロージャで渡す

などで対応できると思います。


②の位置で画像が表示される件ですが、DispatchQueue.main.asyncを使っているために少し時間差が出来ていて、その間に画像の読み込みが完了しているようです。ようするに、たまたま表示されているだけということです。
もっと大きな画像とか、通信状態が悪い環境だと読み込みが間に合わず何も表示されなくなるんじゃないかと思います。


ちなみに、DispatchQueue.main.asyncの処理は、通常、

swift

1if let data = data, let image = UIImage(data: data) { 2 //読み込み完了 3 DispatchQueue.main.async { 4 self.imageView.image = image //画像をセット 5 } 6}

ここに書きます。読み込み完了後に画像をセットする、ということです。

投稿2019/03/20 02:27

fuzzball

総合スコア16731

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

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

KumaChan

2019/03/20 02:32

ありがとうございます。ようやく謎が解けました。 わたしのコードではcompletionHandlerを省略してましたが、本来はその名の通り、完了処理はこの中に書くべきなんですよね。 ②の位置で表示されていたのはおっしゃる通り、たまたまかもしれません。そういえば、ちょっと表示に時間がかかっていました。 本件についてはアドバイスいただいた方法で書き直してみたいと思います。 長時間おつきあいいただき、本当にありがとうございました。 今後とも、よろしくお願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問