Cómo convertir una UIView en una imagen


94

Quiero convertir un UIView en una imagen y guardarlo en mi aplicación. ¿Alguien puede decirme cómo tomar una captura de pantalla de una vista o convertirla en una imagen y cuál es la mejor manera de guardarla en una aplicación (no en el carrete de la cámara)? Aquí está el código de la vista:

var overView   = UIView(frame: CGRectMake(0, 0, self.view.frame.width/1.3, self.view.frame.height/1.3))
overView.center = CGPointMake(CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds)-self.view.frame.height/16);
overView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(overView)
self.view.bringSubviewToFront(overView)

Respuestas:


189

Una extensión en UIViewdebería funcionar.

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

Apple desaconseja usar UIGraphicsBeginImageContextiOS 10 con la introducción de la gama de colores P3. UIGraphicsBeginImageContextes sRGB y solo de 32 bits. Presentaron la nueva UIGraphicsImageRendererAPI que está completamente administrada por colores, basada en bloques, tiene subclases para archivos PDF e imágenes y administra automáticamente la vida útil del contexto. Consulte la sesión 205 de WWDC16 para obtener más detalles (la reproducción de la imagen comienza alrededor de las 11:50)

Para asegurarse de que funcione en todos los dispositivos, utilícelo #availablecon un respaldo a versiones anteriores de iOS:

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContext(self.frame.size)
            self.layer.render(in:UIGraphicsGetCurrentContext()!)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return UIImage(cgImage: image!.cgImage!)
        }
    }
}

9
Esto debe marcarse como la respuesta correcta. Todas las demás opciones harán que la imagen renderizada pierda claridad y se vea pixelada / borrosa.
TuplingD

¡La única forma correcta! Para los principiantes, el uso es: let image = customView.asImage ()
Fabio

1
¿Es posible renderizar la UIView con un fondo transparente? El mío tiene algunas esquinas redondeadas.
ixany

@ixany Esto ya debería solucionarlo. Lo probé con lo siguiente en un patio de juegos y funcionó: let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)); view.backgroundColor = .black; view.layer.cornerRadius = 9; view.asImage();
Naveed J.

2
¡Gracias! Si alguien lo sabe, me gustaría saber qué diferencias hay con el código de hackingwithswift.com/example-code/media/… : return renderer.image {ctx in drawHierarchy (in: limits, afterScreenUpdates: true)}
Kqtr

63

puedes usar la extensión

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}

Aquí la rápida versión 3/4:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}

5
Me avergüenza ser el que pregunta esto, pero ¿cómo puedo llamar a esto? No tengo ninguna imagen y una UIView que quiero que sea una imagen. El código anterior me da una extensión UIImage ... pero ¿ahora qué?
Dave G

8
let image = UIImage(view: myView)
doctorBroctor

No funciona para mí ya que UIGraphicsGetCurrentContext () puede ser nulo y el código falla.
Juan Carlos Ospina Gonzalez

@JuanCarlosOspinaGonzalez Si UIGraphicsGetCurrentContext devolvió nulo, sospecho que debe verificar UIGraphicsBeginImageContext para averiguar por qué falló. Supongo que una de las dimensiones de su tamaño es cero o no es válida, pero no tengo idea de qué podría causar que eso falle.
John Stephen

2
También debe tener en cuenta la escala de la pantalla, por lo que reemplazaría la primera línea del inicializador con esto: UIGraphicsBeginImageContextWithOptions(view.frame.size, false, UIScreen.main.scale)
iVentis

41

Convierta su UIView a una imagen con drawViewHierarchyInRect: afterScreenUpdates: que es muchas veces más rápido que renderInContext

Nota importante: no llame a esta función desde viewDidLoad o viewWillAppear , asegúrese de capturar una vista después de que se muestre / cargue por completo

Obj C

     UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.opaque, 0.0f);
     [myView drawViewHierarchyInRect:myView.bounds afterScreenUpdates:NO];
     UIImage *snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();

     myImageView.image =  snapshotImageFromMyView;

Guardar la imagen editada Álbum de fotos

     UIImageWriteToSavedPhotosAlbum(snapshotImageFromMyView, nil,nil, nil);

Rápido 3/4

    UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.isOpaque, 0.0)
    myView.drawHierarchy(in: myView.bounds, afterScreenUpdates: false)
    let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    print(snapshotImageFromMyView)
    myImageView.image = snapshotImageFromMyView

Generalización súper fácil con extensión, iOS11, swift3 / 4

extension UIImage{
    convenience init(view: UIView) {

    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    self.init(cgImage: (image?.cgImage)!)

  }
}


Use : 
 //myView is completly loaded/visible , calling this code after only after viewDidAppear is call
 imgVV.image = UIImage.init(view: myView)
 // Simple image object
 let img =  UIImage.init(view: myView)

3
No funciona para mí Tengo una pantalla negra para mi UiImage
Badr Filali

De Verdad ?? ¿Puede pegar su código aquí? U podría estar usando un objeto UIView incorrecto para convertirlo como UIImage.
ViJay Avhad

`func convertViewIntoImg () -> Void {imgFakeCard.image = UIImage.init (view: card)}` Con su extensión en swift 3, esta función se llama en viewDidLoad
Badr Filali

1
Gracias por publicar su código y mencionar que ha llamado a viewDidLoad. Esto es lo que salió mal. Está capturando una instantánea de la vista antes de que se cargue en la memoria. Intente llamar al código en viewDidAppear, eso seguramente resolverá el problema. Cualquier pregunta por favor responda.
ViJay Avhad

Eso funciona muy bien gracias, el problema vino de donde llamé a su extensión
Badr Filali

20

En iOS 10:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)
    }
}

1
esto conduce a bloqueos.
KingPolygon

18

Mejores prácticas a partir de iOS 10 y Swift 3

aunque sigue siendo compatible con iOS 9 y versiones anteriores, todavía funciona a partir de iOS 13, Xcode 11.1, Swift 5.1

extension UIView {

    func asImage() -> UIImage? {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
            defer { UIGraphicsEndImageContext() }
            guard let currentContext = UIGraphicsGetCurrentContext() else {
                return nil
            }
            self.layer.render(in: currentContext)
            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

No estoy seguro de qué significa la pregunta con:

¿Cuál es la mejor manera de guardarlo en una aplicación (no en el carrete de la cámara)?


17
    UIGraphicsBeginImageContext(self.view.bounds.size);        
    self.view.layer.renderInContext(UIGraphicsGetCurrentContext())
    var screenShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


1
UIGraphicsGetImageFromCurrentImageContext devuelve UIImage. No es necesario enviarlo a UIImage
Leo Dabus

Esto toma una captura de pantalla de toda la vista, ¿no? Solo quiero convertir una vista general de UIView, es decir, en mi código, que es una subvista de self.view a una imagen. ¡No toda la vista!
Sameer Hussain

@Vakas cuando intenté imprimir la imagen, se muestra demasiado pequeña, ¿cuál es la solución para esto?
Krutarth Patel

6
La imagen de la captura de pantalla no tiene buena calidad, ¿qué debo hacer?
Neela

13

Por ejemplo, si tengo una vista de tamaño: 50 50 a 100,100. Puedo usar lo siguiente para tomar una captura de pantalla:

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0);
    self.view.drawViewHierarchyInRect(CGRectMake(-50,-5-,view.bounds.size.width,view.bounds.size.height), afterScreenUpdates: true)
    var image:UIImage = UIGraphicsGetImageFromCurrentImageContext();

en la línea self.view hay un error en el parámetro. Creo que debería ser -5 no -5-
Krutarth Patel

@sameer tengo una pregunta.Cuando imprimo esta imagen es demasiado pequeña
Krutarth Patel

7

En mi opinión, el enfoque con el inicializador no es tan bueno porque crea dos imágenes.

Prefiero esto:

extension UIView {
    var snapshot: UIImage? {
        UIGraphicsBeginImageContext(self.frame.size)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

Debería llamar a la propiedad de otra forma que no sea imagen. Esto puede interferir con las subclases que tienen propiedades llamadas imagen, como UIImageView.
Hobbes the Tige

Siguió la sugerencia de @HobbestheTige y cambió el nombre de la propiedad
Tino

6

Swift 4.2, iOS 10

extension UIView {

    // If Swift version is lower than 4.2, 
    // You should change the name. (ex. var renderedImage: UIImage?)

    var image: UIImage? {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in layer.render(in: rendererContext.cgContext) }
    }
}

Muestra

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .blue

let view2 = UIView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
view2.backgroundColor = .red
view.addSubview(view2)

let imageView = UIImageView(image: view.image)

ingrese la descripción de la imagen aquí


Solo quería señalar que esto debe nombrarse de otra manera si está utilizando cualquier UIImageViews en su proyecto, de lo contrario .image se convierte en solo lectura. Tal vez lo llame "captura de pantalla" en lugar de "imagen".
Chris

@Chris Sí, tienes razón. Debe cambiar un nombre si la versión de Swift es inferior a 4.2. Gracias por señalar el error.
Den

6

Esto me funciona para Xcode 9 / Swift 3.2 / Swift 4 y Xcode 8 / Swift 3

 if #available(iOS 10.0, *) {

     // for Xcode 9/Swift 3.2/Swift 4 -Paul Hudson's code
     let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
     let capturedImage = renderer.image { 
         (ctx) in
         view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
     }
     return capturedImage

 } else {

     // for Xcode 8/Swift 3
     UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
     view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
     let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
     UIGraphicsEndImageContext()
     return capturedImage!
 }

Aquí se explica cómo usarlo dentro de una función:

fileprivate func captureUIImageFromUIView(_ view:UIView?) -> UIImage {

     guard (view != nil) else{

        // if the view is nil (it's happened to me) return an alternative image
        let errorImage = UIImage(named: "Error Image")
        return errorImage
     }

     // if the view is all good then convert the image inside the view to a uiimage
     if #available(iOS 10.0, *) {

         let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
         let capturedImage = renderer.image { 
             (ctx) in
             view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
         }
         return capturedImage

     } else {

         UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
         view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
         let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
         UIGraphicsEndImageContext()
         return capturedImage!
     }
}

A continuación, se explica cómo hacer algo con la imagen devuelta por la función:

@IBOutlet weak fileprivate var myCustomView: UIView!
var myPic: UIImage?

let myImageView = UIImageView()

@IBAction fileprivate func saveImageButtonTapped(_ sender: UIButton) {

   myPic = captureUIImageFromUIView(myCustomView)

   // display the pic inside a UIImageView
   myImageView.image = myPic!
}

Me dieron el Xcode 9 / Swift 3.2 / 4 Swift respuesta de Paul Hudson convertido UIView a UIImage

Obtuve el Xcode 8 / Swift 3 de algún lugar en SO hace mucho tiempo y olvidé dónde :(


Además, ¿dónde se muestra y / o se guarda la imagen?
Famic Tech

@FamicTech una vez que se crea la imagen, los jóvenes deciden qué hacer con ella. En el ejemplo de mi código, acabo de convertir UIView en un UIImage llamado "myPic", pero no hice nada con 'myPic'. El siguiente paso posible es mostrar 'myPic' dentro de un UIImageVIew como este: myImageVIew.image = myPic. Si está utilizando un collectionView y desea mostrar imágenes en él, entonces crea un UIImageVIew en cada celda y luego muestra la imagen (myPic) dentro del UIImageVIew a través de indexPath.item de la fuente de datos, por ejemplo: myImageView.image = dataSource [indexPath .item] .myPic
Lance Samaria

@FamicTech Estás cometiendo un pequeño error. Esto no tiene nada que ver con un collectionVIew. Tienes 2 tipos de clases diferentes: UIImage y UIView. Ambos se pueden usar para mostrar cualquier imagen, pero ambos lo hacen de diferentes maneras. Casi todo en iOS es una subclase de un UIView, pero un UIImage solo puede mostrar imágenes (2 cosas diferentes). Lo que hace esta función es tomar el UIVIew y convertirlo en un UIImage. Un ejemplo más fácil. Si quieres convertir 4.0 (un Double) a 4 (un Int), lo lanzas Int (4.0), el resultado es 4. Pero con esto conviertes la imagen dentro del UIVIew en un UIImage. ¿Entender?
Lance Samaria

Lo que estoy tratando de hacer es que el usuario venga a mi Controlador de vista de colección y presione un botón que debería tomar la vista actual, que resulta ser una Vista de colección 10x10 y crear una imagen de toda la cuadrícula 10x10 (tenga en cuenta que todo cuadrícula no es completamente visible a la vista). ¿Su código anterior resuelve ese caso de uso?
Famic Tech

El botón está en el controlador de navegación. El usuario haría clic en él y 'debería' tomar la Vista de colección que se muestra debajo del controlador de navegación y crear una Imagen de la Vista de colección (un PDF también funcionaría) con toda la información en las Celdas en la Vista de colección.
Famic Tech

5
var snapshot = overView.snapshotViewAfterScreenUpdates(false)

o en objetivo-c

UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];

¿ImageView es lo mismo que image? Si es así, ¿cómo lo guardo en mi aplicación?
Sameer Hussain

4

Puedes usarlo fácilmente usando la extensión como esta

// Take a snapshot from a view (just one view)
let viewSnapshot = myView.snapshot

// Take a screenshot (with every views in screen)
let screenSnapshot = UIApplication.shared.snapshot

// Take a snapshot from UIImage initialization
UIImage(view: self.view)

Si desea utilizar esos métodos / variables de extensión, implemente esto

  1. Extensión de UIImage

    extension UIImage {
        convenience init(view: UIView) {
            if let cgImage = view.snapshot?.cgImage {
                self.init(cgImage: cgImage)
            } else {
                self.init()
            }
        }
    }
  2. Extensión UIView

    extension UIView {
    
        var snapshot: UIImage? {
            UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
            if UIGraphicsGetCurrentContext() != nil {
                drawHierarchy(in: bounds, afterScreenUpdates: true)
                let screenshot = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return screenshot
            }
            return nil
        }
    }
  3. Extensión de la aplicación UIApplication extension

    extension UIApplication {
    
        var snapshot: UIImage? {
            return keyWindow?.rootViewController?.view.snapshot
        }
    }

Tuve vista con subvistas zPositions manipuladas, y otras respuestas no me dieron las posiciones correctas de las capas, gracias.
Ashkan Ghodrat

3

o iOS 10+, puede usar el nuevo UIGraphicsImageRenderer + el drawHierarchy recomendado, que en algunas situaciones puede ser mucho más rápido que layer.renderInContext

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
        return renderer.image { _ in
            self.drawHierarchy(in: CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height), afterScreenUpdates: false)
        }
    }
}

3

¡Gracias @Bao Tuan Diep! Quiero agregar un suplemento.

Cuando usas el código:

yourView.layer.render(in:UIGraphicsGetCurrentContext()!)

Debes notar que:

 - If you had used `autoLayout` or `Masonry` in `yourView` (that you want to convert) .
 - If you did not add `yourView` to another view which means that `yourView` was not used as a subview but just an object.

Entonces, debes usar :

[yourView setNeedsLayout];
[yourView layoutIfNeeded];

actualizar yourViewantes yourView.layer.render(in:UIGraphicsGetCurrentContext()!).

De lo contrario, puede obtener un objeto de imagen que no contiene elementos.


Gracias por esto. Ayudó mucho !!
Maksim Kniazev

2

Rápido 4.2

import Foundation
import UIKit  

extension UIImage {

    convenience init(view: UIView) {

        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)

    } 
}

utilizando:

dejar img = UIImage.init (ver: self.holderView)


1

Implementación en Swift 3 :

Agregue el código a continuación, fuera del alcance de la clase.

extension UIImage {
    convenience init(_ view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)
    }
}

Uso:

let image = UIImage( Your_View_Outlet )

obteniendo el error "Dibujar una vista (0x1040a6970, UIView) que no se ha renderizado al menos una vez requiere"
kishu mewara

0

Implementé @Naveed J. así, y funcionó como un encanto.

Aquí estaba su extensión:

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

Así es como lo implementé.

//create an image from yourView to display
//determine the frame of the view/imageimage
let screen = self.superview!.bounds
let width = screen.width / 4 //make image 1/4 width of screen
let height = width
let frame = CGRect(x: 0, y: 0, width: width, height: height)
let x = (screen.size.width - frame.size.width) * 0.5
let y = (screen.size.height - frame.size.height) * 0.5
let mainFrame = CGRect(x: x, y: y, width: frame.size.width, height: frame.size.height)

let yourView = YourView() //instantiate yourView
yourView.frame = mainFrame //give it the frame
yourView.setNeedsDisplay() //tell it to display (I am not 100% sure this is needed)

let characterViewImage = yourView.asImage()

0

Inicializador con el nuevo UIGraphicsImageRenderer disponible desde iOS 10:

extension UIImage{
    convenience init(view: UIView) {

    let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
    let canvas = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
    let image = renderer.image { _ in
        self.drawHierarchy(in: canvas, afterScreenUpdates: false)
    }
    self.init(cgImage: (image?.cgImage)!)
  }
}

0

trabaja bien conmigo!

Swift4

 extension UIView {

    func toImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
        let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return snapshotImageFromMyView!
    }

    }

return snapshotImageFromMyView!Hilo 1: Error fatal: Inesperadamente se encontró nulo al desenvolver un valor opcional
Takasur

0

Para la vista que contiene una subvista borrosa (por ejemplo, una instancia de UIVisualEffectView ), solo funciona drawViewHierarchyInRect: afterScreenUpdates .

La respuesta de @ViJay Avhad es correcta para este caso.


0

El uso de UIGraphicsImageRenderer no funciona cuando la vista contiene subvistas del Kit de escena, Metal o Kit de Sprite. En estos casos, utilice

let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let image = renderer.image { ctx in
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}

-1
please try below code.
-(UIImage *)getMainImageFromContext
{
UIGraphicsBeginImageContextWithOptions(viewBG.bounds.size, viewBG.opaque, 0.0);
[viewBG.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.