Retinaディスプレイの品質を損なうことなくUIViewをUIImageにキャプチャする方法


303

私のコードは通常のデバイスでは問題なく動作しますが、Retinaデバイスでぼやけた画像を作成します。

誰かが私の問題の解決策を知っていますか?

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

ぼやけた。私には正しいスケールが失われたようです...
ダニエル

私も。同じ問題を満たしました。
RainCast 2016

結果画像はどこに表示していますか?他の人が説明したように、あなたはあなたのデバイスがスケール2または3を持っている間、あなたはスケール1で画像にビューをレンダリングしていると思います。これは品質の低下ですが、ぼやけるべきではありません。ただし、この画像を(同じ画面で)スケール2のデバイスでもう一度見ると、スクリーンショットではピクセルあたり2ピクセルを使用して、低解像度から高解像度に変換されます。このアップスケーリングでは、補間を使用する可能性が最も高く(iOSのデフォルト)、追加のピクセルのカラー値を平均化します。
Hans Terje Bakke

回答:


667

の使用UIGraphicsBeginImageContextからUIGraphicsBeginImageContextWithOptionsこのページに記載されているように)に切り替えます。スケール(3番目の引数)に0.0を渡すと、画面の倍率と等しい倍率を持つコンテキストが得られます。

UIGraphicsBeginImageContextは1.0の固定倍率を使用しているため、iPhone 4でも他のiPhoneとまったく同じ画像が得られます。iPhone 4が暗黙的にスケールアップしたときにフィルターを適用しているのか、それともあなたの脳がそれを拾っているのか、周りのすべてのものよりシャープではないのだろう。

だから、私は推測します:

#import <QuartzCore/QuartzCore.h>

+ (UIImage *)imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

そしてSwift 4では:

func image(with view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    defer { UIGraphicsEndImageContext() }
    if let context = UIGraphicsGetCurrentContext() {
        view.layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        return image
    }
    return nil
}

6
トミーの回答は問題ありませんが#import <QuartzCore/QuartzCore.h>renderInContext:警告を削除するにはインポートする必要があります。
gwdp '10年

2
@WayneLiu 2.0の倍率を明示的に指定できますが、読み込まれたビットマップは@ 2xバージョンではなく標準バージョンになるため、iPhone 4品質の画像を正確に取得できない可能性があります。現在保持している全員UIImageに再読み込みを指示する方法はありませんが、高解像度バージョンを強制する方法がないため、解決策はないと思います(そして、プレで利用可能なRAMが少ないために問題が発生する可能性があります-とにかく網膜デバイス)。
トミー

6
0.0fscaleパラメータにを使用する代わりに、を使用する方が受け入れられますが[[UIScreen mainScreen] scale]、これも機能します。
アダムカーター

20
@Adam Carter scale: The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.それは明示的に文書化されているので、私の意見では0.0fの方が単純で優れています。
cprcrack 2013年

5
この回答はiOS 7では古くなっています。これを行うための新しい「最良の」方法については、私の回答を参照してください。
ディマ

224

現在受け入れられている答えは、少なくともiOS 7をサポートしている場合は古くなっています。

iOS7 +のみをサポートしている場合は、次のように使用する必要があります。

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0f);
    [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

スウィフト4:

func imageWithView(view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    defer { UIGraphicsEndImageContext() }
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
    return UIGraphicsGetImageFromCurrentImageContext()
}

この記事のとおり、新しいiOS7の方法drawViewHierarchyInRect:afterScreenUpdates:は何倍も高速であることがわかりますrenderInContext:基準


5
間違いなく、それdrawViewHierarchyInRect:afterScreenUpdates:ははるかに高速です。InstrumentsでTimeプロファイラを実行しました。私の画像生成は138msから27msになりました。
ThomasCle 2014

9
何らかの理由で、Googleマップマーカーのカスタムアイコンを作成しようとしたときに、これはうまくいきませんでした。私が得たすべては黒い長方形
でした

6
@CarlosPに設定afterScreenUpdates:してみましたYESか?
ディマ

7
afterScreenUpdatesをYESに設定すると、黒い四角形の問題が修正されました
hyouuu

4
黒の画像も受け取っていました。コードを呼び出すviewDidLoadviewWillAppear:、画像が真っ黒であることがわかりました。私はそれをしなければなりませんviewDidAppear:でした。それで、私もついにに戻りましたrenderInContext:
manicaesar 2015年

32

@Dimaソリューションに基づいてSwift拡張機能を作成しました:

extension UIImage {
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img
    }
}

編集:Swift 4の改良版

extension UIImage {
    class func imageWithView(_ view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0)
        defer { UIGraphicsEndImageContext() }
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
        return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
    }
}

使用法:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))  
let image = UIImage.imageWithView(view)

2
アイデアをありがとう!余談ですが、コンテキストを開始した直後に{UIGraphicsEndImageContext()}を延期して、ローカル変数imgを導入する必要を回避することもできます;)
Dennis L

細かい説明と使い方をありがとう!
Zeus

なぜこれはclassビューを取る関数なのですか?
ルドルフアダムコビッチ

これはstatic関数のように機能しますが、オーバーライドする必要がないため、単にのstatic代わりにfuncに変更できますclass
Heberti Almeida

25

iOS 迅速

最新のUIGraphicsImageRendererの使用

public extension UIView {
    @available(iOS 10.0, *)
    public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage {
        let rendererFormat = UIGraphicsImageRendererFormat.default()
        rendererFormat.opaque = isOpaque
        let renderer = UIGraphicsImageRenderer(size: bounds.size, format: rendererFormat)

        let snapshotImage = renderer.image { _ in
            drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
        }
        return snapshotImage
    }
}

@ masaldana2はまだではないかもしれませんが、サポートが終了したり、ハードウェアアクセラレーションによるレンダリングや改善を追加したりしたら、すぐにジャイアンツの肩の上にいることをお勧めします(最後のApple APIを使用した
別名

これrendererを古いものと比較してパフォーマンスがどの程度か知っている人はいdrawHierarchyますか?
Vive

24

@Tommyおよび@Dimaによる回答を改善するには、次のカテゴリを使用して、UIViewを背景を透明にして品質を損なうことなくUIImageにレンダリングします。iOS7での作業。(または、そのメソッドを実装で再利用し、self参照をイメージに置き換えます)

UIView + RenderViewToImage.h

#import <UIKit/UIKit.h>

@interface UIView (RenderToImage)

- (UIImage *)imageByRenderingView;

@end

UIView + RenderViewToImage.m

#import "UIView+RenderViewToImage.h"

@implementation UIView (RenderViewToImage)

- (UIImage *)imageByRenderingView
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

@end

1
afterViewUpdates:YESでdrawViewHierarchyInRectを使用すると、画像のアスペクト比が変更され、画像がゆがみます。
confile 2015

これは、uiviewコンテンツが変更されたときに発生しますか?はいの場合は、このUIImageを再生成してください。電話中なので、自分でテストできません
Glogo 2015

私は同じ問題を抱えており、誰もこれに気づいていないようですが、この問題の解決策を見つけましたか?@confile?
Reza.Ab 2017年

これは私が必要としているものですが、うまくいきませんでした。私は@interfaceとの@implementation両方(RenderViewToImage)を作成#import "UIView+RenderViewToImage.h"してヘッダーに追加したViewController.hので、これは正しい使い方ですか?例えばUIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];
グレッグ

そして、この方法はViewController.m私がレンダリングしようとしたビューでした- (UIView *)testView { UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)]; v1.backgroundColor = [UIColor redColor]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 150, 150)]; v2.backgroundColor = [UIColor blueColor]; [v2 addSubview:v1]; return v2; }
グレッグ

15

スウィフト3

スウィフト3(に基づくソリューションディマの答え UIViewの拡張子を持つ)は、次のようにする必要があります:

extension UIView {
    public func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
        let snapshotImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}

6

新しいiOS 10.0 APIと以前のメソッドをサポートするドロップインSwift 3.0拡張機能。

注意:

  • iOSバージョンチェック
  • コンテキストのクリーンアップを簡略化するための遅延の使用に注意してください。
  • また、ビューの不透明度と現在のスケールを適用します。
  • ラップ解除を使用!してクラッシュを引き起こす可能性のあるものはありません。

extension UIView
{
    public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage?
    {
        if #available(iOS 10.0, *)
        {
            let rendererFormat = UIGraphicsImageRendererFormat.default()
            rendererFormat.scale = self.layer.contentsScale
            rendererFormat.opaque = self.isOpaque
            let renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: rendererFormat)

            return
                renderer.image
                {
                    _ in

                    self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)
                }
        }
        else
        {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, self.layer.contentsScale)
            defer
            {
                UIGraphicsEndImageContext()
            }

            self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)

            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

5

Swift 2.0:

拡張メソッドを使用する:

extension UIImage{

   class func renderUIViewToImage(viewToBeRendered:UIView?) -> UIImage
   {
       UIGraphicsBeginImageContextWithOptions((viewToBeRendered?.bounds.size)!, false, 0.0)
       viewToBeRendered!.drawViewHierarchyInRect(viewToBeRendered!.bounds, afterScreenUpdates: true)
       viewToBeRendered!.layer.renderInContext(UIGraphicsGetCurrentContext()!)

       let finalImage = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()

       return finalImage
   }

}

使用法:

override func viewDidLoad() {
    super.viewDidLoad()

    //Sample View To Self.view
    let sampleView = UIView(frame: CGRectMake(100,100,200,200))
    sampleView.backgroundColor =  UIColor(patternImage: UIImage(named: "ic_120x120")!)
    self.view.addSubview(sampleView)    

    //ImageView With Image
    let sampleImageView = UIImageView(frame: CGRectMake(100,400,200,200))

    //sampleView is rendered to sampleImage
    var sampleImage = UIImage.renderUIViewToImage(sampleView)

    sampleImageView.image = sampleImage
    self.view.addSubview(sampleImageView)

 }

3

Swift 3.0の実装

extension UIView {
    func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
        drawHierarchy(in: bounds, afterScreenUpdates: false)
        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}

3

Swift 3のすべての回答がうまく機能しなかったため、最も受け入れられている回答を翻訳しました。

extension UIImage {
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let img: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    }
}

2

@Dimaからの回答に基づくSwift 4 UIView拡張機能を次に示します。

extension UIView {
   func snapshotImage() -> UIImage? {
       UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
       drawHierarchy(in: bounds, afterScreenUpdates: false)
       let image = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()
       return image
   }
}

2

Swift 5.1の場合、この拡張機能を使用できます。

extension UIView {

    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)

        return renderer.image {
            rendererContext in

            layer.render(in: rendererContext.cgContext)
        }
    }
}

1

UIGraphicsImageRendereriOS 10で導入された比較的新しいAPI です。ポイントサイズを指定して UIGraphicsImageRendererを構築します。imageメソッドはクロージャー引数を取り、渡されたクロージャーを実行した結果のビットマップを返します。この場合、結果は指定された境界内に描画するために縮小された元の画像です。

https://nshipster.com/image-resizing/

したがって、渡すサイズがUIGraphicsImageRendererピクセルではなくポイントであることを確認してください。

画像が予想よりも大きい場合は、サイズを倍率で割る必要があります。


気になるので、手伝ってくれませんか?stackoverflow.com/questions/61247577/…UIGraphicsImageRenderer を使用していますが、問題は、エクスポートされた画像のサイズをデバイス上の実際のビューよりも大きくしたいことです。しかし、希望するサイズでは、サブビュー(ラベル)は十分に鮮明ではないため、品質が低下します。
オンドレイ・コロル


0
- (UIImage*)screenshotForView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // hack, helps w/ our colors when blurring
    NSData *imageData = UIImageJPEGRepresentation(image, 1); // convert to jpeg
    image = [UIImage imageWithData:imageData];

    return image;
}

-2

このメソッドでは、ビューオブジェクトを渡すだけで、UIImageオブジェクトを返します。

-(UIImage*)getUIImageFromView:(UIView*)yourView
{
 UIGraphicsBeginImageContext(yourView.bounds.size);
 [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
 return image;
}

-6

これをメソッドに追加してUIViewカテゴリに

- (UIImage*) capture {
    UIGraphicsBeginImageContext(self.bounds.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}

2
ヒヤさん、これで質問の答えになるかもしれませんが、他のユーザーはあなたほど知識がないかもしれないことに注意してください。このコードが機能する理由について少し説明を追加してみませんか?ありがとう!
Vogel612 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.