Comment prendre une capture d'écran d'un UIView?


133

Je me demande comment mon application iPhone peut prendre une capture d'écran d'un fichier spécifique en UIViewtant que fichier UIImage.

J'ai essayé ce code mais tout ce que j'obtiens est une image vierge.

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

myUIViewa des dimensions 320x480 et il a quelques sous-vues. Quelle est la bonne façon de procéder?


Jetez un coup d' œil
Rencontrez Doshi le

Réponses:


73

Je pense que vous voudrez peut-être renderInContext, non drawInContext. drawInContext est plus une méthode que vous remplaceriez ...

Notez que cela peut ne pas fonctionner dans toutes les vues, en particulier il y a environ un an lorsque j'ai essayé de l'utiliser avec la vue de la caméra en direct, cela ne fonctionnait pas.


Salut Kendall avez-vous des conseils pour capturer le contenu d'un UIView non pas comme une image fixe, mais comme une vidéo? Merci pour votre temps! Question ici: stackoverflow.com/questions/34956713/…
Crashalot

187

iOS 7 a une nouvelle méthode qui vous permet de dessiner une hiérarchie de vues dans le contexte graphique actuel. Cela peut être utilisé pour obtenir une UIImage très rapidement.

J'ai implémenté une méthode de catégorie UIViewpour obtenir la vue en tant que 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;
}

C'est considérablement plus rapide que la renderInContext:méthode existante .

Référence: https://developer.apple.com/library/content/qa/qa1817/_index.html

MISE À JOUR POUR SWIFT : Une extension qui fait la même chose:

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

MISE À JOUR POUR SWIFT 3

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

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

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

Si vous avez un grand UILabel ou CAShapeLayer, cela ne fonctionne pas, cela finit par ne rien dessiner
jjxtra

grâce à votre extrait swift j'ai résolu mon problème: stackoverflow.com/a/27764590/1139044 .
Nicholas

cela a résolu mon problème. J'utilisais l'ancienne version et cela me donnait plein d'erreurs! Merci un million
apinho

J'utilise la même méthode pour prendre une capture d'écran d'une vue. Si une vue a wkwebview comme sous-vue, elle ne peut pas prendre la capture d'écran. Il montre vide. Comment prendre correctement une capture d'écran?
Rikesh Subedi

1
L'appeler pendant une transition de contrôleurs de vue fait clignoter la fin de la transition.
Iulian Onofrei

63

Vous devez capturer la fenêtre clé pour une capture d'écran ou un UIView. Vous pouvez le faire dans Retina Resolution à l' aide de UIGraphicsBeginImageContextWithOptions et définir son paramètre d'échelle 0.0f. Il capture toujours en résolution native (retina pour iPhone 4 et versions ultérieures).

Celui-ci fait une capture d'écran en plein écran (fenêtre clé)

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

Ce code capture un UIView en résolution native

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

Cela enregistre l'UIImage au format jpg avec une qualité de 95% dans le dossier de documents de l'application si vous devez le faire.

NSString  *imagePath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"Documents/capturedImage.jpg"]];    
[UIImageJPEGRepresentation(capturedImage, 0.95) writeToFile:imagePath atomically:YES];

La capture d'écran en plein écran ne capture malheureusement pas la barre d'état. Très bel extrait cependant.
neoneye

Existe-t-il un moyen de capturer le clavier?
mrvincenzo

@tibidabo merci, cela fonctionne. Mais comment puis-je enregistrer plusieurs images?
josef

"Grande fuite de mémoire de Chesapeake!" - Hermès Conrad. (Sérieusement, gérez votre CG correctement !!)
Albert Renshaw

22

À partir d'iOS7, nous avons ci-dessous les méthodes par défaut:

- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

L'appel de la méthode ci-dessus est plus rapide que d'essayer de rendre vous-même le contenu de la vue actuelle dans une image bitmap.

Si vous souhaitez appliquer un effet graphique, tel qu'un flou, à un instantané, utilisez drawViewHierarchyInRect:afterScreenUpdates:plutôt la méthode.

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


13

Il y a une nouvelle API d'iOS 10

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

10

J'ai créé une extension utilisable pour UIView pour prendre une capture d'écran dans Swift:

extension UIView{

var screenshot: UIImage{

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

Pour l'utiliser, tapez simplement:

let screenshot = view.screenshot

1
Utilisez UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0);plutôt que UIGraphicsBeginImageContext(self.bounds.size);d'utiliser le bon facteur d'échelle de l'appareil.
knshn

1
Je confirme que cela fonctionne, mais pas utiliser à la drawViewHierarchyInRectplace de renderInContext .
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);  
}

Cette méthode peut mettre dans votre classe Controller.


2
drawRectne fait pas partie de UIViewController (IIRC). Il fait partie d'un UIView. Je ne crois pas qu'il sera appelé si c'est dans le contrôleur.
jww

Comment puis-je obtenir le chemin de l'image enregistrée?
GameDevGuru

5
CGImageRef UIGetScreenImage();

Apple nous permet désormais de l'utiliser dans une application publique, même s'il s'agit d'une API privée


Il y a d'autres UIViews en plus de myUIView que je ne veux pas capturer. Sinon, ce serait génial.
cduck

5

Détails

  • Xcode version 10.3 (10G8), Swift 5

Solution

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

Usage

let image = view.makeSnapshot()

Échantillon complet

N'oubliez pas d' ajouter le code de la solution ici

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>

Résultat

entrez la description de l'image ici entrez la description de l'image ici


Ceci est un exemple complet. Merci beaucoup pour ça!
KMC


4

J'ai créé cette extension pour enregistrer une capture d'écran depuis 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)

}}

appeler :

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

Si vous souhaitez créer un png doit changer:

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

par

UIImagePNGRepresentation(image)?.writeToFile(path, atomically: true)

Des idées pourquoi si je capture un UITableViewCell j'obtiens une vue vide, mais si je capture la tableView j'obtiens ce que j'attends?
Unome

J'ai essayé avec un exemple (UItableViewController) et cela fonctionne, peut-être mettez votre code ici pour examen
anthonyqz

L'astuce était que j'avais besoin d'utiliser un CGContextTranslateCTM (context, 0, -view.frame.origin.y);
Unome du

3

Swift 4 mis à jour:

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

Cette méthode de capture d'écran a très bien fonctionné.
eonist le

2

L'extrait suivant est utilisé pour prendre une capture d'écran:

UIGraphicsBeginImageContext(self.muUIView.bounds.size);

[myUIView.layer renderInContext:UIGraphicsGetCurrentContext()];

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

Utiliser la renderInContext:méthode au lieu de la drawInContext:méthode

renderInContext:met le récepteur et ses sous-couches dans le contexte actuel. Cette méthode effectue le rendu directement à partir de l'arborescence des couches.


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

  return image;
}

0

vous pouvez utiliser la catégorie UIView suivante -

@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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.