49266


ImagePickerControllerで画像を選択後、画像を縮小して表示させようと考えています。  
下記のコードを実行すると縮小の実行後、一瞬HUDが表示されるだけで、処理中にHUDが表示されません。

[code]

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    
        hud.show(in: self.view)
    
        let infoDic = Dictionary(uniqueKeysWithValues: info.map {key, value in (key.rawValue, value)})
        let image = infoDic[UIImagePickerController.InfoKey.originalImage.rawValue] as! UIImage
    
        self.image = image.cropAndShrink()
    
        hud.dismiss()
    
        self.dismiss(animated: true)
    }
    
[/code]

HUDは今はJGProgressHUDを使っています。  
hud.show(in: self.view)をhud.show(in: picker.view)としても結果は変わりませんでした。  
縮小処理中にHUDを表示させたいのですが、どのようにすればよろしいでしょうか？


CC BY-SA 4.0
2018-10-14T08:40:54.887
49262
2018-10-14T14:05:17.373
30511
question
swift swift4 uiimagepickercontroller
ImagePickerController表示中にHUDを表示する方法


iOSでは(実は他の多くのWindow/GUIシステムでも同じですが)、実際のUI画面の更新はイベント処理のメソッドが終わった後(にシステム側に画面更新のための時間が回ってきてから)行われます。

従って、同じイベントメソッドの中で`show`, `dismiss`あたりのメソッドを両方呼んでも、それがどんな表示になるのかは予想できないことになります。

とにかく一旦はイベントメソッドを終了しないといけないので、必然的に裏スレッドで処理を行う、マルチスレッドの知識が必要になります。

あたなの`cropAndShrink()`処理がスレッドセーフに書かれているとしたら、例えばこんな感じになります。

[code]

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    
        hud.show(in: self.view)
    
        guard let image = info[.originalImage] as? UIImage else {
            print("originalImage is not set")
            return
        }
    
        //時間のかかる処理は裏スレッド用のキューで実行
        DispatchQueue.global().async {
            let croppedImage = image.cropAndShrink()
    
            //UIの更新は必ずメインスレッドの中で行う必要がある
            DispatchQueue.main.async {
                self.image = croppedImage
                self.hud.dismiss()
                self.dismiss(animated: true)
            }
        }
    }
    
[/code]

ただし、上に書いたように`cropAndShrink()`がスレッドセーフでないと、この処理だとどこかでメモリが絡んだエラーになるかもしれません。お試しの上、うまくいかない点があるのなら、おしらせください。

* * *

さて、おしらせいただいたので、少々修正します。ご質問では`cropAndShrink()`だったのが、自己回答でお示しいただいた例だと`cropToBounds()`になっていたり、設定先が`self.image`ではなく`self.importedImage`になっていたりしますが、自己回答例の方に合わせることにします。

本当はご質問中のメソッド名・プロパティ名が正しいという場合には、適宜読み替えて下さい。

[code]

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    
        guard let loadedImage = info[.originalImage] as? UIImage else {
            print("originalImage is not set")
            return
        }
    
        hud.show(in: self.view)
        self.dismiss(animated: true)
    
        //HUDの表示が有効になるように後からメインキューで実行することを登録するだけで、今のメソッドは直ちに終了
        DispatchQueue.main.async {
            self.importedImage = loadedImage.cropToBounds()
    
            self.hud.dismiss()
        }
    }
    
[/code]

先ほどの例よりコードが短くなってしまいましたが、なぜ最初からこうしなかったのかというと、「`cropToBounds()`の(あるいは`cropAndShrink()`の)処理中にUIが固まってしまうから」というのが理由です。JGProgressHUDがどのようなものかは知らずに書いているのですが、例えばアニメーションを伴うようなものの場合、「UIが固まる」と言うのは、そのようなアニメーションも表示されなくなることを意味します。

`cropToBounds()`や`cropAndShrink()`がメソッド名の通りに1枚の画像をクロッピングしたり縮小したりするだけの物なら、かなりの高解像度画像でも、ユーザに「待たされた」なんて思わせるほどの時間はかかりません。

つまり上記のコードでも(そうなる原因は元のコードとは別ですが)「一瞬HUDが表示されるだけ」と言うのは変わらないかもしれません。

だとしたら、本末転倒ではあります(「ユーザを待たせるから、その間HUDを表示する」のではなく「HUDを表示するためにユーザを待たせる」ことになる)が、まぁ練習だと割り切って、`DispatchQueue.main...`の部分を以下のように書き換えると良いかもしれません。

[code]

        //HUDの表示が有効になるように0.5秒後からメインキューで実行することを登録するだけで、今のメソッドは直ちに終了
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.importedImage = loadedImage.cropToBounds()
    
            self.hud.dismiss()
        }
    
[/code]

再度お時間をとってお試しいただければ幸いです。

* * *

ただし、練習用ではなく実際のアプリであれば「HUDを表示するためにユーザを待たせる」なんてのは、どう考えてもおかしいので

  * `cropAndShrink()`とか`cropToBounds()`には大した時間がかからない  
⇒そもそもHUDなんて表示せずに処理を進める

(一度平均的にどれくらいの処理時間がかかるか計ってみると良いでしょう。)

  * 画像処理が本当にユーザを待たせるほどの時間がかかる  
⇒画像処理をスレッドセーフにして裏スレッドで実行する

(最初に示したコードの形。HUDが表示されなかったと言うことなので、`self.dismiss(animated:
true)`は移動させた方が良いでしょう。)

の、どちらかの形にした方が良いでしょう。


CC BY-SA 4.0
2018-10-14T10:25:44.363
49266
2018-10-14T14:05:17.373
2018-10-14T14:05:17.373
13972
13972
49262
answer


Timerを使用して複雑なタスクを切り分けて、戻った先のViewControllerでHUDを回すことには成功しました。以下ソースです。

[code]

    class ExampleViewController: UIViewController,  UIImagePickerControllerDelegate, UINavigationControllerDelegate{
        private var isImageLoaded = false
        private var timer: Timer? = nil
        private var loadedImage: UIImage? = nil
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let image = info[.originalImage] {
                self.hud.show(in: self.view)
                isImageLoaded = true
                loadedImage = image
                self.dismiss(animated: true)
            }
        }
        @objc func complicatedTask() {
            if isImageLoaded {
                isImageLoaded = false
                timer?.invalidate()
                self.importedImage = loadedImage.cropToBounds()
                timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(complicatedTask), userInfo: nil, repeats: true)
                self.hud.dismiss()
            }
        }
        override func viewDidLoad() {
            super.viewDidLoad()
            timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(complicatedTask), userInfo: nil, repeats: true)
        }
    }
    
[/code]


CC BY-SA 4.0
2018-10-14T11:45:47.720
49271
2018-10-14T12:54:52.787
2018-10-14T12:54:52.787
30511
30511
49262
answer