ViewをUIImageに変換して端末に保存する

はじめに

スクリーンショットを保存して端末に保存する方法を調べてみました。

画面全体を撮影する場合は深く考える必要がないのですが、特定のViewを撮影したい場合、viewが原点から離れている距離を計算する必要があります。

以下の画像の白点で示した座標ぶんスクリーンショットの撮影位置をずらします。

UIImageへの変換と端末保存

端末に画像を保存する際にはViewをUIImageに変換する必要がありますが、UIGraphicsBeginImageContextWithOptionsを使うことで変換可能です。

以前グレースケール変換を行う記事などで使用しています。

問題はピクセル単位で処理を行ったりすると重くなってしまう点でしたが、今回はViewをそのまま表示するだけなので速度について深く考える必要はなさそうです。

UIImageへの変換

let width = hogeView.frame.width
let height = hogeView.frame.height
let size = CGSize(width: width, height: height)

UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let context: CGContext = UIGraphicsGetCurrentContext()!
self.view.layer.render(in: context)
// ビットマップをUIImageとして取得.
let capturedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()

端末保存

保存処理はこの記事からまるっとコードを拝借しました。

    override func viewDidLoad() {
        ...省略
        UIImageWriteToSavedPhotosAlbum(capturedImage, self, #selector(self.showResultOfSaveImage(_:didFinishSavingWithError:contextInfo:)), nil)
    }

    @objc func showResultOfSaveImage(_ image: UIImage, didFinishSavingWithError error: NSError!, contextInfo: UnsafeMutableRawPointer) {

        var title = "保存完了"
        var message = "カメラロールに保存しました"

        if error != nil {
            title = "エラー"
            message = "保存に失敗しました"
        }

        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)

        // OKボタンを追加
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))

        // UIAlertController を表示
        self.present(alert, animated: true, completion: nil)
    }

permission

  • Privacy – Photo Library Usage Description
  • Privacy – Photo Library Additions Usage Description

実行結果

スクリーンショットを撮影することはできましたが、特定のhogeViewを撮影することはできませんでした。

冒頭で説明した通り、hogeViewの原点からの距離を含めていないので撮影が失敗しています。

原点の移動

まず第一にViewControllerの座標系はULO (左上を原点とし右下に向かう) だということを覚えておく必要があります。

http://www.bitz.co.jp/weblog/?date=20120318-13k

それを踏まえてスクリーンショットの対象となるViewの座標までの距離をtranslateByで移動させた後に変換を行うことで、正確にViewの位置をUIImageに変換することができます。

CGContextMoveToPointの新しいメソッドmoveで始めは動かせると思っていたんですが、これは罠で原点を移動させることができませんでした。

UIGraphicsBeginImageContextWithOptions(hogeView.bounds.size, false, 0.0)
let context: CGContext = UIGraphicsGetCurrentContext()!
//context原点に移動させる
context.translateBy(x: -hogeView.frame.origin.x, y: -hogeView.frame.origin.y)
self.view.layer.render(in: context)
// ビットマップをUIImageとして取得
let capturedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()

写真アプリの中を確認してみると、背景のオレンジの領域を含めずに写真アプリに保存ができました。

コード一覧

class ViewController: UIViewController {

    @IBOutlet weak var hogeView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        UIGraphicsBeginImageContextWithOptions(hogeView.bounds.size, false, 0.0)
        let context: CGContext = UIGraphicsGetCurrentContext()!
        //context原点に移動させる
        context.translateBy(x: -hogeView.frame.origin.x, y: -hogeView.frame.origin.y)
        self.view.layer.render(in: context)
        // ビットマップをUIImageとして取得
        let capturedImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        
        //端末に保存
        UIImageWriteToSavedPhotosAlbum(capturedImage, self, #selector(self.showResultOfSaveImage(_:didFinishSavingWithError:contextInfo:)), nil)
        
    }
    
    @objc func showResultOfSaveImage(_ image: UIImage, didFinishSavingWithError error: NSError!, contextInfo: UnsafeMutableRawPointer) {
        var title = "保存完了"
        var message = "カメラロールに保存しました"

        if error != nil {
            title = "エラー"
            message = "保存に失敗しました"
        }
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)

        // OKボタンを追加
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))

        // UIAlertController を表示
        self.present(alert, animated: true, completion: nil)
    }
}

参考文献

[Swift4] Twitterにスクリーンショットを投稿する方法

【Swift】UIImageViewに表示された画像を端末に保存しよう

UIGraphicsBeginImageContextを脱魔術させる

translateBy(x:y:)

View四隅の座標の取得