How to take a screenshot from WKWebView with embedded video? - ios

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)

Related

SwiftUI - How to draw on top of image then save drawing and image together as one

I have an app that takes a snapshot of a view, then the snapshot is used as a image that the user is able to draw on top of. I followed this tutorial to get the drawing functionality.
Everything works great however if I try to take a snapshot of the image with the drawing on top it saves it to the photo album as just the image (no drawing). What I want is to use a button to save a snapshot of the image with the newly added drawing. As seen in the code, I've tried using a ZStack in the view I am snapshotting, however, it did not work out for me.
I think I need to update something in the updateUIView but I am not sure what. The minimal reproducible example below will build with the problem. I appreciate any ideas.
Thank you!
Content View
import SwiftUI
struct ContentView: View {
#State private var snapshotImage: UIImage? = nil
var imageView: some View {
Image("Test")
//EmptyView() <- Uncomment and comment out the line above to just use an empty View as an image
}
var imageDrawingView: some View {
ZStack {
if let image = snapshotImage {
Image(uiImage: image)
CanvasViewWrapper()
}
}
}
var body: some View {
if snapshotImage != nil {
VStack {
ZStack {
imageDrawingView
}
Button("Save Image") {
let imageWithDrawing = imageDrawingView.snapshot()
UIImageWriteToSavedPhotosAlbum(imageWithDrawing, nil, nil, nil) // Save to photos
}
}
} else {
VStack {
imageView
Button("Take ScreenShot") {
self.snapshotImage = imageView.snapshot()
}
}
}
}
}
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
CanvasView
import Foundation
import UIKit
class CanvasView: UIView {
override func draw(_ rect: CGRect){
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
paths.forEach { path in
switch(path.type) {
case .move:
context.move(to: path.point)
break
case .line:
context.addLine(to: path.point)
break
}
}
context.setLineWidth(10)
context.setLineCap(.round)
context.strokePath()
}
#Published var paths = [Path]()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else { return }
paths.append(Path(type: .move, point: point))
setNeedsDisplay()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else { return }
paths.append(Path(type: .line, point: point))
setNeedsDisplay()
}
}
struct Path {
let type: PathType
let point: CGPoint
init(type: PathType, point: CGPoint) {
self.type = type
self.point = point
}
enum PathType {
case move
case line
}
}
CanvasViewWrapper
import SwiftUI
struct CanvasViewWrapper : UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
return CanvasView()
}
func updateUIView(_ uiView: UIView, context: Context) {
uiView.backgroundColor = .clear
}
}
App
import SwiftUI
#main
struct StackMinimalApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Take Snap of UIImageView or UIView with this extension
extension UIView {
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
func asImages() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
Use
let image = someView.asImage()
it will return you the UIView with drawing in the form of image Then Save That image in your library.
To Save
func saveImage(image: UIImage) -> Bool {
guard let data = UIImageJPEGRepresentation(image, 1) ?? UIImagePNGRepresentation(image) else {
return false
}
guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
return false
}
do {
try data.write(to: directory.appendingPathComponent("fileName.png")!)
return true
} catch {
print(error.localizedDescription)
return false
}
}
use
let success = saveImage(image: UIImage(named: "image.png")!)
Get The Image
func getSavedImage(named: String) -> UIImage? {
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named).path)
}
return nil
}
use
if let image = getSavedImage(named: "fileName") {
// do something with image
}

Image (UIImage) orientation changes when loading in UIIMageView or switching cameras

I'm facing this orientation problem in 2 scenarios in my app.
View Controller loads > TensorFlow initializes > UIImage is set to a UIImageView
PreviewView uses the rear camera > Switch to front (> Switch to rear)
In each cases the orientation of the UIImage goes from up to landscape right. I have tried many solutions from here but it doesn't seem to fix the problem. I'm still getting the images in wrong orientation in both cases.
Here's some code:
Getting image from CMSampleBuffer
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
...
let points = modelDataHandler.runModel(onFrame: imagePixelBuffer)
...
}
func runModel(onFrame pixelBuffer: CVPixelBuffer) -> [Corners : CGFloat] {
...
guard let thumbnailPixelBuffer = pixelBuffer.uiImageConverted() else {
return [:]
}
...
}
extension CVPixelBuffer {
func uiImageConverted() -> UIImage? {
let ciImage = CIImage(cvPixelBuffer: self)
let context = CIContext(options: nil)
guard let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: CGSize(width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self)))) else { return nil }
let uiImage = UIImage(cgImage: cgImage)
return uiImage
}
}
Setting UIImage to UIImageView
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tfQueue.async { [self] in
modelDataHandler = ModelDataHandler()
guard modelDataHandler != nil else {
fatalError()
}
DispatchQueue.main.async {
spinnerStack.isHidden = true
spinner.stopAnimating()
runButton.isEnabled = true
runButton.tintColor = .label
imageView.image = imageInfo?.originalImage.fixedOrientation()
imageWidth = imageInfo?.originalImage.size.width ?? 0
imageHeight = imageInfo?.originalImage.size.height ?? 0
}
}
}
extension UIImage {
func fixedOrientation() -> UIImage? {
guard let cImage = cgImage else { return nil }
let currentOrientation = UIDevice.current.orientation
let temp = CIImage(cgImage: cImage)
var ciImage = temp
switch currentOrientation {
case .unknown, .portrait:
ciImage = temp.oriented(forExifOrientation: 6)
case .landscapeRight:
ciImage = temp.oriented(forExifOrientation: 3)
case .landscapeLeft:
ciImage = temp.oriented(forExifOrientation: 1)
default:
break
}
guard let cgImage = CIContext(options: nil).createCGImage(ciImage, from: ciImage.extent) else { return nil }
let fixedImage = UIImage(cgImage: cgImage)
return fixedImage
}
}
How do I fix the orientation problem?

screenShot image with Avplayer ios 10* is Black image

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

UIImageView - Front camera Image rotation [duplicate]

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
}

Swift PNG Image being saved with incorrect orientation

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
}

Resources