Reputation: 1284
I am developing the front facing camera app in iPad2 by using the UIImagePickerController
.
When I capture the image it's shows as flipped from left to right.
How do I correct this?
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
UIImagePickerController *imgPkr = [[UIImagePickerController alloc] init];
imgPkr.delegate = self;
imgPkr.sourceType = UIImagePickerControllerSourceTypeCamera;
imgPkr.cameraDevice=UIImagePickerControllerCameraDeviceFront;
UIImageView *anImageView=[[UIImageView alloc] initWithImage:[UIImage imageNamed:[NSString stringWithFormat:@"select%d.png",val]]];
anImageView.frame = CGRectMake(0, 0, anImageView.image.size.width, anImageView.image.size.height);
imgPkr.cameraOverlayView = anImageView;
[theApp.TabViewControllerObject presentModalViewController:imgPkr animated:YES];
[imgPkr release];
}
Upvotes: 67
Views: 35304
Reputation: 21
Here are full example code of working view controller, which can make back-front photos and mirror front-photos.
import SwiftUI
import PhotosUI
import UIKit
import AVFoundation
struct ImagePicker: UIViewControllerRepresentable {
enum PickerType {
case image(UIImage)
}
private let callback: (PickerType?) -> Void
init(callback: @escaping (PickerType?) -> Void) {
self.callback = callback
}
func makeCoordinator() -> Coordinator {
return Coordinator(controller: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIViewController {
let picker = CameraCaptureViewController()
picker.cameraDelegate = context.coordinator
picker.sourceType = .camera
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIViewController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
// MARK: - Coordinator
final class Coordinator: NSObject, UINavigationControllerDelegate {
var controller: ImagePicker
var lastKnownPosition = AVCaptureDevice.Position.unspecified
init(controller: ImagePicker) {
self.controller = controller
}
}
}
// MARK: - UIImagePickerControllerDelegate
extension ImagePicker.Coordinator: UIImagePickerControllerDelegate, CameraCaptureViewControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard var userPickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
if lastKnownPosition == .front, let cgImage = userPickedImage.cgImage {
userPickedImage = UIImage(cgImage: cgImage, scale: userPickedImage.scale, orientation: .leftMirrored)
}
controller.callback(.image(userPickedImage))
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
controller.callback(nil)
}
func cameraPositionDidChanged(newPosition: AVCaptureDevice.Position) {
lastKnownPosition = newPosition
}
}
protocol CameraCaptureViewControllerDelegate {
func cameraPositionDidChanged(newPosition: AVCaptureDevice.Position)
}
class CameraCaptureViewController: UIImagePickerController {
var lastKnownPosition = AVCaptureDevice.Position.unspecified {
didSet {
cameraDelegate?.cameraPositionDidChanged(newPosition: lastKnownPosition)
}
}
var cameraDelegate: CameraCaptureViewControllerDelegate?
weak private var session: AVCaptureSession?
private var context = 0
deinit {
session?.removeObserver(self, forKeyPath: "inputs")
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil, queue: nil) { [weak self] (notification) in
if self?.lastKnownPosition == .front {
self?.changePhotoOrientation()
}
}
notificationCenter.addObserver(forName: NSNotification.Name(rawValue: "AVCaptureSessionDidStartRunningNotification"), object: nil, queue: nil) { [weak self] notification in
self?.handleCaptureSessionDidStartRunning(notification: notification)
}
notificationCenter.addObserver(forName: NSNotification.Name(rawValue: "AVCaptureSessionDidStopRunningNotification"), object: nil, queue: nil) { [weak self] notification in
self?.handleCaptureSessionDidStopRunning(notification: notification)
}
}
// Changing image orientation for ImagePicker preview
func changePhotoOrientation() {
var subviews: [UIView] = [view]
while !subviews.isEmpty {
let subview = subviews.removeFirst()
subviews += subview.subviews
if (subview.isKind(of: UIImageView.self)) {
subview.transform = cameraViewTransform.scaledBy(x: -1, y: 1)
}
}
}
func handleCaptureSessionDidStartRunning(notification: Notification){
guard let session = notification.object as? AVCaptureSession else { return }
self.session = session
if let input = session.inputs.first as? AVCaptureDeviceInput {
lastKnownPosition = input.device.position
}
session.addObserver(self, forKeyPath: "inputs", options: [ .old, .new ], context: &context)
}
func handleCaptureSessionDidStopRunning(notification: Notification) {
guard let session = notification.object as? AVCaptureSession else { return }
session.removeObserver(self, forKeyPath: "inputs")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let inputs = change?[NSKeyValueChangeKey.newKey] as? [AnyObject], let captureDevice = (inputs.first as? AVCaptureDeviceInput)?.device {
lastKnownPosition = captureDevice.position
}
}
}
Upvotes: 0
Reputation: 12621
cameraViewTransform = cameraViewTransform.scaledBy ..
You simply set the cameraViewTransform
to a value.
In fact, to an affine transform.
Hence,
var mirrored = false
@objc func userTappedToToggleDisplayMirroring() {
mirrored = !mirrored
cameraViewTransform = CGAffineTransform(scaleX: mirrored ? -1 : 1, y: 1)
}
Upvotes: -2
Reputation: 299
It looks like AVCaptureDeviceDidStartRunningNotification
is no longer available as a means of detecting camera device changes. Also, the cameraDevice
property on UIImagePickerController
doesn't work with KVO. However, it's still possible to detect camera device changes, as shown below (though long-term support for this solution isn't guaranteed as we're using KVO on a property that isn't explicitly marked as KVO-compliant).
import AVFoundation
var context = 0
override func viewDidLoad() {
super.viewDidLoad()
// Register for notifications
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(handleCaptureSessionDidStartRunning(_:)), name: AVCaptureSessionDidStartRunningNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(handleCaptureSessionDidStopRunning(_:)), name: AVCaptureSessionDidStopRunningNotification, object: nil)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func handleCaptureSessionDidStartRunning(notification: NSNotification) {
guard let session = notification.object as? AVCaptureSession else { return }
session.addObserver(self, forKeyPath: "inputs", options: [ .Old, .New ], context: &context)
}
func handleCaptureSessionDidStopRunning(notification: NSNotification) {
guard let session = notification.object as? AVCaptureSession else { return }
session.removeObserver(self, forKeyPath: "inputs")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &self.context {
if let inputs = change?[NSKeyValueChangeNewKey] as? [AnyObject], captureDevice = (inputs.first as? AVCaptureDeviceInput)?.device {
switch captureDevice.position {
case .Back: print("Switched to back camera")
case .Front: print("Switched to front camera")
case .Unspecified: break
}
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
Swift 4+ version:
import AVFoundation
var context = 0
override func viewDidLoad() {
super.viewDidLoad()
// Register for notifications
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(forName: NSNotification.Name(rawValue: "AVCaptureSessionDidStartRunningNotification"), object: nil, queue: nil) { [weak self] notification in
self?.handleCaptureSessionDidStartRunning(notification: notification)
}
notificationCenter.addObserver(forName: NSNotification.Name(rawValue: "AVCaptureSessionDidStopRunningNotification"), object: nil, queue: nil) { [weak self] notification in
self?.handleCaptureSessionDidStopRunning(notification: notification)
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func handleCaptureSessionDidStartRunning(notification: Notification){
guard let session = notification.object as? AVCaptureSession else { return }
session.addObserver(self, forKeyPath: "inputs", options: [ .old, .new ], context: &context)
}
func handleCaptureSessionDidStopRunning(notification: Notification){
guard let session = notification.object as? AVCaptureSession else { return }
session.removeObserver(self, forKeyPath: "inputs")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context {
if let inputs = change?[NSKeyValueChangeKey.newKey] as? [AnyObject], let captureDevice = (inputs.first as? AVCaptureDeviceInput)?.device {
switch captureDevice.position {
case .back: print("Switched to back camera")
case .front: print("Switched to front camera")
case .unspecified: break
}
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
Upvotes: 1
Reputation: 1204
It took me few hours, but I think I got there. Here is a working solution for Swift 5.2 of how to get correct image (both in ImagePicker preview and in output).
//Registering to get notification when users takes a picture
override func viewDidLoad() {
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil, queue: nil) { (notification) in
self.changePhotoOrientation()
}
//Changing image orientation for ImagePicker preview
func changePhotoOrientation() {
var subviews: [UIView] = [imagePicker.view]
while (!subviews.isEmpty) {
let subview = subviews.removeFirst()
subviews += subview.subviews
if (subview.isKind(of: UIImageView.self)) {
subview.transform = self.imagePicker.cameraViewTransform.scaledBy(x: -1, y: 1)
}
}
}
//Changing image orientation for the output image
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let userPickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image = UIImage(cgImage: userPickedImage.cgImage!, scale: userPickedImage.scale, orientation: .leftMirrored)
}
}
}
Upvotes: 2
Reputation: 51
Updated "bandog" answer for swift 4
let picker = UIImagePickerController()
picker.cameraViewTransform = picker.cameraViewTransform.scaledBy(x: -1, y: 1)
Upvotes: 5
Reputation: 39988
You can flip the image from the source image use this
UIImage *flippedImage = [UIImage imageWithCGImage:picture.CGImage scale:picture.scale orientation:UIImageOrientationLeftMirrored];
Edit: Added swift code
let flippedImage = UIImage(CGImage: picture.CGImage, scale: picture.scale, orientation:.LeftMirrored)
Upvotes: 76
Reputation: 11675
As the other answers, I had the same problem. As Yonatan Betzer mentioned, just flip the final image is only half the answer, because the preview image, displayed by the UIPickerController when you take a picture with the front camera, it's still inverted (mirrored).
Yonatan Betzer's anwser works great, but he did not mentioned how or where to put the action to change the camera device.
Based in some codes from internet, I created a Pod to get this wanted behavior:
https://github.com/lucasecf/LEMirroredImagePicker
After installed, you just have to call this two lines of code together with your UIImagePickerController
:
self.mirrorFrontPicker = [[LEMirroredImagePicker alloc] initWithImagePicker:pickerController];
[self.mirrorFrontPicker mirrorFrontCamera];
And thats it, simply as that. You can check for more informations in the README of the github link.
Upvotes: 10
Reputation: 15694
Full Working Example in Swift, which answers to the initial question of this post (tested on an iPhone 5c using iOS 8.2):
import UIKit
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIActionSheetDelegate {
@IBOutlet var myUIImageView: UIImageView!
var myUIImagePickerController: UIImagePickerController!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
println("viewWillAppear(animated: Bool) method called.")
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
override func viewWillDisappear(animated: Bool) {
println("viewWillDisappear(animated: Bool) method called.")
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "cameraChanged:", name: "AVCaptureDeviceDidStartRunningNotification", object: nil)
}
/* UIImagePickerControllerDelegate Section */
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
if(self.myUIImagePickerController.sourceType == UIImagePickerControllerSourceType.Camera) {
self.myUIImageView.image = info[UIImagePickerControllerEditedImage] as? UIImage
} else {
self.myUIImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
}
self.dismissViewControllerAnimated(true, completion: nil)
}
func imagePickerControllerDidCancel(picker: UIImagePickerController) {
self.dismissViewControllerAnimated(true, completion: nil)
}
/*
You can choose to use one of the UIResponder methods:
touchesBegan, touchesMoved, touchesEnded etc, in order to detect the touch
on the UIImageView.
*/
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let touch: UITouch? = touches.anyObject() as? UITouch
if (touch?.view == myUIImageView) {
println("myUIImageView has been tapped by the user.")
self.takingAPictureUsingTheCamera()
}
}
func takingAPictureUsingTheCamera() {
self.myUIImagePickerController = UIImagePickerController()
self.myUIImagePickerController.delegate = self // Set the delegate
self.myUIImagePickerController.sourceType = UIImagePickerControllerSourceType.Camera
self.myUIImagePickerController.cameraDevice = UIImagePickerControllerCameraDevice.Front
// self.myUIImagePickerController.editing = true
self.myUIImagePickerController.allowsEditing = true
self.presentViewController(self.myUIImagePickerController, animated: true, completion: nil)
}
func cameraChanged(notification: NSNotification) {
println("cameraChanged(notification: NSNotification) method called.")
self.myUIImagePickerController.cameraViewTransform = CGAffineTransformIdentity
if(self.myUIImagePickerController.cameraDevice == UIImagePickerControllerCameraDevice.Front){
self.myUIImagePickerController.cameraViewTransform = CGAffineTransformScale(self.myUIImagePickerController.cameraViewTransform, -1, 1)
}
}
}// End class
Upvotes: 0
Reputation: 7173
I know this question is really old but it seems like this is a still a common problem. Just set a CGAffineTransform
on the cameraViewTransform
property on a UIImagePickerController
object.
let picker = UIImagePickerController()
picker.cameraViewTransform = CGAffineTransformScale(picker.cameraViewTransform, -1, 1)
Upvotes: 6
Reputation: 10398
Just to add how I have just achieved this without subclassing UIImagePickerController and without adding extra buttons to the camera view.
Simply listen for this notification which is fired several times whenever the camera is changed:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cameraChanged:)
name:@"AVCaptureDeviceDidStartRunningNotification"
object:nil];
Then use this method to flip the camera view:
- (void)cameraChanged:(NSNotification *)notification
{
if(imagePicker.cameraDevice == UIImagePickerControllerCameraDeviceFront)
{
imagePicker.cameraViewTransform = CGAffineTransformIdentity;
imagePicker.cameraViewTransform = CGAffineTransformScale(imagePicker.cameraViewTransform, -1, 1);
} else {
imagePicker.cameraViewTransform = CGAffineTransformIdentity;
}
}
Upvotes: 6
Reputation: 299
I had the same problem - and the solution above only got me half the answer, because the user had to approve the mirrored image before getting to the next page of my app - where I use the captured image after flipping it.
To solve this I had to flip the camera view whenever I switch to the front facing camera:
- (IBAction)flipCamera:(id)sender {
if(cameraUI.cameraDevice == UIImagePickerControllerCameraDeviceFront)
{
cameraUI.cameraDevice = UIImagePickerControllerCameraDeviceRear;
}
else {
cameraUI.cameraDevice = UIImagePickerControllerCameraDeviceFront;
}
cameraUI.cameraViewTransform = CGAffineTransformScale(cameraUI.cameraViewTransform, -1, 1);
}
Just to expand on this great answer, some typical complete code, Dec2013, iOS7 / Xcode5. Does everything. You just need an icon (cameraToggle.PNG in the example).
-(void)showTheDeviceCamera
{
if ( ! [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] )
return;
// self.cameraController is a UIImagePickerController
self.cameraController = [[UIImagePickerController alloc] init];
self.cameraController.delegate = (id)self;
self.cameraController.mediaTypes = @[(NSString *)kUTTypeImage];
self.cameraController.allowsEditing = YES;
self.cameraController.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:self.cameraController animated:YES completion:NULL];
// Add front-rear toggle button MANUALLY, IF NECESSARY
// (You seem to usually get it for free, on iPhone, but
// need to add manually on an iPad.)
UIView *buttonView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cameraToggle"]];
[buttonView sizeToFit];
buttonView.userInteractionEnabled = YES;
[self.cameraController.view addSubview:buttonView];
UITapGestureRecognizer *tap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_frontRearButtonClicked) ];
tap.numberOfTapsRequired = 1;
[buttonView addGestureRecognizer:tap];
// we'll add it at the top right .. could be anywhere you want
buttonView.center = CGPointMake(
self.cameraController.view.frame.size.width-buttonView.frame.size.width,
3.0 * buttonView.frame.size.height
);
}
-(void)_frontRearButtonClicked
{
[UIView transitionWithView:self.cameraController.view
duration:1.0
options:UIViewAnimationOptionAllowAnimatedContent | UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
if ( self.cameraController.cameraDevice == UIImagePickerControllerCameraDeviceRear )
self.cameraController.cameraDevice = UIImagePickerControllerCameraDeviceFront;
else
self.cameraController.cameraDevice = UIImagePickerControllerCameraDeviceRear;
} completion:NULL];
}
Upvotes: 19