しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

【iOS】ファイルから読んだUIImageが表示されない問題対応

ファイルから画像を読み込むアプリでうまく画像表示できない事がありました。

class ViewController: UIViewController {
    var imageView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 画像をファイルに保存
        let path = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/test"
        if let originalImage = UIImage(named: "image") {
            let path = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/test"
            let data = UIImagePNGRepresentation(originalImage)
            try? data?.write(to: URL(fileURLWithPath: path), options: [.atomic])
        }
        
        // 画像をファイルから読み込み
        let image = UIImage(contentsOfFile: path)
        imageView = UIImageView(image: image)
        imageView.backgroundColor = UIColor.white
        view.addSubview(imageView)
    }
}

下は正常な表示です。

f:id:llcc:20170717221827p:plain

画像読み込みが失敗したのは下のようなケースです。
画像読み込み後に元ファイルを削除すると、画像が表示されなくなりました。

class ViewController: UIViewController {
    var imageView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 省略…
        
        // ファイルを削除
        try? FileManager.default.removeItem(atPath: path)
    }
}

次のように何も表示されなくなります。

f:id:llcc:20170717222015p:plain

ファイルの削除をviewDidAppearにしたら問題なく画像表示できました。

class ViewController: UIViewController {
    var imageView = UIImageView()
    
    // 省略…
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let path = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/test"
        try? FileManager.default.removeItem(atPath: path)
    }
}

viewWillAppearでファイルを削除した場合は画像表示できませんでした。
おそらく画面描画のタイミングでファイルを読み込んでいるため、画面描画前であるviewWillAppearで削除すると画像読み込みが失敗するのだと思います。

class ViewController: UIViewController {
    var imageView = UIImageView()
    
    // 省略…
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let path = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/test"
        try? FileManager.default.removeItem(atPath: path)
    }
}

ちなみに公式ドキュメントによると、ファイルのリロード処理が走る事があるようです。
そのため、viewDidAppearで削除してもリロード処理が走ったら画像が表示されない可能性があります。

init(contentsOfFile:) - UIImage | Apple Developer Documentation

Discussion This method loads the image data into memory and marks it as purgeable. If the data is purged and needs to be reloaded, the image object loads that data again from the specified path.

対策

今回は、Data型を経由する事で対策しました。
次のようにファイルからData形式で読み込んで、それをUIImageに渡しました。

class ViewController: UIViewController {
    var imageView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 画像をファイルに保存
        let path = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/test"
        if let originalImage = UIImage(named: "image") {
            let path = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/test"
            let data = UIImagePNGRepresentation(originalImage)
            try? data?.write(to: URL(fileURLWithPath: path), options: [.atomic])
        }
        
        // 画像をファイルから読み込み
        if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
            let image = UIImage(data: data)
            imageView = UIImageView(image: image)
            imageView.backgroundColor = UIColor.white
            view.addSubview(imageView)
        }
        
        // ファイルを削除
        try? FileManager.default.removeItem(atPath: path)
    }
}

これで無事に画像表示できるようになりました。

f:id:llcc:20170717223501p:plain