extension UIView {
func capture() -> UIImage? {
var image: UIImage?
if #available(iOS 10.0, *) {
let format = UIGraphicsImageRendererFormat()
format.opaque = isOpaque
let renderer = UIGraphicsImageRenderer(size: frame.size, format: format)
image = renderer.image { context in
drawHierarchy(in: frame, afterScreenUpdates: true)
}
} else {
UIGraphicsBeginImageContextWithOptions(frame.size, isOpaque, UIScreen.main.scale)
drawHierarchy(in: frame, afterScreenUpdates: true)
image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
return image
}
}
public extension AVAsset {
func takeScreenshot(at: Double,completion: #escaping (UIImage?) -> Void) {
DispatchQueue.global().async {
let imageGenerator = AVAssetImageGenerator(asset: self)
let time = CMTime(seconds: at, preferredTimescale: 600)
let times = [NSValue(time: time)]
imageGenerator.generateCGImagesAsynchronously(forTimes: times, completionHandler: { _, image, _, _, _ in
if let image = image {
completion(UIImage(cgImage: image))
} else {
completion(nil)
}
})
}
}
}
Related
We got a project from another team as the original dev left the org. I need to fix a defect. I have some code like below where UIGraphicsBeginPDFContextToData happens on Global queue. But this gives memory warnings as below UIView extension's renderToImage does not happens on main thread.
I wanted to wrap the renderToImage in DispatchQueue.main.async but it would break the for loop in someFunction. I can't use .sync on main thread. Can someone tell me how can I finish renderToImage waiting on Global queue?
ORIGINAL CODE:
DispatchQueue.global(qos: .userInitiated).async {
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
zip(self.pdfPages, self.pageViews).forEach { (page, view) in
let image = view.renderToImage()
guard let imageData = image.jpegData(compressionQuality: 0.5),
let compressedImage = UIImage(data: imageData) else { return }
UIGraphicsBeginPDFPageWithInfo(CGRect(origin: CGPoint.zero, size: page.originSize), nil)
compressedImage.draw(in: CGRect(origin: CGPoint.zero, size: page.originSize))
DispatchQueue.main.async {
self.progressLabel.text = "Processing PDF... \(page.pageNumber) / \(pageNumbers)"
}
}
UIGraphicsEndPDFContext()
DispatchQueue.main.async {
self.progressIndicator.stopAnimating()
self.progressView.isHidden = true
completionBlock?(pdfData as Data)
}
}
extension UIView {
func renderToImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
After applying #paulw11 answer:
fileprivate func generatePDF(_ completionBlock: ((_ pdfData: Data) -> Void)?) {
let pageNumbers = pageViews.count
progressView.isHidden = false
progressIndicator.startAnimating()
progressLabel.text = "Processing PDF... \(0) / \(pageNumbers)"
let semaphore = DispatchSemaphore(value: 0)
let pdfContextQueue = DispatchQueue(label: kMessagesFaxPDFConversionQueue, target: DispatchQueue.global(qos: .userInitiated))
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
pdfContextQueue.async {
zip(self.pdfPages, self.pageViews).forEach { (page, view) in
view.renderToImage { image in
guard let imageData = image.jpegData(compressionQuality: 0.5),
let compressedImage = UIImage(data: imageData) else { return }
UIGraphicsBeginPDFPageWithInfo(CGRect(origin: CGPoint.zero, size: page.originSize), nil)
compressedImage.draw(in: CGRect(origin: CGPoint.zero, size: page.originSize))
DispatchQueue.main.async {
self.progressLabel.text = "Processing PDF... \(page.pageNumber) / \(pageNumbers)"
}
semaphore.signal()
}
semaphore.wait()
}
UIGraphicsEndPDFContext()
DispatchQueue.main.async {
self.progressIndicator.stopAnimating()
self.progressView.isHidden = true
completionBlock?(pdfData as Data)
}
}
}
extension UIView {
func renderToImage(completion: #escaping ((UIImage) -> Void)) {
DispatchQueue.main.async {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
completion(UIImage()); return
}
self.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
completion(image!)
}
}
}
The first thing I would check is if you can't simply perform the work on the main queue - Does it really take that long that there is a noticeable impact on the UI? What else would the user be doing while the PDF was being generated anyway? If they are waiting for the PDF to be generated so that it can be shared or printed, then simply showing a spinner may be enough.
If you do want to keep the work on another queue then I would convert renderToImage to an asynchronous function and use a DispatchSemaphore to block your work queue until the loop iteration is complete.
func someFunction() {
let semaphore = DispatchSemaphore(value:0)
let queue = DispatchQueue(label:"PDFQueue", target: DispatchQueue.global(qos: .userInitiated))
queue.async {
for view in PDFViews {
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
zip(self.pdfPages, self.pageViews).forEach { (page, view) in
view.renderToImage { image in
if let image = image {
...
...
}
semaphore.signal()
}
semaphore.wait()
}
}
}
}
extension UIView {
func renderToImage(completion: ((UIImage?)->Void)) {
DispatchQueue.main.async {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
guard let context = UIGraphicsGetCurrentContext() else {
completion(nil)
}
layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
completion(image)
}
}
}
I want to take a screenshot from WKWebView that contains a playing video. I've tried a lot of methods, they work well on the simulator, but can't capture the content of the video on the device. Video content cannot be cpatured on the device:
Simulator:
Simulator
Device:
Device
I tried the following ways:
CALayer#render
let window = UIApplication.shared.windows.first { $0.isKeyWindow }
if window == nil { return }
let layer = window!.layer
let screenRect = window!.screen.bounds
UIGraphicsBeginImageContext(screenRect.size)
let ctx:CGContext = UIGraphicsGetCurrentContext()!
layer.render(in: ctx)
self.screenShotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
and
UIView#drawHierarchy
let window = UIApplication.shared.windows.first { $0.isKeyWindow }
if window == nil { return }
UIGraphicsBeginImageContextWithOptions(window!.frame.size, false, 0)
window!.drawHierarchy(in: window!.frame, afterScreenUpdates: true)
self.screenShotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
and
WKWebView#takeSnapshot
let config = WKSnapshotConfiguration()
config.rect = WKWebViewHolder.webView!.frame
config.afterScreenUpdates = false
webView.takeSnapshot(with: config, completionHandler: { (image: UIImage?, error: Error?) in
if error != nil {
return
}
self.screenShotImage = image
})
Can someone help me? 🤔
You can try the below code to take the screenshot:
Details
Xcode 9.3, Swift 4.1.
Xcode 10.2 (10E125) and 11.0 (11A420a), Swift 5.
Tested on iOS: 9, 10, 11, 12, 13
Solution
import UIKit
extension UIApplication {
func getKeyWindow() -> UIWindow? {
if #available(iOS 13, *) {
return windows.first { $0.isKeyWindow }
} else {
return keyWindow
}
}
func makeSnapshot() -> UIImage? { return getKeyWindow()?.layer.makeSnapshot() }
}
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()
}
}
}
extension UIImage {
convenience init?(snapshotOf view: UIView) {
guard let image = view.makeSnapshot(), let cgImage = image.cgImage else { return nil }
self.init(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation)
}
}
Usage
imageView.image = UIApplication.shared.makeSnapshot()
// or
imageView.image = view.makeSnapshot()
// or
imageView.image = view.layer.makeSnapshot()
// or
imageView.image = UIImage(snapshotOf: view)
I'm using rich notifications to display a https url image in a push notification. The image is full screen. The problem is the rich notification displays the center of the image which cuts off the top and bottom of the image.
I tried to shrink the image before sending it, but it still gets cut off.
I tried to shrink the image when retrieving it and displaying it but it still gets cut off.
How can I get the entire image to fit inside the rich notification attachment?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
func failEarly() {
contentHandler(request.content)
}
guard let bestAttemptContent = bestAttemptContent else { failEarly(); return }
guard let attachmentUrlString = request.content.userInfo["media-url"] as? String else { failEarly(): return }
guard let url = URL(string: attachmentUrlString) else { failEarly(); return }
URLSession.shared.downloadTask(with: url, completionHandler: { [weak self](tempLocation, response, error) -> Void in
if let error = error { return }
guard let location = tempLocation else { return }
guard let response = response else { return }
do {
let lastPathComponent = response.url?.lastPathComponent ?? ""
var attachmentID = UUID.init().uuidString + lastPathComponent
if response.suggestedFilename != nil {
attachmentID = UUID.init().uuidString + response.suggestedFilename!
}
let tempDict = NSTemporaryDirectory()
let tempFilePath = tempDict + attachmentID
try FileManager.default.moveItem(atPath: location.path, toPath: tempFilePath)
guard let image = UIImage(contentsOfFile: tempFilePath) else { return }
guard let resizedImage = NotificationService.resizeImage(image: image, newWidth: 200) else { return } // I went all the way down to 25 and it was just a blurry image
guard let imageData = resizedImage.jpegData(compressionQuality: 1.0) else { return }
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("pkm", isDirectory: false)
.appendingPathExtension("jpg")
try imageData.write(to: fileURL)
let attachment = try UNNotificationAttachment.init(identifier: "pkm.jpg", url: fileURL, options: nil)
bestAttemptContent.attachments.append(attachment)
}
catch { return }
OperationQueue.main.addOperation({[weak self]() -> Void in
self?.contentHandler?(bestAttemptContent);
})
}).resume()
}
extension NotificationService {
static func resizeImage(image: UIImage, newWidth: CGFloat) -> UIImage? {
let scale = newWidth / image.size.width
let newHeight = image.size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
image.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
rich notification with the top and bottom cut off (no head, no bottom foot):
an example of the image before the top and bottom were cut off (has head and feet):
I couldn't get the full image but I could get the top half of it by cropping it using this answer
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
func failEarly() {
contentHandler(request.content)
}
guard let bestAttemptContent = bestAttemptContent else { failEarly(); return }
guard let attachmentUrlString = request.content.userInfo["media-url"] as? String else { failEarly(): return }
guard let url = URL(string: attachmentUrlString) else { failEarly(); return }
URLSession.shared.downloadTask(with: url, completionHandler: { [weak self](tempLocation, response, error) -> Void in
if let error = error { return }
guard let location = tempLocation else { return }
guard let response = response else { return }
do {
let lastPathComponent = response.url?.lastPathComponent ?? ""
var attachmentID = UUID.init().uuidString + lastPathComponent
if response.suggestedFilename != nil {
attachmentID = UUID.init().uuidString + response.suggestedFilename!
}
let tempDict = NSTemporaryDirectory()
let tempFilePath = tempDict + attachmentID
try FileManager.default.moveItem(atPath: location.path, toPath: tempFilePath)
guard let image = UIImage(contentsOfFile: tempFilePath) else { return }
let croppedImage = NotificationService.cropImageInHalf(image: image) // cropping occurs here
guard let imageData = croppedImage.jpegData(compressionQuality: 1.0) else { return }
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent("pkm", isDirectory: false)
.appendingPathExtension("jpg")
try imageData.write(to: fileURL)
let attachment = try UNNotificationAttachment.init(identifier: "pkm.jpg", url: fileURL, options: nil)
bestAttemptContent.attachments.append(attachment)
}
catch { return }
OperationQueue.main.addOperation({[weak self]() -> Void in
self?.contentHandler?(bestAttemptContent);
})
}).resume()
}
extension NotificationService {
static func cropImageInHalf(image: UIImage) -> UIImage {
let height = CGFloat(image.size.height * 0.5)
let rect = CGRect(x: 0, y: 0, width: image.size.width, height: height)
return cropImage(image: image, toRect: rect)
}
static func cropImage(image:UIImage, toRect rect:CGRect) -> UIImage {
let imageRef:CGImage = image.cgImage!.cropping(to: rect)!
let croppedImage:UIImage = UIImage(cgImage:imageRef)
return croppedImage
}
}
If I use the image before it is saved it is normal. But if I save it and use it later is is 90 degrees turned. How can I make sure it doesn't save sideways?
func saveEvent(_ center1: CLLocation, title2: String, imagePicked1: UIImage)
{
let data = UIImagePNGRepresentation(imagePicked1);///
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(NSUUID().uuidString+".dat")
do {
try data!.write(to: url!, options: [])
} catch let e as NSError {
print("Error! \(e)");
return
}
let image11 = CKAsset(fileURL: url!)
self.eventRecord.setObject(image11 as CKAsset, forKey: "Picture")
let publicData = CKContainer.default().publicCloudDatabase
publicData.save(self.eventRecord, completionHandler: { record, error in
if error == nil
{
print("Image saved")
}else{
print(error!)
}
})
}
If you need to save your PNG with correct rotation you will need to redraw your image if its orientation it is not .up. You can redraw it as follow:
extension UIImage {
func png(isOpaque: Bool = true) -> Data? { flattened(isOpaque: isOpaque)?.pngData() }
func flattened(isOpaque: Bool = true) -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
edit/update:
For iOS10+ tvOS10+ you can use UIGraphicsImageRenderer:
extension UIImage {
func png(isOpaque: Bool = true) -> Data? { flattened(isOpaque: isOpaque).pngData() }
func flattened(isOpaque: Bool = true) -> UIImage {
if imageOrientation == .up { return self }
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: size, format: format).image { _ in draw(at: .zero) }
}
}
Playground testing:
Usage for images without transparency:
let image = UIImage(data: try! Data(contentsOf: URL(string: "https://i.stack.imgur.com/varL9.jpg")!))!
if let data = image.png() {
let imageFromPNGData = UIImage(data: data)
}
With transparency :
if let data = image.png(isOpaque: false) {
let imageFromPNGData = UIImage(data: data)
}
Just convert the image to JPEG data instead. No need to redraw your image:
let imageData = image.jpegData(compressionQuality: 1.0)
You can use this as well to prevent it from changing of orientation.
func rotateImage(image: UIImage) -> UIImage? {
if (image.imageOrientation == UIImage.Orientation.up ) {
return image
}
UIGraphicsBeginImageContext(image.size)
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
let copy = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return copy
}
If I use the image before it is saved it is normal. But if I save it and use it later is is 90 degrees turned. How can I make sure it doesn't save sideways?
func saveEvent(_ center1: CLLocation, title2: String, imagePicked1: UIImage)
{
let data = UIImagePNGRepresentation(imagePicked1);///
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(NSUUID().uuidString+".dat")
do {
try data!.write(to: url!, options: [])
} catch let e as NSError {
print("Error! \(e)");
return
}
let image11 = CKAsset(fileURL: url!)
self.eventRecord.setObject(image11 as CKAsset, forKey: "Picture")
let publicData = CKContainer.default().publicCloudDatabase
publicData.save(self.eventRecord, completionHandler: { record, error in
if error == nil
{
print("Image saved")
}else{
print(error!)
}
})
}
If you need to save your PNG with correct rotation you will need to redraw your image if its orientation it is not .up. You can redraw it as follow:
extension UIImage {
func png(isOpaque: Bool = true) -> Data? { flattened(isOpaque: isOpaque)?.pngData() }
func flattened(isOpaque: Bool = true) -> UIImage? {
if imageOrientation == .up { return self }
UIGraphicsBeginImageContextWithOptions(size, isOpaque, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
edit/update:
For iOS10+ tvOS10+ you can use UIGraphicsImageRenderer:
extension UIImage {
func png(isOpaque: Bool = true) -> Data? { flattened(isOpaque: isOpaque).pngData() }
func flattened(isOpaque: Bool = true) -> UIImage {
if imageOrientation == .up { return self }
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: size, format: format).image { _ in draw(at: .zero) }
}
}
Playground testing:
Usage for images without transparency:
let image = UIImage(data: try! Data(contentsOf: URL(string: "https://i.stack.imgur.com/varL9.jpg")!))!
if let data = image.png() {
let imageFromPNGData = UIImage(data: data)
}
With transparency :
if let data = image.png(isOpaque: false) {
let imageFromPNGData = UIImage(data: data)
}
Just convert the image to JPEG data instead. No need to redraw your image:
let imageData = image.jpegData(compressionQuality: 1.0)
You can use this as well to prevent it from changing of orientation.
func rotateImage(image: UIImage) -> UIImage? {
if (image.imageOrientation == UIImage.Orientation.up ) {
return image
}
UIGraphicsBeginImageContext(image.size)
image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
let copy = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return copy
}