Cara menangkap UIView ke UIImage tanpa kehilangan kualitas pada tampilan retina


303

Kode saya berfungsi dengan baik untuk perangkat normal tetapi membuat gambar buram pada perangkat retina.

Adakah yang tahu solusi untuk masalah saya?

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

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

buram. Sepertinya skala yang tepat hilang ...
Daniel

saya juga. bertemu masalah yang sama.
RainCast

Di mana Anda menampilkan gambar hasil? Seperti yang dijelaskan orang lain, saya yakin Anda menampilkan tampilan ke gambar dengan skala 1, sedangkan perangkat Anda memiliki skala 2 atau 3. Jadi Anda menurunkan skala ke resolusi yang lebih rendah. Ini adalah kehilangan kualitas tetapi tidak harus kabur. Tetapi ketika Anda melihat gambar ini lagi (pada layar yang sama?) Pada perangkat dengan skala 2 itu akan mengkonversi dari resolusi rendah ke yang lebih tinggi, menggunakan 2 piksel per piksel dalam tangkapan layar. Peningkatan ini menggunakan interpolasi yang paling mungkin (default pada iOS), rata-rata nilai warna untuk piksel tambahan.
Hans Terje Bakke

Jawaban:


667

Beralih dari penggunaan UIGraphicsBeginImageContextke UIGraphicsBeginImageContextWithOptions(seperti yang didokumentasikan pada halaman ini ). Lewati 0,0 untuk skala (argumen ketiga) dan Anda akan mendapatkan konteks dengan faktor skala yang sama dengan layar.

UIGraphicsBeginImageContextmenggunakan faktor skala tetap 1,0, jadi Anda benar-benar mendapatkan gambar yang persis sama pada iPhone 4 seperti pada iPhone lainnya. Saya berani bertaruh iPhone 4 akan menerapkan filter ketika Anda secara implisit meningkatkannya atau otak Anda mengambilnya menjadi kurang tajam daripada segala sesuatu di sekitarnya.

Jadi saya kira:

#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;
}

Dan di 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
Jawaban Tommy baik-baik saja, tetapi Anda masih perlu mengimpor #import <QuartzCore/QuartzCore.h>untuk menghapus renderInContext:peringatan.
gwdp

2
@WayneLiu Anda dapat menentukan faktor skala 2,0 secara eksplisit, tetapi Anda mungkin tidak akan mendapatkan gambar berkualitas iPhone 4 karena setiap bitmap yang telah dimuat akan menjadi versi standar daripada versi @ 2x. Saya tidak berpikir ada solusi untuk itu karena tidak ada cara untuk menginstruksikan semua orang yang saat ini memegang UIImageuntuk memuat ulang tetapi memaksakan versi resolusi tinggi (dan Anda mungkin akan menghadapi masalah karena RAM yang lebih sedikit tersedia di pra perangkat -retina pula).
Tommy

6
Alih-alih menggunakan 0.0fparameter for for scale, apakah lebih dapat diterima untuk digunakan [[UIScreen mainScreen] scale], tetapi juga berfungsi.
Adam Carter

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.Secara eksplisit didokumentasikan, jadi 0.0f lebih sederhana dan lebih baik menurut saya.
cprcrack

5
Jawaban ini kedaluwarsa untuk iOS 7. Lihat jawaban saya untuk metode "terbaik" baru untuk melakukan ini.
Dima

224

Jawaban yang saat ini diterima sudah kedaluwarsa, setidaknya jika Anda mendukung iOS 7.

Inilah yang harus Anda gunakan jika Anda hanya mendukung 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;
}

Swift 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()
}

Sesuai artikel ini , Anda dapat melihat bahwa metode iOS7 baru drawViewHierarchyInRect:afterScreenUpdates:jauh lebih cepat daripada renderInContext:.patokan


5
Tidak ada keraguan, itu drawViewHierarchyInRect:afterScreenUpdates:jauh lebih cepat. Saya baru saja menjalankan profiler waktu di Instrumen. Generasi gambar saya beralih dari 138ms ke 27ms.
ThomasCle

9
Untuk beberapa alasan ini tidak berfungsi untuk saya ketika saya mencoba membuat ikon khusus untuk penanda Google Maps; semua yang saya dapatkan adalah persegi panjang hitam :(
JakeP

6
@CarlosP Anda mencoba pengaturan afterScreenUpdates:ke YES?
Dima

7
atur afterScreenUpdates ke YA memperbaiki masalah persegi panjang hitam untuk saya
hyouuu

4
Saya juga menerima gambar hitam. Ternyata jika saya memanggil kode viewDidLoadatau viewWillAppear:gambarnya hitam; Saya harus melakukannya viewDidAppear:. Jadi saya juga akhirnya kembali ke renderInContext:.
manicaesar

32

Saya telah membuat ekstensi Swift berdasarkan solusi @Dima:

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
    }
}

EDIT: Swift 4 versi yang disempurnakan

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()
    }
}

Pemakaian:

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

2
Terima kasih atas idenya! Selain itu, Anda juga dapat menunda {UIGraphicsEndImageContext ()} segera setelah memulai konteks dan menghindari keharusan memperkenalkan variabel lokal img;)
Dennis L

Terima kasih banyak atas penjelasan dan penggunaannya yang terinci!
Zeus

Mengapa ini merupakan classfungsi yang mengambil tampilan?
Rudolf Adamkovič

Ini berfungsi seperti staticfungsi tetapi karena tidak perlu untuk meng-override Anda hanya dapat mengubahnya ke staticfunc sebagai gantinya class.
Heberti Almeida

25

iOS Cepat

Menggunakan UIGraphicsImageRenderer modern

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 mungkin belum, tetapi begitu mereka mulai mencela hal-hal atau menambahkan perenderan yang dipercepat perangkat keras atau peningkatan, Anda sebaiknya berada di pundak raksasa (AKA menggunakan Apple API terakhir)
Juan Boero

Adakah yang tahu apa kinerja membandingkan ini rendererdengan yang lama drawHierarchy..?
Vive

24

Untuk meningkatkan jawaban oleh @Tommy dan @Dima, gunakan kategori berikut untuk merender UIView ke dalam UIImage dengan latar belakang transparan dan tanpa kehilangan kualitas. Bekerja di iOS7. (Atau gunakan kembali metode itu dalam implementasi, ganti selfreferensi dengan gambar Anda)

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
Jika saya menggunakan drawViewHierarchyInRect dengan afterScreenUpdates: YA rasio aspek gambar saya telah berubah dan gambar terdistorsi.
confile

Apakah ini terjadi ketika konten uiview Anda diubah? Jika ya maka cobalah untuk membuat kembali UIImage ini lagi. Maaf tidak dapat menguji ini sendiri karena saya sedang menelepon
Glogo

Saya memiliki masalah yang sama dan sepertinya tidak ada yang memperhatikan ini, apakah Anda menemukan solusi untuk masalah ini? @ confile?
Reza.Ab

Ini hanya yang saya butuhkan tetapi tidak berhasil. Saya mencoba membuat @interfacedan @implementationkeduanya (RenderViewToImage)serta menambahkan #import "UIView+RenderViewToImage.h"ke header ViewController.hsehingga apakah ini penggunaan yang benar? misalnya UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];?
Greg

dan metode ini dalam ViewController.madalah pandangan yang saya coba render- (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; }
Greg

15

Cepat 3

Solusi Swift 3 (berdasarkan jawaban Dima ) dengan ekstensi UIView harus seperti ini:

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

Ekstensi Drop-in Swift 3.0 yang mendukung iOS 10.0 API baru & metode sebelumnya.

catatan:

  • Cek versi iOS
  • Perhatikan penggunaan penundaan untuk menyederhanakan pembersihan konteks.
  • Juga akan menerapkan opacity & skala tampilan saat ini.
  • Tidak ada yang hanya menggunakan membuka !yang dapat menyebabkan crash.

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:

Menggunakan metode ekstensi:

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
   }

}

Pemakaian:

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

Implementasi 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

Semua jawaban Swift 3 tidak berhasil untuk saya, jadi saya telah menerjemahkan jawaban yang paling diterima:

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

Berikut adalah ekstensi Swift 4 UIView berdasarkan jawaban dari @Dima.

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

2

Untuk Swift 5.1 Anda dapat menggunakan ekstensi ini:

extension UIView {

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

        return renderer.image {
            rendererContext in

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

1

UIGraphicsImageRendereradalah API yang relatif baru, diperkenalkan di iOS 10. Anda membangun UIGraphicsImageRenderer dengan menentukan ukuran titik . Metode gambar mengambil argumen penutupan dan mengembalikan bitmap yang dihasilkan dari mengeksekusi penutupan yang diteruskan. Dalam hal ini, hasilnya adalah gambar asli diperkecil untuk menggambar dalam batas yang ditentukan.

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

Jadi pastikan ukuran yang Anda lewati UIGraphicsImageRendereradalah titik, bukan piksel.

Jika gambar Anda lebih besar dari yang Anda harapkan, Anda perlu membagi ukuran Anda dengan faktor skala.


Saya ingin tahu, bisakah Anda membantu saya dengan ini? stackoverflow.com/questions/61247577/... Saya menggunakan UIGraphicsImageRenderer, masalahnya adalah saya ingin gambar yang diekspor berukuran lebih besar daripada tampilan aktual di perangkat. Tetapi dengan subview ukuran yang diinginkan (label) tidak cukup tajam - sehingga ada kualitas yang hilang.
Ondřej Korol


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

Dalam metode ini hanya melewatkan objek tampilan dan itu akan mengembalikan objek UIImage.

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

-6

Tambahkan ini ke metode ke Kategori UIView

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

2
Halo, sementara ini mungkin menjawab pertanyaan, harap perhatikan bahwa pengguna lain mungkin tidak sepaham Anda. Mengapa Anda tidak menambahkan sedikit penjelasan tentang mengapa kode ini berfungsi? Terima kasih!
Vogel612
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.