Obtenir une capture d'écran d'un UIScrollView, y compris les parties hors écran

J'ai un UIScrollView decendent qui implémente une méthode takeScreenshot qui ressemble à ceci:

 -(void)takeScreenshot { CGRect contextRect = CGRectMake(0, 0, 768, 1004); UIGraphicsBeginImageContext(contextRect.size); [self.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // do something with the viewImage here. } 

Cela se déplace essentiellement vers le haut de la vue défilement, et prend une capture d'écran de la zone visible. Cela fonctionne bien quand l'iPad est orienté portrait, mais quand il est en paysage le fond de l'image est coupé (comme la hauteur de la zone visible est seulement 748, pas 1004).

Est-il possible d'get un instantané de l' UIScrollView , y compris les zones qui ne sont pas à l'écran? Ou dois-je faire défiler la vue, prendre une deuxième photo et les assembler?

    Voici un code qui fonctionne …

     - (IBAction) renderScrollViewToImage { UIImage* image = nil; UIGraphicsBeginImageContext(_scrollView.contentSize); { CGPoint savedContentOffset = _scrollView.contentOffset; CGRect savedFrame = _scrollView.frame; _scrollView.contentOffset = CGPointZero; _scrollView.frame = CGRectMake(0, 0, _scrollView.contentSize.width, _scrollView.contentSize.height); [_scrollView.layer renderInContext: UIGraphicsGetCurrentContext()]; image = UIGraphicsGetImageFromCurrentImageContext(); _scrollView.contentOffset = savedContentOffset; _scrollView.frame = savedFrame; } UIGraphicsEndImageContext(); if (image != nil) { [UIImagePNGRepresentation(image) writeToFile: @"/tmp/test.png" atomically: YES]; system("open /tmp/test.png"); } } 

    Les dernières lignes écrivent simplement l'image dans /tmp/test.png, puis l'ouvre dans Preview.app. Cela ne fonctionne évidemment que dans le simulateur 🙂

    Projet complet dans le référentiel Github ScrollViewScreenShot

    pour moi, la réponse https://stackoverflow.com/a/3539944/4164443 ne fonctionnait pas. J'avais une tâche de mise en œuvre sur iOS 8.

    En fait, cette méthode fonctionne pour iPhone, mais iPad (à la fois simu et réel) est un autre cas. Cela rend juste la partie visible et le rest de l'image est juste vide.

    J'ai essayé avec drawViewHierarchyInRect – pas de chance. Selon afterScreenUpdates étant true ou false j'ai été étiré une partie de l'image ou encore seulement une partie de l'image.

    Donc, la seule façon que j'ai trouvé pour get une capture d'écran correcte est d'append scrollview à une autre vue temporaire et de le rendre.

    Exemple de code est ci-dessous (scrollview est sortie dans mon VC)

     func getImageOfScrollView()->UIImage{ var image = UIImage(); UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.mainScreen().scale) // save initial values let savedContentOffset = self.scrollView.contentOffset; let savedFrame = self.scrollView.frame; let savedBackgroundColor = self.scrollView.backgroundColor // reset offset to top left point self.scrollView.contentOffset = CGPointZero; // set frame to content size self.scrollView.frame = CGRectMake(0, 0, self.scrollView.contentSize.width, self.scrollView.contentSize.height); // remove background self.scrollView.backgroundColor = UIColor.clearColor() // make temp view with scroll view content size // a workaround for issue when image on ipad was drawn incorrectly let tempView = UIView(frame: CGRectMake(0, 0, self.scrollView.contentSize.width, self.scrollView.contentSize.height)) // save superview let tempSuperView = self.scrollView.superview // remove scrollView from old superview self.scrollView.removeFromSuperview() // and add to tempView tempView.addSubview(self.scrollView) // render view // drawViewHierarchyInRect not working correctly tempView.layer.renderInContext(UIGraphicsGetCurrentContext()) // and get image image = UIGraphicsGetImageFromCurrentImageContext(); // and return everything back tempView.subviews[0].removeFromSuperview() tempSuperView?.addSubview(self.scrollView) // restore saved settings self.scrollView.contentOffset = savedContentOffset; self.scrollView.frame = savedFrame; self.scrollView.backgroundColor = savedBackgroundColor UIGraphicsEndImageContext(); return image } 

    Exemple de travail d'Extension UIView avec gestion pour UIScrollView:

     extension UIView { func screenshot() -> UIImage { if(self is UIScrollView) { let scrollView = self as! UIScrollView let savedContentOffset = scrollView.contentOffset let savedFrame = scrollView.frame UIGraphicsBeginImageContext(scrollView.contentSize) scrollView.contentOffset = .zero self.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height) self.layer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext(); scrollView.contentOffset = savedContentOffset scrollView.frame = savedFrame return image! } UIGraphicsBeginImageContext(self.bounds.size) self.layer.render(in: UIGraphicsGetCurrentContext()!) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image! } } 

    Si vous ne voulez pas étendre votre vue de défilement au-delà de l'écran entier (et cela ne fonctionnera pas avec autolayout de toute façon) il y a une meilleure façon.

    Vous pouvez utiliser des transformations charts de base en conjonction avec le contentOffset de la vue contentOffset pour accomplir la même chose.

     // // ScrollViewSnapshotter.swift // ScrollViewSnapshotter // // Created by Moshe Berman on 4/10/16. // Copyright © 2016 Moshe Berman. All rights reserved. // import UIKit class ScrollViewSnapshotter: NSObject { func PDFWithScrollView(scrollview: UIScrollView) -> NSData { /** * Step 1: The first thing we need is the default origin and size of our pages. * Since bounds always start at (0, 0) and the scroll view's bounds give us * the correct size for the visible area, we can just use that. * * In the United States, a standard printed page is 8.5 inches by 11 inches, * but when generating a PDF it's simpler to keep the page size matching the * visible area of the scroll view. We can let our printer software (such * as the Preview app on OS X or the Printer app on iOS) do the scaling. * * If we wanted to scale ourselves, we could multiply each of those * numbers by 72, to get the number of points for each dimension. * We would have to change how we generated the the pages below, so * for simplicity, we're going to stick to one page per screenful of content. */ let pageDimensions = scrollview.bounds /** * Step 2: Now we need to know how many pages we will need to fit our content. * To get this, we divide our scroll views dimensions by the size * of each page, in either direction. * We also need to round up, so that the pages don't get clipped. */ let pageSize = pageDimensions.size let totalSize = scrollview.contentSize let numberOfPagesThatFitHorizontally = Int(ceil(totalSize.width / pageSize.width)) let numberOfPagesThatFitVertically = Int(ceil(totalSize.height / pageSize.height)) /** * Step 3: Set up a Core Graphics PDF context. * * First we create a backing store for the PDF data, then * pass it and the page dimensions to Core Graphics. * * We could pass in some document information here, which mostly cover PDF metadata, * including author name, creator name (our software) and a password to * require when viewing the PDF file. * * Also note that we can use UIGraphicsBeginPDFContextToFile() instead, * which writes the PDF to a specified path. I haven't played with it, so * I don't know if the data is written all at once, or as each page is closed. */ let outputData = NSMutableData() UIGraphicsBeginPDFContextToData(outputData, pageDimensions, nil) /** * Step 4: Remember some state for later. * Then we need to clear the content insets, so that our * core graphics layer and our content offset match up. * We don't need to reset the content offset, because that * happens implicitly, in the loop below. */ let savedContentOffset = scrollview.contentOffset let savedContentInset = scrollview.contentInset scrollview.contentInset = UIEdgeInsetsZero /** * Step 6: Now we loop through the pages and generate the data for each page. */ if let context = UIGraphicsGetCurrentContext() { for indexHorizontal in 0 ..< numberOfPagesThatFitHorizontally { for indexVertical in 0 ..< numberOfPagesThatFitVertically { /** * Step 6a: Start a new page. * * This automatically closes the previous page. * There's a similar method UIGraphicsBeginPDFPageWithInfo, * which allows you to configure the rectangle of the page and * other metadata. */ UIGraphicsBeginPDFPage() /** * Step 6b:The sortingck here is to move the visible portion of the * scroll view *and* adjust the core graphics context * appropriately. * * Consider that the viewport of the core graphics context * is attached to the top of the scroll view's content view * and we need to push it in the opposite direction as we scroll. * Further, anything not inside of the visible area of the scroll * view is clipped, so scrolling will move the core graphics viewport * out of the rendered area, producing empty pages. * * To counter this, we scroll the next screenful into view, and adjust * the core graphics context. Note that core graphics uses a coordinate * system which has the y coordinate decreasing as we go from top to bottom. * This is the opposite of UIKit (although it matches AppKit on OS X.) */ let offsetHorizontal = CGFloat(indexHorizontal) * pageSize.width let offsetVertical = CGFloat(indexVertical) * pageSize.height scrollview.contentOffset = CGPointMake(offsetHorizontal, offsetVertical) CGContextTranslateCTM(context, -offsetHorizontal, -offsetVertical) // NOTE: Negative offsets /** * Step 6c: Now we are ready to render the page. * * There are faster ways to snapshot a view, but this * is the most straightforward way to render a layer * into a context. */ scrollview.layer.renderInContext(context) } } } /** * Step 7: End the document context. */ UIGraphicsEndPDFContext() /** * Step 8: Restore the scroll view. */ scrollview.contentInset = savedContentInset scrollview.contentOffset = savedContentOffset /** * Step 9: Return the data. * You can write it to a file, or display it the user, * or even pass it to iOS for sharing. */ return outputData } } 

    Voici un article de blog que j'ai écrit pour expliquer le process.

    Le process de génération d'un PDF est très similaire à l'instantané d'une image, sauf qu'au lieu de pages, vous devez créer une grande canvas correspondant à la taille de l'affichage déroulant, puis extraire le contenu en morceaux.

    Je ne sais pas grand-chose mais je peux deviner que si on définit la taille du contextRect comme ça pour le paysage, ça peut bien marcher:

      CGRect contextRect = CGRectMake(0, 0, 1004, 768*2); 

    Parce que ce contextRect va déterminer la taille de UIGraphicsBeginImageContext donc j'espère que le double de la hauteur peut résoudre votre problème

    Voici une autre façon de le faire, qui prend en count le niveau de zoom. J'ai un scrollview avec 4 différentes couches UIImageView, et je veux prendre une capture d'écran de leur état actuel:

     float theScale = 1.0f / theScrollView.zoomScale; // The viewing rectangle in absolute coordinates CGRect visibleArea = CGRectMake((int)(theScrollView.contentOffset.x * theScale), (int)(theScrollView.contentOffset.y * theScale), (int)(theScrollView.bounds.size.width * theScale), (int)(theScrollView.bounds.size.height * theScale)); NSArray *layers = [NSArray arrayWithObjects:imageLayer1, imageLayer2, imageLayer3, imageLayer4, nil]; UIGraphicsBeginImageContext(visibleArea.size); for (UIImageView *layer in layers) { CALayer *coreLayer = layer.layer; coreLayer.bounds = CGRectMake(layer.frame.origin.x - visibleArea.origin.x, layer.frame.origin.y - visibleArea.origin.y, layer.frame.size.width, layer.frame.size.height); [coreLayer renderInContext:UIGraphicsGetCurrentContext()]; } UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 

    Cela prend la capture d'écran en coordonnées absolues. C'est-à-dire, si vous avez une image 2048 * 2048 dans le scrollview et vous pouvez en voir environ un quart, alors quelle que soit la résolution de votre écran, il faudrait une capture d'écran de 512 * 512. Si vous voulez prendre une capture d'écran à la résolution de votre écran (par exemple, 320 * 480), vous devez ajuster l'image comme suit, directement après le code ci-dessus:

     UIGraphicsBeginImageContext(theScrollView.frame.size); [screenshot drawInRect:CGRectMake(0, 0, theScrollView.frame.size.width, theScrollView.frame.size.height)]; UIImage *smallScreenshot = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); 

    Version SWIFT 3 grâce à @gleb vodovozov:

     func getImageOfScrollView()->UIImage{ var image = UIImage(); UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.main.scale) // save initial values let savedContentOffset = self.scrollView.contentOffset; let savedFrame = self.scrollView.frame; let savedBackgroundColor = self.scrollView.backgroundColor // reset offset to top left point self.scrollView.contentOffset = CGPoint.zero; // set frame to content size self.scrollView.frame = CGRect(x: 0, y: 0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height) // remove background self.scrollView.backgroundColor = UIColor.clear // make temp view with scroll view content size // a workaround for issue when image on ipad was drawn incorrectly let tempView = UIView(frame: CGRect(x: 0, y: 0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height)) // save superview let tempSuperView = self.scrollView.superview // remove scrollView from old superview self.scrollView.removeFromSuperview() // and add to tempView tempView.addSubview(self.scrollView) // render view // drawViewHierarchyInRect not working correctly tempView.layer.render(in: UIGraphicsGetCurrentContext()!) // and get image image = UIGraphicsGetImageFromCurrentImageContext()!; // and return everything back tempView.subviews[0].removeFromSuperview() tempSuperView?.addSubview(self.scrollView) // restore saved settings self.scrollView.contentOffset = savedContentOffset; self.scrollView.frame = savedFrame; self.scrollView.backgroundColor = savedBackgroundColor UIGraphicsEndImageContext(); return image }