Como tirar uma captura de tela de um UIView?

133

Gostaria de saber como meu aplicativo para iPhone pode tirar uma captura de tela de um específico UIViewcomo um UIImage.

Eu tentei esse código, mas tudo o que recebo é uma imagem em branco.

UIGraphicsBeginImageContext(CGSizeMake(320,480));
CGContextRef context = UIGraphicsGetCurrentContext();
[myUIView.layer drawInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

myUIViewtem dimensões de 320 x 480 e possui algumas sub-visualizações. Qual é a maneira correta de fazer isso?

cduck
fonte
Basta verificar stackoverflow.com/a/44517922/3908884
Meet Doshi

Respostas:

73

Eu acho que você pode querer renderInContext, não drawInContext. drawInContext é mais um método que você substituiria ...

Observe que ele pode não funcionar em todas as visualizações, especificamente um ano atrás, quando tentei usar isso com a visualização da câmera ao vivo, não funcionou.

Kendall Helmstetter Gelner
fonte
Olá Kendall, você tem conselhos para capturar o conteúdo de um UIView não como uma imagem parada, mas como um vídeo? Obrigado pelo seu tempo! Pergunta aqui: stackoverflow.com/questions/34956713/…
Crashalot
187

O iOS 7 possui um novo método que permite desenhar uma hierarquia de visualizações no contexto gráfico atual. Isso pode ser usado para obter uma UIImage muito rápido.

Eu implementei um método de categoria UIViewpara obter a visualização como UIImage:

- (UIImage *)pb_takeSnapshot {
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);

    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];

    // old style [self.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

É consideravelmente mais rápido que o renderInContext:método existente .

Referência: https://developer.apple.com/library/content/qa/qa1817/_index.html

ATUALIZAÇÃO PARA SWIFT : uma extensão que faz o mesmo:

extension UIView {

    func pb_takeSnapshot() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)

        drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)

        // old style: layer.renderInContext(UIGraphicsGetCurrentContext())

        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

ATUALIZAÇÃO PARA SWIFT 3

    UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)

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

    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return image
Klaas
fonte
Se você tem um grande UILabel ou CAShapeLayer, isso não funciona, ele acaba não desenho nada
jjxtra
graças ao seu snippet rápido, resolvi meu problema: stackoverflow.com/a/27764590/1139044 .
Nicholas
resolveu meu problema. Eu estava usando a versão antiga e estava me dando muitos erros! Thanks a million
apinho 20/10/2015
Estou usando da mesma maneira para tirar uma captura de tela de uma exibição. Se uma visualização tiver wkwebview como subvisão, não poderá fazer a captura de tela. Mostra em branco. Como tirar uma captura de tela corretamente?
Rikesh Subedi
1
Chamar isso durante uma transição de controladores de exibição pisca no final da transição.
Iulian Onofrei
63

Você precisa capturar a janela da chave para uma captura de tela ou um UIView. Você pode fazer isso na resolução Retina usando UIGraphicsBeginImageContextWithOptions e defina seu parâmetro de escala 0.0f. Ele sempre captura em resolução nativa (retina para iPhone 4 e posterior).

Este faz uma captura de tela em tela cheia (janela principal)

UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
CGRect rect = [keyWindow bounds];
UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[keyWindow.layer renderInContext:context];   
UIImage *capturedScreen = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Este código captura um UIView em resolução nativa

CGRect rect = [captureView bounds];
UIGraphicsBeginImageContextWithOptions(rect.size,YES,0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
[captureView.layer renderInContext:context];   
UIImage *capturedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Isso salva a UIImage no formato jpg com 95% de qualidade na pasta de documentos do aplicativo, se você precisar fazer isso.

NSString  *imagePath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"Documents/capturedImage.jpg"]];    
[UIImageJPEGRepresentation(capturedImage, 0.95) writeToFile:imagePath atomically:YES];
Tibidabo
fonte
Infelizmente, a captura de tela em tela cheia não captura a barra de status. Snippet muito bom embora.
neoneye
Existe alguma maneira de capturar o teclado?
mrvincenzo
@tibidabo obrigado, funciona. Mas como posso salvar mais de uma imagem?
Josef
"Grande vazamento de memória de Chesapeake!" Hermes Conrad. (Falando sério, gerir o seu CG corretamente !!)
Albert Renshaw
22

iOS7 em diante, temos os métodos padrão abaixo:

- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

Chamar o método acima é mais rápido do que tentar renderizar o conteúdo da visualização atual em uma imagem de bitmap.

Se você deseja aplicar um efeito gráfico, como desfoque, a um instantâneo, use o drawViewHierarchyInRect:afterScreenUpdates:método.

https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html

san
fonte
13

Há uma nova API do iOS 10

extension UIView {
    func makeScreenshot() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: self.bounds)
        return renderer.image { (context) in
            self.layer.render(in: context.cgContext)
        }
    }
}
Mike Demidov
fonte
10

Eu criei uma extensão utilizável para o UIView fazer a captura de tela no Swift:

extension UIView{

var screenshot: UIImage{

    UIGraphicsBeginImageContext(self.bounds.size);
    let context = UIGraphicsGetCurrentContext();
    self.layer.renderInContext(context)
    let screenShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return screenShot
}
}

Para usá-lo, basta digitar:

let screenshot = view.screenshot
Hossam Ghareeb
fonte
1
Use em UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0);vez de UIGraphicsBeginImageContext(self.bounds.size);usar o fator de escala correto do dispositivo.
9136 knshn
1
Confirmo que funciona, mas usar em drawViewHierarchyInRectvez de renderInContext não.
Mike Demidov
7
- (void)drawRect:(CGRect)rect {
  UIGraphicsBeginImageContext(self.bounds.size);    
  [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  UIImageWriteToSavedPhotosAlbum(viewImage, nil, nil, nil);  
}

Este método pode colocar na sua classe Controller.

nova alma
fonte
2
drawRecté não parte do UIViewController (IIRC). Faz parte de um UIView. Eu não acredito que será chamado se estiver no controlador.
JWW
Como posso obter o caminho da imagem salva?
GameDevGuru 5/09
5
CGImageRef UIGetScreenImage();

A Apple agora nos permite usá-lo em um aplicativo público, mesmo que seja uma API privada

Matt S.
fonte
Existem outros UIViews no topo do myUIView que eu não quero capturar. Caso contrário, isso seria ótimo.
cduck
5

Detalhes

  • Xcode Versão 10.3 (10G8), Swift 5

Solução

import UIKit

extension CALayer {
    func makeSnapshot() -> UIImage? {
        let scale = UIScreen.main.scale
        UIGraphicsBeginImageContextWithOptions(frame.size, false, scale)
        defer { UIGraphicsEndImageContext() }
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        render(in: context)
        let screenshot = UIGraphicsGetImageFromCurrentImageContext()
        return screenshot
    }
}

extension UIView {
    func makeSnapshot() -> UIImage? {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(size: frame.size)
            return renderer.image { _ in drawHierarchy(in: bounds, afterScreenUpdates: true) }
        } else {
            return layer.makeSnapshot()
        }
    }
}

Uso

let image = view.makeSnapshot()

Amostra completa

Não se esqueça de adicionar o código da solução aqui

import UIKit

class ViewController: UIViewController {

    @IBOutlet var viewForScreenShot: UIView!
    @IBOutlet var screenShotRenderer: UIImageView!

    @IBAction func makeViewScreenShotButtonTapped2(_ sender: UIButton) {
        screenShotRenderer.image = viewForScreenShot.makeSnapshot()
    }
}

Main.storyboard

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_2214957" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Acg-GO-mMN">
                                <rect key="frame" x="67" y="28" width="240" height="128"/>
                                <subviews>
                                    <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="4Fr-O3-56t">
                                        <rect key="frame" x="72" y="49" width="96" height="30"/>
                                        <constraints>
                                            <constraint firstAttribute="height" constant="30" id="cLv-es-h7Q"/>
                                            <constraint firstAttribute="width" constant="96" id="ytF-FH-gdm"/>
                                        </constraints>
                                        <nil key="textColor"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                        <textInputTraits key="textInputTraits"/>
                                    </textField>
                                </subviews>
                                <color key="backgroundColor" red="0.0" green="0.47843137250000001" blue="1" alpha="0.49277611300000002" colorSpace="custom" customColorSpace="sRGB"/>
                                <color key="tintColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                <constraints>
                                    <constraint firstItem="4Fr-O3-56t" firstAttribute="centerX" secondItem="Acg-GO-mMN" secondAttribute="centerX" id="egj-rT-Gz5"/>
                                    <constraint firstItem="4Fr-O3-56t" firstAttribute="centerY" secondItem="Acg-GO-mMN" secondAttribute="centerY" id="ymi-Ll-WIV"/>
                                </constraints>
                            </view>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SQq-IE-pvj">
                                <rect key="frame" x="109" y="214" width="157" height="30"/>
                                <state key="normal" title="make view screen shot"/>
                                <connections>
                                    <action selector="makeViewScreenShotButtonTapped2:" destination="BYZ-38-t0r" eventType="touchUpInside" id="KSY-ec-uvA"/>
                                </connections>
                            </button>
                            <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="CEZ-Ju-Tpq">
                                <rect key="frame" x="67" y="269" width="240" height="128"/>
                                <constraints>
                                    <constraint firstAttribute="width" constant="240" id="STo-iJ-rM4"/>
                                    <constraint firstAttribute="height" constant="128" id="tfi-zF-zdn"/>
                                </constraints>
                            </imageView>
                        </subviews>
                        <color key="backgroundColor" red="0.95941069162436543" green="0.95941069162436543" blue="0.95941069162436543" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="CEZ-Ju-Tpq" firstAttribute="top" secondItem="SQq-IE-pvj" secondAttribute="bottom" constant="25" id="6x1-iB-gKF"/>
                            <constraint firstItem="Acg-GO-mMN" firstAttribute="leading" secondItem="CEZ-Ju-Tpq" secondAttribute="leading" id="LUp-Be-FiC"/>
                            <constraint firstItem="SQq-IE-pvj" firstAttribute="top" secondItem="Acg-GO-mMN" secondAttribute="bottom" constant="58" id="Qu0-YT-k9O"/>
                            <constraint firstItem="Acg-GO-mMN" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="Qze-zd-ajY"/>
                            <constraint firstItem="Acg-GO-mMN" firstAttribute="trailing" secondItem="CEZ-Ju-Tpq" secondAttribute="trailing" id="b1d-sp-GHD"/>
                            <constraint firstItem="SQq-IE-pvj" firstAttribute="centerX" secondItem="CEZ-Ju-Tpq" secondAttribute="centerX" id="qCL-AF-Cro"/>
                            <constraint firstItem="Acg-GO-mMN" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="8" symbolic="YES" id="u5Y-eh-oSG"/>
                            <constraint firstItem="CEZ-Ju-Tpq" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="vkx-JQ-pOF"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="screenShotRenderer" destination="CEZ-Ju-Tpq" id="8QB-OE-ib6"/>
                        <outlet property="viewForScreenShot" destination="Acg-GO-mMN" id="jgL-yn-8kk"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="32.799999999999997" y="37.331334332833585"/>
        </scene>
    </scenes>
</document>

Resultado

insira a descrição da imagem aqui insira a descrição da imagem aqui

Vasily Bodnarchuk
fonte
Este é um exemplo abrangente. Muito obrigado por isso!
KMC
4

A Apple não permite:

CGImageRef UIGetScreenImage();

Os aplicativos devem fazer uma captura de tela usando o drawRectmétodo especificado em: http://developer.apple.com/library/ios/#qa/qa2010/qa1703.html

Adeem Maqsood Basraa
fonte
2
Então, e a resposta de Matt S?
Gajendra K Chauhan
4

Eu criei esta extensão para salvar uma captura de tela do UIView

extension UIView {
func saveImageFromView(path path:String) {
    UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)
    drawViewHierarchyInRect(bounds, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    UIImageJPEGRepresentation(image, 0.4)?.writeToFile(path, atomically: true)

}}

ligar para :

let pathDocuments = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).first!
let pathImage = "\(pathDocuments)/\(user!.usuarioID.integerValue).jpg"
reportView.saveImageFromView(path: pathImage)

Se você deseja criar um png, deve alterar:

UIImageJPEGRepresentation(image, 0.4)?.writeToFile(path, atomically: true)

de

UIImagePNGRepresentation(image)?.writeToFile(path, atomically: true)
anthonyqz
fonte
Alguma idéia de por que, se eu capturar uma tela de um UITableViewCell, obtenho uma exibição em branco, mas se capturar a tela do tableView, recebo o que espero?
Unome
Eu tentei com um exemplo (UITableViewController) e ele funciona, talvez colocar o seu código aqui para revisão
anthonyqz
O truque era que eu precisava usar um CGContextTranslateCTM (contexto, 0, -view.frame.origin.y);
Unome
3

Swift 4 atualizado:

extension UIView {
   var screenShot: UIImage?  {
        if #available(iOS 10, *) {
            let renderer = UIGraphicsImageRenderer(bounds: self.bounds)
            return renderer.image { (context) in
                self.layer.render(in: context.cgContext)
            }
        } else {
            UIGraphicsBeginImageContextWithOptions(bounds.size, false, 5);
            if let _ = UIGraphicsGetCurrentContext() {
                drawHierarchy(in: bounds, afterScreenUpdates: true)
                let screenshot = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return screenshot
            }
            return nil
        }
    }
}
Ankit Kumar Gupta
fonte
Este método de captura de tela funcionou muito bem.
eonist 21/06
2

O seguinte trecho é usado para fazer a captura de tela:

UIGraphicsBeginImageContext(self.muUIView.bounds.size);

[myUIView.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Use renderInContext:método em vez de drawInContext:método

renderInContext:O método renderiza o receptor e suas subcamadas no contexto atual. Este método é renderizado diretamente da árvore de camadas.

Jayprakash Dubey
fonte
1
-(UIImage *)convertViewToImage
{
    UIGraphicsBeginImageContext(self.bounds.size);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

  return image;
}
Ayush Goel
fonte
0

você pode usar a seguinte categoria UIView -

@implementation UIView (SnapShot)

 - (UIImage *)snapshotImage
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);        
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];        
    // old style [self.layer renderInContext:UIGraphicsGetCurrentContext()];        
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();        
    UIGraphicsEndImageContext();        
    return image;
}    
@end
Vishwas Singh
fonte