Take snapshot from UIScreen - ios

I want to take a snapshot from view including the status bar.
the code below works in the simulator but doesn't on a physical device.
extension UIScreen {
class func screenshot() -> UIImage {
let view = main.snapshotView(afterScreenUpdates: false)
UIGraphicsBeginImageContext(view.bounds.size)
view.drawHierarchy(in: view, afterScreenUpdates: true)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshot!
}
}

to fix your problem you can use this function:
func takeScreenshot() -> UIImage? {
var screenshotImage :UIImage?
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else {return nil}
layer.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshotImage
}
I just tested this and it works on a physical device.

Create a extension like:
extension UIApplication {
func getViewScreenShot(vc: UIViewController) -> UIImage? {
let scale = UIScreen.main.scale
let bounds = vc.view.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, false, scale)
if UIGraphicsGetCurrentContext() != nil {
vc.view.drawHierarchy(in: bounds, afterScreenUpdates: true)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return screenshot!
}
return nil
}
}
And call it like:
weak var weakSelf = self
let screenShotImage: UIImage = UIApplication.shared.getViewScreenShot(vc: weakSelf!)

Related

Get visible part of UIImageView as UIImage in Swift

In my app, I'm trying to implement a crop feature. So now I need to convert UIImageView's visible part to UIImage.
Here's a screenshot from the UI debugger.
I've tried to achieve it with this function, but it doesn't work.
func imageFromImageView(imageView: UIImageView) -> UIImage? {
UIGraphicsBeginImageContext(imageView.frame.size)
let context = UIGraphicsGetCurrentContext()!
context.rotate(by: 2 * .pi)
imageView.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
You can use bellow extension:
private var rendererKey: UInt8 = 0
extension UIView {
var renderer: UIGraphicsImageRenderer! {
get {
guard let rendererInstance = objc_getAssociatedObject(self, &rendererKey) as? UIGraphicsImageRenderer else {
self.renderer = UIGraphicsImageRenderer(bounds: bounds)
return self.renderer
}
return rendererInstance
}
set(newValue) {
objc_setAssociatedObject(self, &rendererKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
func snapImageView() -> UIImageView {
let img:UIImage = renderer.image { ctx in
DispatchQueue.main.async {
layer.render(in: ctx.cgContext)
}
}
let imageView:UIImageView = UIImageView(image: img)
imageView.frame = renderer.format.bounds
imageView.clipsToBounds = true
return imageView
}
}
// Generate image and image view of any view instance
let anImageView = yourView.snapImageView()

Why does UIGraphicsGetCurrentContext return nil after UIGraphicsBeginImageContext

I am following a code example to make a blurred UILabel, https://stackoverflow.com/a/62224908/2226315.
My requirement is to make the label on blur after label initialization instead of calling the blur method at runtime. However, when I try to call blur after label gets initialized the value returned from UIGraphicsGetCurrentContext is nil hence having a "Fatal error: Unexpectedly found nil while unwrapping an Optional value"
UIGraphicsBeginImageContext(bounds.size)
print("DEBUG: bounds.size", bounds.size)
self.layer.render(in: UIGraphicsGetCurrentContext()!) // <- return nil
var image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
print("DEBUG: image image", image)
I tried adding the code in all the following places individually, the context can be fetched now however it does not generate the blur effect as expected.
override func layoutSubviews() {
super.layoutSubviews()
self.blur()
}
// OR
override func draw(_ rect: CGRect) {
super.draw(rect)
self.blur()
}
Full code snippet,
class BlurredLabel: UILabel {
func blur(_ blurRadius: Double = 2.5) {
let blurredImage = getBlurryImage(blurRadius)
let blurredImageView = UIImageView(image: blurredImage)
blurredImageView.translatesAutoresizingMaskIntoConstraints = false
blurredImageView.tag = 100
blurredImageView.contentMode = .center
blurredImageView.backgroundColor = .white
addSubview(blurredImageView)
NSLayoutConstraint.activate([
blurredImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
blurredImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
func unblur() {
subviews.forEach { subview in
if subview.tag == 100 {
subview.removeFromSuperview()
}
}
}
private func getBlurryImage(_ blurRadius: Double = 2.5) -> UIImage? {
UIGraphicsBeginImageContext(bounds.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
guard let image = UIGraphicsGetImageFromCurrentImageContext(),
let blurFilter = CIFilter(name: "CIGaussianBlur") else {
UIGraphicsEndImageContext()
return nil
}
UIGraphicsEndImageContext()
blurFilter.setDefaults()
blurFilter.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)
var convertedImage: UIImage?
let context = CIContext(options: nil)
if let blurOutputImage = blurFilter.outputImage,
let cgImage = context.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
convertedImage = UIImage(cgImage: cgImage)
}
return convertedImage
}
}
REFERENCE
Add blur view to label?
How to blur UILabel text
UPDATE
Usage based on "Eugene Dudnyk" answer
definitionLabel = BlurredLabel()
definitionLabel.numberOfLines = 0
definitionLabel.lineBreakMode = .byWordWrapping
definitionLabel.textColor = UIColor(named: "text")
definitionLabel.text = "Lorem Ipsum is simply dummy text"
definitionLabel.clipsToBounds = false
definitionLabel.isBluring = true
Here is a better solution - instead of retrieving the blurred image, just let the label blur itself.
When you need it to be blurred, set label.isBlurring = true.
Also, this solution is better for performance, because it reuses the same context and does not need the image view.
class BlurredLabel: UILabel {
var isBlurring = false {
didSet {
setNeedsDisplay()
}
}
var blurRadius: Double = 2.5 {
didSet {
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
}
}
lazy var blurFilter: CIFilter? = {
let blurFilter = CIFilter(name: "CIGaussianBlur")
blurFilter?.setDefaults()
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
return blurFilter
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.isOpaque = false
layer.needsDisplayOnBoundsChange = true
layer.contentsScale = UIScreen.main.scale
layer.contentsGravity = .center
isOpaque = false
isUserInteractionEnabled = false
contentMode = .redraw
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func display(_ layer: CALayer) {
let bounds = layer.bounds
guard !bounds.isEmpty && bounds.size.width < CGFloat(UINT16_MAX) else {
layer.contents = nil
return
}
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, layer.contentsScale)
if let ctx = UIGraphicsGetCurrentContext() {
self.layer.draw(in: ctx)
var image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
if isBlurring, let cgImage = image {
blurFilter?.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
let ciContext = CIContext(cgContext: ctx, options: nil)
if let blurOutputImage = blurFilter?.outputImage,
let cgImage = ciContext.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
image = cgImage
}
}
layer.contents = image
}
UIGraphicsEndImageContext()
}
}
Transformed #EugeneDudnyk answer to UIView extension so it can be used also with TextView.
extension UIView {
struct BlurableKey {
static var blurable = "blurable"
}
func blur(radius: CGFloat) {
guard superview != nil else { return }
UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard
let blur = CIFilter(name: "CIGaussianBlur"),
let image = image
else {
return
}
blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blur.setValue(radius, forKey: kCIInputRadiusKey)
let ciContext = CIContext(options: nil)
let boundingRect = CGRect(
x:0,
y: 0,
width: frame.width,
height: frame.height
)
guard
let result = blur.value(forKey: kCIOutputImageKey) as? CIImage,
let cgImage = ciContext.createCGImage(result, from: boundingRect)
else {
return
}
let blurOverlay = UIImageView()
blurOverlay.frame = boundingRect
blurOverlay.image = UIImage(cgImage: cgImage)
blurOverlay.contentMode = .left
addSubview(blurOverlay)
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
blurOverlay,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
func unBlur() {
guard
let blurOverlay = objc_getAssociatedObject(self, &BlurableKey.blurable) as? UIImageView
else {
return
}
blurOverlay.removeFromSuperview()
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
nil,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
var isBlurred: Bool {
return objc_getAssociatedObject(self, &BlurableKey.blurable) is UIImageView
}
}

UIView to UIImage without subviews and hidden

I've got a UIView that I draw on just like a fingerpainting app, but, it's invisible sometimes. And I want to be able to take a screenshot of it when it's invisible. Also, I want a screenshot of it when it is visible however I dont want any of the subviews. I only want the UIView itself. Here's somethings I've tried:
func shapshot() -> UIImage? {
UIGraphicsBeginImageContext(self.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
self.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if image == nil {
return nil
}
// if !SettingsManager.shared.isColorInverted {
//return image!.invertedImage()
// }
return image
}
func snapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, self.isOpaque, UIScreen.main.scale)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
Both aren't working.
try this,
func snapshot() -> UIImage {
let bounds = UIScreen.main.bounds
UIGraphicsBeginImageContextWithOptions(bounds.size, true, 0.0)
self.view.drawHierarchy(in: bounds, afterScreenUpdates: true)
self.btnShare.isHidden = true
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}

An empty snapshotView on iPhone 7/7plus

My first question here:) Recently I update my Xcode to 8, and the resizableSnapshotView method doesn't work properly on some simulators. The snapshotView works well on all testing devices with iOS9/10 and simulators under iPhone6s, but it is empty on iPhone7/7p simulators. I think 7/7p may need some authorities for accessing snapshot, but I have no idea what they are.
let cell = self.tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as! CalendarCell
var topFrame = cell.frame
topFrame.origin.y = tableView.contentOffset.y
topFrame.size.height -= tableView.contentOffset.y
topSnapshotView = tableView.resizableSnapshotView(from: topFrame, afterScreenUpdates: false, withCapInsets: UIEdgeInsets.zero)
Use the following UIView extension to create a snapshot using CoreGraphics.
I can confirm this works on iPhone 7 simulator.
public extension UIView {
public func snapshotImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: false)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImage
}
public func snapshotView() -> UIView? {
if let snapshotImage = snapshotImage() {
return UIImageView(image: snapshotImage)
} else {
return nil
}
}
}
Works in:
Version 8.1 (8B62)
Simulator Version 10.0 (SimulatorApp-700.14
Swift 3
import Foundation
import UIKit
extension UIView {
func snapshotView() -> UIView? {
guard let image = snapshotImage() else { return nil }
return UIImageView(image: image)
}
func snapshotImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, contentScaleFactor)
defer { UIGraphicsEndImageContext() }
drawHierarchy(in: bounds, afterScreenUpdates: false)
return UIGraphicsGetImageFromCurrentImageContext()
}
}

Add blur view to label?

How can I add a blur view to a label? The label is in front of a UIImage and I wanted the background of the label to be blurred, so that the user can read the text better. I get the Blur effect inside the bounds of the label, but the text itself disappears (maybe also gets blurred, idk why). I also tried to add a label programmatically, but I didn't get it working. I'm thankful for any kind of help!
let blur = UIBlurEffect(style: .Light)
let blurView = UIVisualEffectView(effect: blur)
blurView.frame = findATeamLabel.bounds
findATeamLabel.addSubview(blurView)
You can make your own BlurredLabel which can blur/unblur its text. Through a CoreImage blur filter you take the text of the label, blur it in an image, and display that image on top of the label.
class BlurredLabel: UILabel {
func blur(_ blurRadius: Double = 2.5) {
let blurredImage = getBlurryImage(blurRadius)
let blurredImageView = UIImageView(image: blurredImage)
blurredImageView.translatesAutoresizingMaskIntoConstraints = false
blurredImageView.tag = 100
blurredImageView.contentMode = .center
blurredImageView.backgroundColor = .white
addSubview(blurredImageView)
NSLayoutConstraint.activate([
blurredImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
blurredImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
func unblur() {
subviews.forEach { subview in
if subview.tag == 100 {
subview.removeFromSuperview()
}
}
}
private func getBlurryImage(_ blurRadius: Double = 2.5) -> UIImage? {
UIGraphicsBeginImageContext(bounds.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
guard let image = UIGraphicsGetImageFromCurrentImageContext(),
let blurFilter = CIFilter(name: "CIGaussianBlur") else {
UIGraphicsEndImageContext()
return nil
}
UIGraphicsEndImageContext()
blurFilter.setDefaults()
blurFilter.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)
var convertedImage: UIImage?
let context = CIContext(options: nil)
if let blurOutputImage = blurFilter.outputImage,
let cgImage = context.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
convertedImage = UIImage(cgImage: cgImage)
}
return convertedImage
}
}
PS: Please make sure to improve this component as of your requirements (for example avoid blurring if already blurred or you could remove the current blurred and apply the blurred again if text has changed).
PSPS: Take into consideration also that applying blur to something makes its content bleeds out, so either set clipsToBounds = false to the BlurredLabel or find out other way to accomplish your visual effect in order to avoid the blurred image looks like is not in same position as the label unblurred text that was previously.
To use it you can simply create a BlurredLabel:
let blurredLabel = BlurredLabel()
blurredLabel.text = "56.00 €"
And on some button tap maybe you could achieve blurring as of blurredLabel.blur() and unblurring as of blurredLabel.unblur().
This is the output achieved with blur() through a blurRadius of 2.5:
To read more about Gaussian Blur, there is a good article on Wikipedia: https://en.wikipedia.org/wiki/Gaussian_blur
You could try sending it to the back of the view hierarchy for the label. Try
findATeamLabel.sendSubviewToBack(blurView)
Swift 5 - Blur as UIView extension
extension UIView {
struct BlurableKey {
static var blurable = "blurable"
}
func blur(radius: CGFloat) {
guard superview != nil else { return }
UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard
let blur = CIFilter(name: "CIGaussianBlur"),
let image = image
else {
return
}
blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blur.setValue(radius, forKey: kCIInputRadiusKey)
let ciContext = CIContext(options: nil)
let boundingRect = CGRect(
x:0,
y: 0,
width: frame.width,
height: frame.height
)
guard
let result = blur.value(forKey: kCIOutputImageKey) as? CIImage,
let cgImage = ciContext.createCGImage(result, from: boundingRect)
else {
return
}
let blurOverlay = UIImageView()
blurOverlay.frame = boundingRect
blurOverlay.image = UIImage(cgImage: cgImage)
blurOverlay.contentMode = .left
addSubview(blurOverlay)
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
blurOverlay,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
func unBlur() {
guard
let blurOverlay = objc_getAssociatedObject(self, &BlurableKey.blurable) as? UIImageView
else {
return
}
blurOverlay.removeFromSuperview()
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
nil,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
var isBlurred: Bool {
return objc_getAssociatedObject(self, &BlurableKey.blurable) is UIImageView
}
}
I got it working by adding just a View behind the label (the label is NOT inside that view, just in front of it). Then I added the blur effect to the view... I still think there should be an easier way.
None of the answers worked for me, the accepted answer dont work with background of different color of white, or label color different of black based on the comments it goes to another question yet the answer made the blur move to the right a lot. So after some reading adjusting the CGRect on the result image.
class BlurredLabel: UILabel {
var isBlurring = false {
didSet {
setNeedsDisplay()
}
}
var blurRadius: Double = 8 {
didSet {
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
}
}
lazy var blurFilter: CIFilter? = {
let blurFilter = CIFilter(name: "CIGaussianBlur")
blurFilter?.setDefaults()
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
return blurFilter
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.isOpaque = false
layer.needsDisplayOnBoundsChange = true
layer.contentsScale = UIScreen.main.scale
layer.contentsGravity = .center
isOpaque = false
isUserInteractionEnabled = false
contentMode = .redraw
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func display(_ layer: CALayer) {
let bounds = layer.bounds
guard !bounds.isEmpty && bounds.size.width < CGFloat(UINT16_MAX) else {
layer.contents = nil
return
}
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, layer.contentsScale)
if let ctx = UIGraphicsGetCurrentContext() {
self.layer.draw(in: ctx)
var image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
if isBlurring {
blurFilter?.setValue(CIImage(cgImage: image!), forKey: kCIInputImageKey)
let ciContext = CIContext(cgContext: ctx, options: nil)
if let blurOutputImage = blurFilter?.outputImage {
let boundingRect = CGRect(
x:0,
y: blurOutputImage.extent.minY,
width: blurOutputImage.extent.width,
height: blurOutputImage.extent.height
)
image = ciContext.createCGImage(blurOutputImage, from: boundingRect)
}
}
layer.contents = image
}
UIGraphicsEndImageContext()
}
}

Resources