How to be able to zoom with front facing camera - ios

In my custom camera, I implemented a pinch gesture recognizer. Then i added a function for the video to be able to zoom. The camera zooms when I am on the back camera, but when i switch it to the front facing camera, I am not allowed to zoom. This is my function
var zoomFactor: CGFloat = 1.0
#objc func pinch(_ pinch: UIPinchGestureRecognizer) {
print("123")
guard let device = videoCaptureDevice else { return }
func minMaxZoom(_ factor: CGFloat) -> CGFloat { return min(max(factor, 1.0), device.activeFormat.videoMaxZoomFactor) }
func update(scale factor: CGFloat) {
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
device.videoZoomFactor = factor
} catch {
debugPrint(error)
}
}
let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
switch pinch.state {
case .began: fallthrough
case .changed: update(scale: newScaleFactor)
case .ended:
zoomFactor = minMaxZoom(newScaleFactor)
update(scale: zoomFactor)
default: break
}
}

Related

viewController.view does not showing but it appears when ViewDebugMode

I'm creating Slide Menu using PanGesture and addChild.
Although ParentViewController was able to slide to the left, MenuViewController placed just to the right of it is not displayed.
However, when you check with ViewDebug, it certainly exists and is placed in the expected location.
But why isn't it displayed on the actual screen?
👇 when view was panned
👇 when View Debug and this button tapped
class ParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesturePanned)))
let menuViewController = MenuViewController()
menuViewController.view.frame = CGRect(
x: view.frame.maxX,
y: 0,
width: view.bounds.width,
height: UIScreen.main.bounds.height)
view.addSubview(menuViewController.view)
addChild(menuViewController)
menuViewController.didMove(toParent: self)
}
#objc private func panGesturePanned(gesture: UIPanGestureRecognizer) {
guard let gestureView = gesture.view else { return }
switch gesture.state {
case .began:
print()
case .changed:
tabBarController?.view.frame.origin.x = min(max(gesture.translation(in: gestureView).x, -240), 0)
case .cancelled:
print()
case .ended:
print()
default: break
}
}
}
You are moving tabBarController, but as I understand you want to move menuViewController. Try this:
#objc private func panGesturePanned(gesture: UIPanGestureRecognizer) {
guard let gestureView = gesture.view else { return }
switch gesture.state {
case .began: break
case .changed:
tabBarController?.view.frame.origin.x = max(-menuViewController.view.bounds.width, gesture.translation(in: gestureView).x)
menuViewController.view.frame.origin.x = max(0, menuViewController.view.bounds.width - gesture.translation(in: gestureView).x)
case .cancelled: break
case .ended: break
default: break
}
}

Pan gesture (hold/drag) zoom on camera like Snapchat

I am trying to replicate Snapchat camera's zoom feature where once you have started recording you can drag your finger up or down and it will zoom in or out accordingly. I have been successful with zooming on pinch but have been stuck on zooming with the PanGestureRecognizer.
Here is the code I've tried the problem is that I do not know how to replace the sender.scale that I use for pinch gesture recognizer zooming. I'm using AVFoundation. Basically, I'm asking how I can do the hold zoom (one finger drag) like in TikTok or Snapchat properly.
let minimumZoom: CGFloat = 1.0
let maximumZoom: CGFloat = 15.0
var lastZoomFactor: CGFloat = 1.0
var latestDirection: Int = 0
#objc func panGesture(_ sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: doubleTapSwitchCamButton)
var currentDirection: Int = 0
if velocity.y > 0 || velocity.y < 0 {
let originalCapSession = captureSession
var devitce : AVCaptureDevice!
let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDuoCamera], mediaType: AVMediaType.video, position: .unspecified)
let devices = videoDeviceDiscoverySession.devices
devitce = devices.first!
guard let device = devitce else { return }
// Return zoom value between the minimum and maximum zoom values
func minMaxZoom(_ factor: CGFloat) -> CGFloat {
return min(min(max(factor, minimumZoom), maximumZoom), device.activeFormat.videoMaxZoomFactor)
}
func update(scale factor: CGFloat) {
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
device.videoZoomFactor = factor
} catch {
print("\(error.localizedDescription)")
}
}
//These 2 lines below are the problematic ones, pinch zoom uses this one below, and the newScaleFactor below that is a testing one that did not work.
let newScaleFactor = minMaxZoom(sender.scale * lastZoomFactor)
//let newScaleFactor = CGFloat(exactly: number + lastZoomFactor)
switch sender.state {
case .began: fallthrough
case .changed: update(scale: newScaleFactor!)
case .ended:
lastZoomFactor = minMaxZoom(newScaleFactor!)
update(scale: lastZoomFactor)
default: break
}
} else {
}
latestDirection = currentDirection
}
You can use the gesture recogniser's translation property, to calculate the displacement in points, and normalise this displacement as a zoom factor.
Fitting this into your code, you could try:
... somewhere in your view setup code, i.e. viewDidLoad....
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGesture))
button.addGestureRecognizer(panGestureRecognizer)
private var initialZoom: CGFloat = 1.0
#objc func panGesture(_ sender: UIPanGestureRecognizer) {
// note that 'view' here is the overall video preview
let velocity = sender.velocity(in: view)
if velocity.y > 0 || velocity.y < 0 {
let originalCapSession = captureSession
var devitce : AVCaptureDevice!
let videoDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInDuoCamera], mediaType: AVMediaType.video, position: .unspecified)
let devices = videoDeviceDiscoverySession.devices
devitce = devices.first!
guard let device = devitce else { return }
let minimumZoomFactor: CGFloat = 1.0
let maximumZoomFactor: CGFloat = min(device.activeFormat.videoMaxZoomFactor, 10.0) // artificially set a max useable zoom of 10x
// clamp a zoom factor between minimumZoom and maximumZoom
func clampZoomFactor(_ factor: CGFloat) -> CGFloat {
return min(max(factor, minimumZoomFactor), maximumZoomFactor)
}
func update(scale factor: CGFloat) {
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
device.videoZoomFactor = factor
} catch {
print("\(error.localizedDescription)")
}
}
switch sender.state {
case .began:
initialZoom = device.videoZoomFactor
startRecording() /// call to start recording your video
case .changed:
// distance in points for the full zoom range (e.g. min to max), could be view.frame.height
let fullRangeDistancePoints: CGFloat = 300.0
// extract current distance travelled, from gesture start
let currentYTranslation: CGFloat = sender.translation(in: view).y
// calculate a normalized zoom factor between [-1,1], where up is positive (ie zooming in)
let normalizedZoomFactor = -1 * max(-1,min(1,currentYTranslation / fullRangeDistancePoints))
// calculate effective zoom scale to use
let newZoomFactor = clampZoomFactor(initialZoom + normalizedZoomFactor * (maximumZoomFactor - minimumZoomFactor))
// update device's zoom factor'
update(scale: newZoomFactor)
case .ended, .cancelled:
stopRecording() /// call to start recording your video
break
default:
break
}
}
}

Using UIPanGestureRecognizer to swipe down UIView

I have an IBAction for a UIPanGestureRecognizer in my UIView, I am able to handle the gesture and recognise the state changes from began, cancelled and ended, as well as respond to those changes.
However when using the sender.location to handle the swipe down, the UIView actually moves up once the gesture has began, and then continues to move down. The experience is jarring and I am not sure what I am doing wrong. Does anybody have any ideas ?
func update(_ translation: CGPoint, origin: CGPoint) {
let offSetY = translation.y
cardView.frame.origin.y = offSetY
let multiplier = 1 - (translation.y / 2000)
self.view.alpha = multiplier
}
func cancel(_ origin: CGPoint) {
let animator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 0.6) {
self.visualEffectView.alpha = 1
self.cardView.alpha = 1.0
self.view.alpha = 1.0
self.cardView.center = origin
}
animator.startAnimation()
}
func finish() {
let animator = UIViewPropertyAnimator(duration: 0.9, dampingRatio: 0.9) {
self.visualEffectView.effect = nil
self.dismiss(animated: true, completion: nil)
}
animator.startAnimation()
}
#IBAction func panGestureAction(_ sender: UIPanGestureRecognizer) {
self.view.backgroundColor = .white
let originalCardPosition = cardView.center
//let cardOriginY = cardView.frame.origin.y
let translation = sender.translation(in: self.cardView)
let origin = sender.location(in: self.cardView)
switch sender.state {
case .changed:
if translation.y > 0 { update(translation, origin: origin) }
case .ended:
let translation = sender.translation(in: self.cardView)
if translation.y > 100 {
finish()
} else {
cardView.alpha = 1.0
cancel(originalCardPosition)
}
case .cancelled:
cancel(originalCardPosition)
default: break
}
}
The problem is that you set the origin.y of the cardView directly to the value of translation.y. You need to add translation.y to the view's original y value determined when the gesture begins.
Add a property to the class:
var originalY: CGFloat = 0
Then in the .began state of the gesture, set it:
originalY = cardView.frame.origin.y
Then in your update method you set the origin:
cardView.frame.origin.y = originalY + offSetY

How to rotate to another face of 3d cube when swiped right

I created a 3d cube using SceneKit and added a gesture to recognize swiping right. But, I do not know how to rotate the cube to another face when swiped right. I do not want the cube to rotate continuously when swiped, only moving to another face that is on the right side. Sorry for any confusions.
Here is my code where I created cube:
import UIKit
import SceneKit
class ViewController: UIViewController {
// UI
#IBOutlet weak var geometryLabel: UILabel!
#IBOutlet weak var sceneView: SCNView!
// Geometry
var geometryNode: SCNNode = SCNNode()
// Gestures
var currentAngle: Float = 0.0
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// MARK: Scene
sceneSetup()
}
func sceneSetup() {
let scene = SCNScene()
sceneView.scene = scene
sceneView.allowsCameraControl = false
sceneView.autoenablesDefaultLighting = true
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 25)
scene.rootNode.addChildNode(cameraNode)
var geometries = [
SCNBox(width: 8.0, height: 8.0, length: 8.0, chamferRadius: 1.0)
]
var materials = [SCNMaterial]()
for i in 1...6 {
let material = SCNMaterial()
if i == 1 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 2 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 3 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 4 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 5 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
if i == 6 { material.diffuse.contents = UIColor(red:0.02, green:0.98, blue:0.98, alpha:1.0) }
materials.append(material)
}
for i in 0..<geometries.count {
let geometry = geometries[i]
let node = SCNNode(geometry: geometry)
node.geometry?.materials = materials
scene.rootNode.addChildNode(node)
}
let swipeRight: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(respondToSwipeGesture(gesture:)))
swipeRight.direction = .right
sceneView.addGestureRecognizer(swipeRight)
}
Function for swipe right gesture when recognize:
func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped Right")
default:
break
}
}
}
You need to use a SCNAction.rotate getting the current w value of the node rotation and adding the amount of angle you want, using this 2 extensions for converting to Radians and from Radians, replace my self.shipNode by your cube node, and change the axis if you need to, taking in account that the first 0 in the SCNVector4Make sentence is X axis
This is an example
extension Int {
var degreesToRadians: Double { return Double(self) * .pi / 180 }
var radiansToDegrees: Double { return Double(self) * 180 / .pi }
}
extension FloatingPoint {
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
#objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
//here we rotate the ship node 30 grades in the Y axis each time
self.shipNode?.runAction(SCNAction.rotate(toAxisAngle: SCNVector4Make(0, 1, 0, (self.shipNode?.rotation.w)! + Float(30.degreesToRadians)), duration: 1))
}
To rotate down you only have to change the axis of the rotation
you can use this
- (void) handleTap:(UIGestureRecognizer*)gestureRecognize
{
[self.ship runAction:[SCNAction rotateToAxisAngle:SCNVector4Make(1, 0, 0, [self getRadiansFromAngle:[self getAngleFromRadian:self.ship.rotation.w] + 30]) duration:1]];
}
UPDATED
Using your code
You need to use SCNAction.rotate(by: #Float, around: #SCNVector3, duration: #duration) method instead, below is the full code for your requeriments
#objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped Right")
geometryNode.runAction(SCNAction.rotate(by: CGFloat(90.degreesToRadians), around: SCNVector3Make(0, 1, 0), duration: 0.2))
case UISwipeGestureRecognizerDirection.left:
print("Swiped Left")
geometryNode.runAction(SCNAction.rotate(by: CGFloat(-90.degreesToRadians), around: SCNVector3Make(0, 1, 0), duration: 0.2))
case UISwipeGestureRecognizerDirection.down:
print("Swiped Down")
geometryNode.runAction(SCNAction.rotate(by: CGFloat(90.degreesToRadians), around: SCNVector3Make(1, 0, 0), duration: 0.2))
default:
break
}
}
}

Add gesture pinch zoom to camera preview layer, iOS, Swift

I am trying to add a pinch zoom to camera preview layer that is added programmatically. I have this code below as a function but that is all I have, from tips. I don't have any other code relating to it. I cannot find any more info around they all seem to focus on still images.
override func viewDidLoad() {
super.viewDidLoad()
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action:#selector(pinch(_:)))
pinchRecognizer.delegate = self
self.cameraPreviewlayer.addGestureRecognizer(pinchRecognizer)
}
I get an error on this line
self.cameraPreviewlayer.addGestureRecognizer(pinchRecognizer)
it says cameraPreviewLayer does not have add geture.
Here is the function.
#objc func pinch(_ pinch: UIPinchGestureRecognizer) {
let device = videoDeviceInput.device
// Return zoom value between the minimum and maximum zoom values
func minMaxZoom(_ factor: CGFloat) -> CGFloat {
return min(min(max(factor, minimumZoom), maximumZoom), device.activeFormat.videoMaxZoomFactor)
}
func update(scale factor: CGFloat) {
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
device.videoZoomFactor = factor
} catch {
print("\(error.localizedDescription)")
}
}
let newScaleFactor = minMaxZoom(pinch.scale * lastZoomFactor)
switch pinch.state {
case .began: fallthrough
case .changed: update(scale: newScaleFactor)
case .ended:
lastZoomFactor = minMaxZoom(newScaleFactor)
update(scale: lastZoomFactor)
default: break
}
}
I was able to fix this.
All I had to do was move the
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action:#selector(pinch(_:)))
pinchRecognizer.delegate = self
self.cameraPreviewlayer.addGestureRecognizer(pinchRecognizer)
from view did load to where I set up the camera preview layer.
cameraPreviewlayer = AVCaptureVideoPreviewLayer(session: captureSession)
cameraPreviewlayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
cameraPreviewlayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
cameraPreviewlayer?.frame = self.view.frame
// scanArea.setRegionOfInterestWithProposedRegionOfInterest(regionOfInterest)
self.view.layer.insertSublayer(cameraPreviewlayer!, at: 0)
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action:#selector(pinch(_:)))
pinchRecognizer.delegate = self
self.view.addGestureRecognizer(pinchRecognizer)

Resources