Can't generate PNG file from UIView snapshot - swift

I want to generate a .png file from UIView snapshot but it's always nil. Could you give me a hint what's wrong with my code? I'm running Catalina 10.15.2 with Xcode 11.3 and iOS 13.3.
Here's my code's snippet:
extension UIView {
fileprivate func snapShot(view: UIView) -> UIView {
let snapshot = view.snapshotView(afterScreenUpdates: false)
self.addSubview(snapshot!)
return snapshot!
}
}
class ViewController: UIViewController {
var image: UIImage?
var imageView: UIImageView?
#IBAction func take(_ sender: UIButton) {
let oneThird = CGRect(x: 0,
y: 0,
width: view.frame.size.width / 3,
height: view.frame.size.height / 3)
let snap = view.snapShot(view: self.view)
snap.frame = oneThird
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
let png = snap.largeContentImage?.pngData()
if let pngData = png {
self.image = UIImage(data: pngData)
}
self.imageView = UIImageView(image: self.image)
}
print(self.view.subviews.indices) // 0..<5
print(imageView?.image as Any) // NIL
}
}
Any help appreciated.

Try the following extension:
extension UIView {
func getImageFromCurrentContext(bounds: CGRect? = nil) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds?.size ?? self.bounds.size, false, 0.0)
self.drawHierarchy(in: bounds ?? self.bounds, afterScreenUpdates: true)
guard let currentImage = UIGraphicsGetImageFromCurrentImageContext() else {
return nil
}
UIGraphicsEndImageContext()
return currentImage
}
}
Pass the bounds if you would like 1/3 of the view or don't provide a bounds to get the image of the entire view.
Tested with sample code:
import UIKit
class ViewController: UIViewController {
#IBOutlet private(set) var testView: UIView!
#IBOutlet private(set) var testImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.testImageView.image = self.testView.getImageFromCurrentContext()
}
}
}
I made UIView on the storyboard have a sample label inside it and changed the background color just to verify it is in fact creating the image. Then added an UIImageView under that. Connected the UIView to testView and the UIImageView to testImageView.

Related

Update image in UIImageView

I try to make UIKit element with replaceable image in it, that I can use like swiftUI element.
I stuck at the moment, when image in UIImageView should be refreshed with imageInBlackBox from ObservableObject. I try'd to set new imageInBlackBox from updateUIView and from imagePickerController after image has been selected. But both of this methods don't update UIImageView, it works only for swiftUI Image element, that I use for test.
How I should make imageView.image be refreshed after imageInBlackBox change?
//Data model
struct imageAdderDataHolder: Identifiable, Hashable {
var id: UUID = UUID()
var isShowingImagePicker:Bool = false
var imageInBlackBox:UIImage = UIImage(systemName: "photo")! // image for replace
var height: CGFloat = 160
var width: CGFloat = 160
}
//Data storage
class imageAdderData: ObservableObject{
init() {}
static let shared = imageAdderData()
#Published var img1: imageAdderDataHolder = imageAdderDataHolder()
}
struct simpleadder: UIViewRepresentable{
#ObservedObject var imageData: imageAdderData = .shared
let mainView: UIView = UIView()
var imageView: UIImageView = UIImageView()
func makeUIView(context: Context) -> UIView {
imageView.image = imageData.img1.imageInBlackBox
imageView.frame.size.width = imageData.img1.width
imageView.frame.size.height = imageData.img1.height
imageView.contentMode = .scaleAspectFit
mainView.addSubview(imageView)
return mainView
}
func updateUIView(_ uiView: UIView, context: Context) {
imageView.image = imageData.img1.imageInBlackBox // try to replace image
}
}
// swiftui view for test
struct photoadder: View {
#ObservedObject var imageData: imageAdderData = .shared
var body: some View {
VStack{
HStack{
simpleadder()
.frame(width: imageData.img1.width, height: imageData.img1.height)
.border(Color.black, width:1)
.sheet(isPresented: $imageData.img1.isShowingImagePicker, content: {
imagePickerUIView(isPresented: $imageData.img1.isShowingImagePicker)
})
Image(uiImage: imageData.img1.imageInBlackBox) // working element for test
.resizable()
.aspectRatio(contentMode: ContentMode.fit)
.frame(width: imageData.img1.width, height: imageData.img1.height)
.border(Color.black, width: 1)
}
Button("change image") {
imageData.img1.isShowingImagePicker = true
}
}
}
}
struct imagePickerUIView: UIViewControllerRepresentable {
#ObservedObject var imageData: imageAdderData = .shared
#Binding var isPresented: Bool
func makeUIViewController(context:
UIViewControllerRepresentableContext<imagePickerUIView>) ->
UIViewController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
return controller
}
func makeCoordinator() -> imagePickerUIView.Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
let parent: imagePickerUIView
init(parent: imagePickerUIView) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info:
[UIImagePickerController.InfoKey : Any]) {
if let selectedImageFromPicker = info[.originalImage] as? UIImage {
// try to replace image
parent.imageData.img1.imageInBlackBox = selectedImageFromPicker
}
self.parent.isPresented = false
}
}
func updateUIViewController(_ uiViewController:
imagePickerUIView.UIViewControllerType, context:
UIViewControllerRepresentableContext<imagePickerUIView>) {
}
}
Here is a solution - in representable all views should be created inside makeUIView, because struct can be (and usually does) recreated on parent update (so internal instances will be recreated as well, while made UIKit views life-cycle persisted).
Tested with Xcode 12.4 / iOS 14.4.
Fixed code:
struct simpleadder: UIViewRepresentable{
#ObservedObject var imageData: imageAdderData = .shared
func makeUIView(context: Context) -> UIView {
let mainView: UIView = UIView() // << create here !!
let imageView: UIImageView = UIImageView() // << create here !!
imageView.image = imageData.img1.imageInBlackBox
imageView.frame.size.width = imageData.img1.width
imageView.frame.size.height = imageData.img1.height
imageView.contentMode = .scaleAspectFit
mainView.addSubview(imageView)
return mainView
}
func updateUIView(_ uiView: UIView, context: Context) {
// find needed view in run-time
if let imageView = uiView.subviews.first as? UIImageView {
imageView.image = imageData.img1.imageInBlackBox
}
}
}

How can I add background image into canvasView(made by PencilKit) in swift?

I make drawing book apps. And I made pencilKit in swift So as an image that I add in this question.
So I can draw several things on the canvasView. But!!! I really wanna take pictures in the photo roll so I wanna use that as the background of my canvasView.
But if canvasView.addSubview(ImageView) is in the code then my gray ImageView is bo on the top of my canvasView so if I draw things, then that things are drawn under the canvasView.
If I use imageView.sendSubviewToBack(canvasView) then nothing happen. I really did know how to insert my background image between RootView and canvasView.
Here are my image and code.
//
// ViewController.swift
// webtoon
//
// Created by 신효근 on 2020/07/13.
// Copyright © 2020 신효근. All rights reserved.
//
import UIKit
import PencilKit
class ViewController: UIViewController, PKCanvasViewDelegate, PKToolPickerObserver {
#IBOutlet weak var canvasView: PKCanvasView!
let canvasWidth : CGFloat = 768
let canvasOverscrollHight : CGFloat = 500
var imageView = UIImageView()
var drawing = PKDrawing()
let picker = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
canvasView.delegate = self
canvasView.drawing = drawing
canvasView.alwaysBounceVertical = true
canvasView.allowsFingerDrawing = true
if let window = parent?.view.window,
let toolPicker = PKToolPicker.shared(for: window){
toolPicker.setVisible(true, forFirstResponder:canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
self.imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 500)
self.imageView.backgroundColor = .gray
imageView.sendSubviewToBack(canvasView)
}
// override func viewDidLayoutSubviews() {
// let canvasScale = canvasView.bounds.width/canvasWidth
// canvasView.minimumZoomScale = canvasScale
// canvasView.maximumZoomScale = canvasScale
// canvasView.zoomScale = canvasScale
// }
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
#IBAction func saveDrawingToCameraRoll(_ sender: Any) {
}
#IBAction func addButtonPressed(_ sender: Any) {
self.present(self.picker, animated: true)
}
}
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var newImage: UIImage? = nil
print("Start")
print(info)
print("end")
if let possibleImage = info["UIImagePickerControllerEditedImage"] as? UIImage { // 수정된 이미지가 있을 경우
newImage = possibleImage
} else if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage { // 오리지널 이미지가 있을 경우
newImage = possibleImage
}
imageView.image = newImage // 받아온 이미지를 이미지 뷰에 넣어준다.
picker.dismiss(animated: true) // 그리고 picker를 닫아준다.
}
}
Here is my code it works for me
canvasView.isOpaque = false
canvasView.backgroundColor = UIColor.clear
canvasView.delegate = context.coordinator
canvasView.becomeFirstResponder()
let imageView = UIImageView(image: canvasImage)
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
let contentView = canvasView.subviews[0]
contentView.addSubview(imageView)
contentView.sendSubviewToBack(imageView)

How to implement custom camera preview in iOS?

I am trying to implement Custom camera effect like:- Image
I Thought that this is achieve like this way
This type of functionality already implemented in one app which is available in App Store. here is the link enter link description here
I want to copy this app's camera functionality.
I have already implemented something like this.
I am using below code for achieved above functionality.
Into ViewController.swift class.
import UIKit
import AVFoundation
#available(iOS 10.0, *)
class ViewController: UIViewController
{
#IBOutlet weak var vc: UIView!
#IBOutlet weak var img: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
setupCamera()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
session.startRunning()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
session.stopRunning()
}
#IBOutlet fileprivate var previewView: PreviewView! {
didSet {
previewView.videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
previewView.layer.cornerRadius = previewView.layer.frame.size.width/2
previewView.clipsToBounds = true
}
}
#IBOutlet fileprivate var imageView: UIImageView! {
didSet {
imageView.layer.cornerRadius = imageView.layer.frame.size.width/2
imageView.clipsToBounds = true
}
}
fileprivate let session: AVCaptureSession = {
let session = AVCaptureSession()
session.sessionPreset = AVCaptureSessionPresetPhoto
return session
}()
fileprivate let output = AVCaptureStillImageOutput()
}
#available(iOS 10.0, *)
extension ViewController {
func setupCamera() {
let backCamera = AVCaptureDevice.defaultDevice(withMediaType:
AVMediaTypeVideo)
guard let input = try? AVCaptureDeviceInput(device: backCamera) else {
fatalError("back camera not functional.") }
session.addInput(input)
session.addOutput(output)
previewView.session = session
}
}
// MARK: - #IBActions
#available(iOS 10.0, *)
private extension ViewController {
#IBAction func capturePhoto() {
if let videoConnection = output.connection(withMediaType: AVMediaTypeVideo) {
output.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (CMSampleBuffer, Error) in
if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(CMSampleBuffer) {
if let cameraImage = UIImage(data: imageData) {
self.imageView.image = cameraImage
UIImageWriteToSavedPhotosAlbum(cameraImage, nil, nil, nil)
}
}
})
}
}
}
Also Create Preview Class and this class into UIView from storyboard file.
From above code I have achived this image.
I need to add any shape of image layer as a frame into UIView. ButI have no idea how to achieved this type of functionality.
So, Basically my task is, how to add any shape of image layer into UIView and after capture image how to save image with image layer, like Final Image clue image

IBAction stops working when I connect IBOutlet

I have an IBAction wired up to a button in my storyboard that plays a sound. This works just fine. However, when I wire up an IBOutlet to the same button, the code for the outlet takes over and the IBAction stops working. In this case, the IBOutlet serves the purpose of making the button pulse with animation. Here is my code:
import UIKit
import Firebase
import AVFoundation
class Page1: UIViewController, AVAudioPlayerDelegate, UIAlertViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var testpulse: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "ahh", withExtension: "mp3")
do {
ahh = try AVAudioPlayer(contentsOf: url!)
ahh.prepareToPlay()
}
catch let error as NSError {
print(error.debugDescription)
}
testpulse.isUserInteractionEnabled = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(Page1.addPulse))
tapGestureRecognizer.numberOfTapsRequired = 1
testpulse.addGestureRecognizer(tapGestureRecognizer)
}
#objc func addPulse(){
let pulse = Pulsing(numberOfPulses: 1, radius: 110, position: testpulse.center)
pulse.animationDuration = 0.8
pulse.backgroundColor = UIColor.purple.cgColor
self.view.layer.insertSublayer(pulse, below: testpulse.layer)
}
#IBAction func ahh(_ sender: Any) {
ahh.play()
}
}
And for my Swift animation file I have this:
import UIKit
class Pulsing: CALayer {
var animationGroup = CAAnimationGroup()
var initialPulseScale:Float = 0
var nextPulseAfter:TimeInterval = 0
var animationDuration:TimeInterval = 1.5
var radius:CGFloat = 200
var numberOfPulses:Float = Float.infinity
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
init (numberOfPulses:Float = Float.infinity, radius:CGFloat, position:CGPoint) {
super.init()
self.backgroundColor = UIColor.black.cgColor
self.contentsScale = UIScreen.main.scale
self.opacity = 0
self.radius = radius
self.numberOfPulses = numberOfPulses
self.position = position
self.bounds = CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2)
//self.cornerRadius = radius
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.setupAnimationGroup()
DispatchQueue.main.async {
self.add(self.animationGroup, forKey: "pulse")
}
}
setupAnimationGroup()
self.add(animationGroup, forKey: "pulse")
}
func createScaleAnimation () -> CABasicAnimation {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale.xy")
scaleAnimation.fromValue = NSNumber(value: initialPulseScale)
scaleAnimation.toValue = NSNumber(value: 1)
scaleAnimation.duration = animationDuration
return scaleAnimation
}
func createOpacityAnimation() -> CAKeyframeAnimation? {
let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity")
opacityAnimation.duration = animationDuration
opacityAnimation.values = [0.4, 0.8, 0]
opacityAnimation.keyTimes = [0, 0.2, 1]
return opacityAnimation
}
func setupAnimationGroup() {
self.animationGroup = CAAnimationGroup()
self.animationGroup.duration = animationDuration + nextPulseAfter
self.animationGroup.repeatCount = numberOfPulses
let defaultCurve = CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)
self.animationGroup.timingFunction = defaultCurve
self.animationGroup.animations = [createScaleAnimation(), createOpacityAnimation()!]
}
}
When I connect the IBAction to my button, the sound works. However when I connect the IBOutlet, the animation works but the IBAction doesn't. What could be causing this? It's still connected in the storyboard.
import UIKit
import Firebase
import AVFoundation
class Page1: UIViewController, AVAudioPlayerDelegate, UIAlertViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var testpulse: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "ahh", withExtension: "mp3")
do {
ahh = try AVAudioPlayer(contentsOf: url!)
ahh.prepareToPlay()
}
catch let error as NSError {
print(error.debugDescription)
}
testpulse.isUserInteractionEnabled = true
}
#objc func addPulse(){
let pulse = Pulsing(numberOfPulses: 1, radius: 110, position: testpulse.center)
pulse.animationDuration = 0.8
pulse.backgroundColor = UIColor.purple.cgColor
self.view.layer.insertSublayer(pulse, below: testpulse.layer)
}
#IBAction func ahh(_ sender: Any) {
ahh.play()
addPulse()
}
}

iCarousel shows images only after resize

I have imported the iCarousel provided by nicklockwood into a macOs app.
https://github.com/nicklockwood/iCarousel
The app is written in Swift.
After managing to get everything working (importing .m and .h files, adding Bridging Header) there is one minor thing.
Once the app is started and the NSViewController is activated I see a blank ViewController. Only if I start resizing the view I see the iCarousel with all loaded picture.
My code looks like the following:
class CarouselViewController: NSViewController, iCarouselDataSource, iCarouselDelegate {
var images = [NSImage]()
#IBOutlet var carousel: iCarousel!
override func awakeFromNib() {
super.awakeFromNib()
loadImages()
}
override func viewDidLoad() {
super.viewDidLoad()
self.carousel.type = iCarouselType.coverFlow
self.carousel.dataSource = self
self.carousel.delegate = self
carousel.reloadData()
}
func loadImages() {
let filePath = "/pics/"
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.picturesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
let path = paths?.appending(filePath)
//
let fileManager = FileManager.default
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path!)!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("jpg") || element.hasSuffix("png") {
if let image = NSImage(contentsOfFile: path! + element) {
print("File: \(path!)\(element)")
self.images.append(image)
}
}
}
}
func numberOfItems(in carousel: iCarousel) -> Int {
return images.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: NSView?) -> NSView {
let imageView = NSImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
imageView.image = self.images[index]
imageView.imageScaling = .scaleAxesIndependently
return imageView
}
func carouselItemWidth(_ carousel: iCarousel) -> CGFloat {
return 200.0
}
}
How can I manage to display my iCarousel without resizing first?
Thank you
I guess I've found one solution:
The drawing of the actual images in the carousel is triggered by layOutItemViews or layoutSubviews. One function that is public accessible is setType which allows to set the carousel type.
If I set the type after assigning dataSource and after loading the data, the images will be displayed fine.
So the most simple answer is to change viewDidLayout like the following:
override func viewDidLoad() {
super.viewDidLoad()
self.carousel.dataSource = self
self.carousel.delegate = self
carousel.reloadData()
// Triggers layOutItemView and thus will render all images.
self.carousel.type = iCarouselType.coverFlow
}
Now it is also possible to assign datasource via storyboard and the Connection inspector.