How to take screenshot of portion of UIView?

2019-01-17 13:20发布

问题:

I want the user to go on my app and take a screenshot of the app after pressing a button programmatically in Swift. I know that UIGraphicsGetImageFromCurrentImageContext() takes a screenshot but I don't want a picture of the entire screen. I want a rectangle to pop up (sort of like a crop tool) and the user can drag and resize the rectangle to take a screenshot of only a certain part of the screen. I want the rectangle to go over a WKWebView and crop a pic of the web view.

回答1:

The standard snapshot technique is drawViewHierarchyInRect. And to get portion of a image, you can use CGImageCreateWithImageInRect.

Thus, in Swift 2:

extension UIView {

    /// Create snapshot
    ///
    /// - parameter rect: The `CGRect` of the portion of the view to return. If `nil` (or omitted),
    ///                   return snapshot of the whole view.
    ///
    /// - returns: Returns `UIImage` of the specified portion of the view.

    func snapshot(of rect: CGRect? = nil) -> UIImage? {
        // snapshot entire view

        UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, 0)
        drawViewHierarchyInRect(bounds, afterScreenUpdates: true)
        let wholeImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // if no `rect` provided, return image of whole view

        guard let rect = rect, let image = wholeImage else { return wholeImage }

        // otherwise, grab specified `rect` of image

        let scale = image.scale
        let scaledRect = CGRect(x: rect.origin.x * scale, y: rect.origin.y * scale, width: rect.size.width * scale, height: rect.size.height * scale)
        guard let cgImage = CGImageCreateWithImageInRect(image.CGImage!, scaledRect) else { return nil }
        return UIImage(CGImage: cgImage, scale: scale, orientation: .Up)
    }

}

In Swift 3:

extension UIView {

    /// Create snapshot
    ///
    /// - parameter rect: The `CGRect` of the portion of the view to return. If `nil` (or omitted),
    ///                   return snapshot of the whole view.
    ///
    /// - returns: Returns `UIImage` of the specified portion of the view.

    func snapshot(of rect: CGRect? = nil) -> UIImage? {
        // snapshot entire view

        UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
        drawHierarchy(in: bounds, afterScreenUpdates: true)
        let wholeImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // if no `rect` provided, return image of whole view

        guard let image = wholeImage, let rect = rect else { return wholeImage }

        // otherwise, grab specified `rect` of image

        let scale = image.scale
        let scaledRect = CGRect(x: rect.origin.x * scale, y: rect.origin.y * scale, width: rect.size.width * scale, height: rect.size.height * scale)
        guard let cgImage = image.cgImage?.cropping(to: scaledRect) else { return nil }
        return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
    }

}

And to use it, you can do:

if let image = webView.snapshot(of: rect) {
    // do something with `image` here
}


回答2:

This is a previously asked question at How to capture UIView to UIImage without loss of quality on retina display but to expand in swift (2.3):

extension UIView {

    class func image(view: UIView) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return nil
        }
        view.layer.renderInContext(ctx)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img
    }

    func image() -> UIImage? {
        return UIView.image(self)
    }
}

So you can either get an image from a view with UIView.image(theView) or by asking the view itself let viewImage = self.view.image()

Do bear in mind though that this is rough and probably needs further looking at for thread safety etc....