Como converter um UIView em uma imagem

93

Quero converter um UIView em uma imagem e salvá-lo em meu aplicativo. Alguém pode me dizer como fazer uma captura de tela de uma visualização ou convertê-la em uma imagem e qual é a melhor maneira de salvá-la em um aplicativo (não o rolo da câmera)? Aqui está o código para a visualização:

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)
Sameer Hussain
fonte
Também relevante: stackoverflow.com/q/59592933/294884
Fattie

Respostas:

187

Uma extensão em UIViewdeve resolver o problema.

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

A Apple desencoraja o uso do UIGraphicsBeginImageContextiOS 10 com a introdução da gama de cores P3. UIGraphicsBeginImageContexté sRGB e apenas 32 bits. Eles introduziram a nova UIGraphicsImageRendererAPI que é totalmente gerenciada por cores, baseada em blocos, tem subclasses para PDFs e imagens e gerencia automaticamente o tempo de vida do contexto. Confira a sessão 205 do WWDC16 para obter mais detalhes (a renderização da imagem começa em torno da marca de 11:50)

Para ter certeza de que funciona em todos os dispositivos, use #availablecom um substituto para versões anteriores do 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!)
        }
    }
}
Naveed J.
fonte
9
Essa deve ser marcada como a resposta correta. Todas as outras opções farão com que a imagem renderizada perca a clareza e pareça pixelada / borrada.
TuplingD
O único caminho certo! Para iniciantes, o uso é: let image = customView.asImage ()
Fabio
1
É possível renderizar o UIView com um fundo transparente? O meu tem alguns cantos arredondados.
ixany
@ixany Isso já deve cuidar disso. Eu apenas tentei com o seguinte em um Playground e funcionou: 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
Obrigado! Se alguém souber, eu ficaria curioso para saber quais diferenças existem com o código de hackingwithswift.com/example-code/media/… : return renderer.image {ctx in drawHierarchy (in: bounds, afterScreenUpdates: true)}
Kqtr
63

você pode usar extensão

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

Aqui está a versão 3/4 rápida:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}
Bao Tuan Diep
fonte
5
Estou com vergonha de ser quem está perguntando isso, mas como faço para chamar isso? Não tenho imagem e um UIView que quero ser uma imagem. O código acima me dá uma extensão UIImage ... mas e agora?
Dave G
8
let image = UIImage(view: myView)
doctorBroctor
Não funciona para mim porque UIGraphicsGetCurrentContext () pode ser nulo e o código falha.
Juan Carlos Ospina Gonzalez
@JuanCarlosOspinaGonzalez Se UIGraphicsGetCurrentContext retornou nulo, então eu suspeito que você precisa verificar UIGraphicsBeginImageContext para descobrir por que falhou. Meu palpite é que uma das dimensões do seu tamanho é zero ou inválida de alguma outra forma, mas não tenho ideia do que realmente poderia causar isso.
John Stephen
2
você também deve ter em mente a escala da tela, então eu substituiria a primeira linha do inicializador por: UIGraphicsBeginImageContextWithOptions(view.frame.size, false, UIScreen.main.scale)
iVentis
41

Converta seu UIView em imagem por drawViewHierarchyInRect: afterScreenUpdates: que é muitas vezes mais rápido que renderInContext

Observação importante: não chame esta função de viewDidLoad ou viewWillAppear , certifique-se de capturar uma visualização depois que ela for exibida / carregada totalmente

Obj C

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

     myImageView.image =  snapshotImageFromMyView;

Salve o álbum de fotos da imagem editada

     UIImageWriteToSavedPhotosAlbum(snapshotImageFromMyView, nil,nil, nil);

Swift 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

Generalização super fácil com extensão, 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)
ViJay Avhad
fonte
3
Não está funcionando para mim, ganhei uma tela preta para minha UiImage
Badr Filali
Sério ?? Você pode colar seu código aqui ... Você pode estar usando um objeto UIView errado para ser convertido como UIImage.
ViJay Avhad
`func convertViewIntoImg () -> Void {imgFakeCard.image = UIImage.init (view: card)}` Com sua extensão em swift 3 esta função é chamada em viewDidLoad
Badr Filali
1
Obrigado por postar seu código e mencionar que você chamou viewDidLoad. Aqui está o que você deu errado. Você está capturando um instantâneo de vista antes de ser carregado na memória. Tente chamar o código em viewDidAppear, isso resolverá o problema com certeza. Qualquer pergunta por favor responda.
ViJay Avhad
Esse trabalho foi ótimo, obrigado, o problema veio de onde eu liguei para sua extensão
Badr Filali
20

No 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)!)
    }
}
Afrodev
fonte
1
isso leva a travamentos.
KingPolygon de
18

Prática recomendada a partir do iOS 10 e Swift 3

embora ainda suporte iOS 9 e anteriores, ainda funciona a partir do 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()
        }
    }
}

Não tenho certeza do que a pergunta significa por:

Qual é a melhor maneira de salvá-lo em um aplicativo (não no rolo da câmera)?

Jon Willis
fonte
17
    UIGraphicsBeginImageContext(self.view.bounds.size);        
    self.view.layer.renderInContext(UIGraphicsGetCurrentContext())
    var screenShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
Vakas
fonte
1
UIGraphicsGetImageFromCurrentImageContext retorna UIImage. Não há necessidade de lançá-lo para UIImage
Leo Dabus
Isso tira uma captura de tela de toda a visão, certo? Eu só quero converter um UIView ou seja, visão geral no meu código que é uma subvisão de self.view para uma imagem. Não a visão inteira!
Sameer Hussain
@Vakas quando tentei imprimir a imagem.a exibição é muito pequena.qual é a solução para isso
Krutarth Patel
6
A imagem da captura de tela não tem boa qualidade, o que devo fazer?
Neela
13

Por exemplo, se eu tiver uma visão de tamanho: 50 50 em 100.100. Posso usar o seguinte para fazer uma captura de tela:

    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();
Sameer Hussain
fonte
na linha self.view há um erro no parâmetro. Acho que deveria ser -5 e não -5-
Krutarth Patel
@ mesmo, tenho uma pergunta. quando imprimo esta imagem, ela é muito pequena
Krutarth Patel
7

Na minha opinião, a abordagem com o inicializador não é tão boa porque ele cria duas imagens.

Eu prefiro isso:

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
    }
}
Tino
fonte
Você deve chamar a propriedade de algo diferente de imagem. Isso pode interferir nas subclasses que possuem propriedades chamadas imagem, como UIImageView.
Hobbes, o Tige
Seguiu a sugestão de @HobbestheTige e renomeou a propriedade
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) }
    }
}

Amostra

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)

insira a descrição da imagem aqui

Den
fonte
Só queria salientar que isso precisa ter outro nome, se você estiver usando qualquer UIImageViews em seu projeto, caso contrário .image se torna somente leitura. Pode ser chamado de 'captura de tela' em vez de 'imagem'.
Chris
@Chris Sim, você está certo. Você deve alterar um nome se a versão do Swift for inferior a 4.2. Obrigado por apontar o erro.
Den
6

Isso funciona para mim para Xcode 9 / Swift 3.2 / Swift 4 e 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!
 }

Veja como usá-lo dentro de uma função:

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

Veja como fazer algo com a imagem retornada da função:

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

Recebi a resposta do Xcode 9 / Swift 3.2 / Swift 4 de Paul Hudson convert uiview para uiimage

Peguei o Xcode 8 / Swift 3 em algum lugar do SO, há muito tempo e esqueci onde :(

Lance Samaria
fonte
Além disso, onde a imagem é exibida e / ou salva?
Famic Tech
@FamicTech uma vez que a imagem é criada, cabe aos jovens decidir o que fazer com ela. No exemplo do meu código, acabei de converter o UIView em um UIImage chamado "myPic", mas não fiz nada com 'myPic'. A próxima etapa possível é exibir 'myPic' dentro de um UIImageVIew como este: myImageVIew.image = myPic. Se você estiver usando um collectionView e quiser exibir imagens nele, crie um UIImageVIew em cada célula e, em seguida, exiba a imagem (myPic) dentro do UIImageVIew por meio do indexPath.item da fonte de dados ex: myImageView.image = dataSource [indexPath .item] .myPic
Lance Samaria
@FamicTech Você está cometendo um pequeno erro. Isso não tem nada a ver com uma coleção nova. Você tem 2 tipos de classes diferentes: UIImage e um UIView. Ambos podem ser usados ​​para exibir qualquer imagem, mas ambos fazem isso de maneiras diferentes. Quase tudo no iOS é uma subclasse de um UIView, mas um UIImage só pode exibir imagens (2 coisas diferentes). O que esta função faz é pegar o UIVIew e convertê-lo em um UIImage. Um exemplo mais fácil. Se você quiser converter 4.0 (um Double) para 4 (um Int), você conjura Int (4.0) o resultado é 4. Mas com isso você converte a imagem dentro do UIVIew para um UIImage. Compreendo?
Lance Samaria
o que estou tentando fazer é fazer com que o usuário venha ao meu Controlador de visualização de coleção e pressione um botão que deve assumir a visualização atual, que por acaso é uma visualização de coleção 10x10 e criar uma imagem de toda a grade 10x10 (observe que toda a a grade não está totalmente visível). Seu código acima resolve esse caso de uso?
Famic Tech
O botão está no Controlador de navegação. O usuário clicaria nele e 'deveria' pegar a Visualização da coleção exibida no controlador de navegação e criar uma imagem da Visualização da coleção (um PDF também funcionaria) com todas as informações nas células na visualização da coleção.
Famic Tech
5
var snapshot = overView.snapshotViewAfterScreenUpdates(false)

ou no objetivo-c

UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];
Yedidya Reiss
fonte
ImageView é o mesmo que imagem? Se sim, como posso salvá-lo no meu aplicativo?
Sameer Hussain
4

Você pode usá-lo facilmente usando a extensão 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)

Se você quiser usar esses métodos / variáveis ​​de extensão, implemente este

  1. Extensão UIImage

    extension UIImage {
        convenience init(view: UIView) {
            if let cgImage = view.snapshot?.cgImage {
                self.init(cgImage: cgImage)
            } else {
                self.init()
            }
        }
    }
  2. Extensão 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. Extensão UIApplication

    extension UIApplication {
    
        var snapshot: UIImage? {
            return keyWindow?.rootViewController?.view.snapshot
        }
    }
Khemmachart Chutapetch
fonte
eu tinha view with subviews zPositions manipulado, e outras respostas não me deram posições de camada corretas, obrigado.
Ashkan Ghodrat de
3

ou iOS 10+ você pode usar o novo UIGraphicsImageRenderer + o drawHierarchy recomendado, que em algumas situações pode ser muito mais rápido do 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)
        }
    }
}
Морт
fonte
3

Obrigado @Bao Tuan Diep! Eu quero adicionar um suplemento.

Quando você usa o código:

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

Você deve 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.

Então, você deve usar :

[yourView setNeedsLayout];
[yourView layoutIfNeeded];

para atualizar yourViewantes yourView.layer.render(in:UIGraphicsGetCurrentContext()!).

Caso contrário, você pode obter um objeto de imagem que não contém elementos

guozqzzu
fonte
Obrigado por isso. Ajudou muito !!
Maksim Kniazev
2

Swift 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)!)

    } 
}

usando:

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

reza_khalafi
fonte
1

Implementação em Swift 3 :

Adicione o código abaixo, fora do escopo da classe.

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 )
Roy
fonte
recebendo o erro "Desenhar uma vista (0x1040a6970, UIView) que não foi renderizada pelo menos uma vez requer"
kishu mewara
0

Implementei @Naveed J. o método de assim, e funcionou .

Aqui estava sua extensão:

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

Aqui está como eu o implementei.

//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()
Jacob F. Davis C-CISO
fonte
0

Inicializador com o novo UIGraphicsImageRenderer disponível 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)!)
  }
}
Frédéric Adda
fonte
0

funciona bem comigo!

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

    }
Faris
fonte
return snapshotImageFromMyView!Tópico 1: Erro fatal: Inesperadamente encontrado nulo ao desembrulhar um valor opcional
Takasur
0

Para a visualização contém subvisualização desfocada (por exemplo, instância UIVisualEffectView ), apenas drawViewHierarchyInRect: afterScreenUpdates funciona.

A resposta de @ViJay Avhad está correta para este caso.

Rafalkitta
fonte
-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;
}
herry.master
fonte