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;
}
Daniel
sumber
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
}
Tommy
sumber
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

Dima
sumber
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)
Heberti Almeida
sumber
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
    }
}
Alexander Belyavskiy
sumber
@ 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
Glogo
sumber
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
    }
}
Mehdi Hosseinzadeh
sumber
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()
        }
    }
}
Leslie Godwin
sumber
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)

 }
AG
sumber
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
    }
}
sama
sumber
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!
    }
}
Marco Antonio Uzcategui Pescoz
sumber
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
   }
}
danfordham
sumber
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)
        }
    }
}
Andrey M.
sumber
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.

pkamb
sumber
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;
}
PJRadadiya
sumber
-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;
}
Atanu Mondal
sumber
-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;
}
Shafraz Buhary
sumber
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