react-native-camera-kit/ios/CameraViewController.swift
2016-04-14 10:44:00 +03:00

386 lines
16 KiB
Swift

//
// CameraViewController.swift
// ReactNativeCameraKit
//
// Created by Natalia Grankina on 4/13/16.
// Copyright © 2016 Facebook. All rights reserved.
//
import UIKit
import AVFoundation
import AssetsLibrary
import Photos
struct AspectRatio {
let widthRatio: Int
let heightRatio: Int
init(widthRatio: Int, heightRatio: Int) {
self.widthRatio = widthRatio
self.heightRatio = heightRatio
}
}
class CameraViewController : UIViewController, PhotoViewControllerDelegate {
var cameraViewControllerDelegate: CameraViewControllerDelegate?
var cameraManager: CameraSessionManager!
var cameraOptions: [String: AnyObject]!
let topBarHeight: CGFloat = 50
var topBarButtonSize: CGSize!
let bottomBarHeight: CGFloat = 115
var flashButton: UIButton!
let flashModes = ["Auto", "On", "Off"]
var flashModeSelector: UISegmentedControl!
var ratioField = UITextField()
let aspectRatios: [String]
var aspectRatio: AspectRatio!
var ratioLayer = UIView()
var infoLabel: UITextField!
let flashColor = UIColor(colorLiteralRed: 0.95, green: 0.76, blue: 0.2, alpha: 1)
let assetCollectionName: String!
init(cameraOptions: [String: AnyObject]) {
self.cameraOptions = cameraOptions
self.aspectRatios = cameraOptions["aspectRatios"] as! [String]
self.assetCollectionName = cameraOptions["collectionName"] as! String;
super.init(nibName: nil, bundle: nil)
self.aspectRatio = self.extractRatio(aspectRatios[0])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func shouldAutorotate() -> Bool {
return false
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.setupCameraManager(.BackFacingCamera)
let sessionQueue = dispatch_queue_create("cameraQueue", DISPATCH_QUEUE_SERIAL)
dispatch_async(sessionQueue) { () -> Void in
self.cameraManager.captureSession.startRunning()
}
self.buildUi()
}
private func setupCameraManager(cameraType: CameraType) {
self.cameraManager = CameraSessionManager(cameraType: cameraType)
self.cameraManager.previewLayer.frame = CGRect(x: 0, y: topBarHeight, width: self.view.frame.size.width, height: self.view.frame.size.height - (topBarHeight + bottomBarHeight))
self.view.layer.addSublayer(self.cameraManager.previewLayer)
self.fitAspectRatio(aspectRatio)
}
private func buildUi() {
topBarButtonSize = CGSizeMake(view.bounds.size.height * 0.04, view.bounds.size.height * 0.04)
self.addToolbars()
self.addShutterButton()
self.addCloseButton()
self.addFlashButton()
self.addFlashModeSelector()
self.addRatioSelector()
}
private func addToolbars() {
let topBarView = UIView()
topBarView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: topBarHeight)
topBarView.backgroundColor = UIColor.blackColor()
self.view.addSubview(topBarView)
let bottomBarView = UIView()
bottomBarView.frame = CGRect(x: 0, y: self.view.frame.size.height - bottomBarHeight, width: self.view.frame.size.width, height: bottomBarHeight)
bottomBarView.backgroundColor = UIColor.blackColor()
self.view.addSubview(bottomBarView)
}
private func addShutterButton() {
let shutterButtonSize = CGSizeMake(self.view.bounds.size.width * 0.23, self.view.bounds.size.width * 0.23)
let image = UIImage(named: "ShutterIcon") as UIImage?
let button = UIButton(type: UIButtonType.Custom) as UIButton
button.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: shutterButtonSize)
button.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height - shutterButtonSize.height / 2 - 5)
button.setImage(image, forState: .Normal)
button.addTarget(self, action: "onTakePhoto:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
}
private func addCloseButton() {
let image = UIImage(named: "CloseIcon") as UIImage?
let closeButton = UIButton(type: UIButtonType.Custom) as UIButton
closeButton.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: topBarButtonSize)
closeButton.center = CGPointMake(topBarButtonSize.width / 2, topBarHeight / 2)
closeButton.setImage(image, forState: .Normal)
closeButton.addTarget(self, action: "onClose:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(closeButton)
}
func addFlashButton() {
let image = UIImage(named: "FlashAutoIcon") as UIImage?
flashButton = UIButton(type: UIButtonType.Custom) as UIButton
flashButton.frame = CGRect(origin: CGPoint(x: self.view.bounds.size.width - topBarButtonSize.width, y: 0), size: topBarButtonSize)
flashButton.center = CGPointMake(self.view.bounds.size.width - topBarButtonSize.width / 2, topBarHeight / 2)
flashButton.setImage(image, forState: .Normal)
flashButton.addTarget(self, action: "onFlashChange:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(flashButton)
}
func addFlashModeSelector() {
let controlWidth = 0.6 * self.view.bounds.size.width
flashModeSelector = UISegmentedControl(items: flashModes)
flashModeSelector.selectedSegmentIndex = 0
flashModeSelector.frame = CGRectMake((self.view.bounds.size.width - controlWidth) / 2, 0, controlWidth, topBarHeight)
flashModeSelector.backgroundColor = UIColor.clearColor()
flashModeSelector.tintColor = UIColor.clearColor()
flashModeSelector.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.whiteColor()], forState: UIControlState.Normal)
flashModeSelector.setTitleTextAttributes([NSForegroundColorAttributeName: flashColor], forState: UIControlState.Selected)
flashModeSelector.addTarget(self, action: "changeFlashMode:", forControlEvents: .ValueChanged)
}
func addRatioSelector() {
let ratioPicker = UIPickerView()
ratioPicker.showsSelectionIndicator = true
ratioPicker.delegate = self
ratioPicker.dataSource = self
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.Default
toolBar.tintColor = UIColor.blackColor()
toolBar.translucent = true
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: "onRatioSelected:")
toolBar.setItems([doneButton], animated: false)
toolBar.userInteractionEnabled = true
let fieldHeight: CGFloat = 40
ratioField.tintColor = UIColor.clearColor()
ratioField.inputView = ratioPicker
ratioField.inputAccessoryView = toolBar
ratioField.text = aspectRatios[0]
ratioField.frame = CGRectMake(self.view.frame.size.width * 0.85, self.view.frame.size.height - bottomBarHeight, self.view.frame.size.width * 0.15, fieldHeight)
ratioField.textAlignment = NSTextAlignment.Right
ratioField.contentVerticalAlignment = UIControlContentVerticalAlignment.Center
ratioField.textColor = flashColor
self.view.addSubview(ratioField)
self.view.addSubview(ratioLayer)
infoLabel = UITextField()
infoLabel.text = cameraOptions["aspectRatioInfoMessage"] as! String
infoLabel.adjustsFontSizeToFitWidth = true
infoLabel.textColor = UIColor.whiteColor()
infoLabel.textAlignment = NSTextAlignment.Left
infoLabel.frame = CGRectMake(0, self.view.frame.size.height - bottomBarHeight, self.view.frame.size.width * 0.75, fieldHeight)
infoLabel.inputView = ratioPicker
infoLabel.inputAccessoryView = toolBar
self.view.addSubview(infoLabel)
}
private func fitAspectRatio(aspectRatio: AspectRatio) {
let previewLayerExcess = CropHelper.cropRectangleToFitRatio(self.cameraManager.previewLayer.frame.width, originalRectangleHeight: self.cameraManager.previewLayer.frame.height, widthRatio: aspectRatio.widthRatio, heightRatio: aspectRatio.heightRatio)
let backgroundColor = UIColor(white: 0, alpha: 0.5)
self.ratioLayer.removeFromSuperview()
self.ratioLayer = UIView(frame: CGRect(origin: self.cameraManager.previewLayer.frame.origin, size: self.cameraManager.previewLayer.frame.size))
self.view.addSubview(self.ratioLayer)
if (previewLayerExcess.verticalExcess != 0.0) {
let topExcess = UIView()
topExcess.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: self.cameraManager.previewLayer.frame.width, height: previewLayerExcess.verticalExcess / 2))
topExcess.backgroundColor = backgroundColor
self.ratioLayer.addSubview(topExcess)
let bottomExcess = UIView()
bottomExcess.frame = CGRect(x: self.cameraManager.previewLayer.frame.origin.x, y: self.cameraManager.previewLayer.frame.height - previewLayerExcess.verticalExcess / 2, width: self.cameraManager.previewLayer.frame.width, height: previewLayerExcess.verticalExcess / 2)
bottomExcess.backgroundColor = backgroundColor
self.ratioLayer.addSubview(bottomExcess)
} else {
let leftExcess = UIView()
leftExcess.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: previewLayerExcess.horizontalExcess / 2, height: self.cameraManager.previewLayer.frame.height))
leftExcess.backgroundColor = backgroundColor
self.ratioLayer.addSubview(leftExcess)
let rightExcess = UIView()
rightExcess.frame = CGRect(x: self.cameraManager.previewLayer.frame.origin.x + self.cameraManager.previewLayer.frame.width - previewLayerExcess.horizontalExcess / 2, y: 0, width: previewLayerExcess.horizontalExcess / 2, height: self.cameraManager.previewLayer.frame.height)
rightExcess.backgroundColor = backgroundColor
self.ratioLayer.addSubview(rightExcess)
}
}
func onFlashChange(sender: UIButton) {
sender.selected = !sender.selected
if sender.selected {
flashButton.setImage(UIImage(named: "FlashAutoIcon") as UIImage?, forState: .Normal)
self.view.addSubview(flashModeSelector)
} else {
flashModeSelector.removeFromSuperview()
setFlashIcon()
}
}
func onRatioSelected(sender: UIButton) {
self.ratioField.resignFirstResponder()
self.infoLabel.resignFirstResponder()
}
func changeFlashMode(_: UISegmentedControl) {
setFlashIcon()
flashButton.selected = false
flashModeSelector.removeFromSuperview()
}
private func setFlashIcon() {
switch flashModeSelector.selectedSegmentIndex {
case 1:
cameraManager.changeFlashMode(.On)
flashButton.setImage(UIImage(named: "FlashOnIcon") as UIImage?, forState: .Normal)
break
case 2:
cameraManager.changeFlashMode(.Off)
flashButton.setImage(UIImage(named: "FlashOffIcon") as UIImage?, forState: .Normal)
break
default:
cameraManager.changeFlashMode(.Auto)
flashButton.setImage(UIImage(named: "FlashAutoIcon") as UIImage?, forState: .Normal)
break
}
}
func onTakePhoto(sender: UIButton) {
self.cameraManager.captureStillImage({ (image: UIImage) -> Void in
let croppedImage = self.cropImage(image)
self.showPhotoViewController(croppedImage)
})
}
func onClose(sender: UIButton) {
self.cameraManager.stopSession()
//dismissViewControllerAnimated(true, completion: nil)
if let delegate = self.cameraViewControllerDelegate {
delegate.cameraViewControllerDidCancel(self)
}
}
func showPhotoViewController(image: UIImage) {
let photoViewController = PhotoViewController(image: image)
photoViewController.delegate = self
photoViewController.view.bounds = self.view.bounds
self.addChildViewController(photoViewController)
self.view.addSubview(photoViewController.view)
photoViewController.didMoveToParentViewController(self)
}
func hidePhotoViewController(controller: PhotoViewController) {
controller.willMoveToParentViewController(nil)
controller.view.removeFromSuperview()
controller.removeFromParentViewController()
}
//PhotoViewControllerDelegate
func retakePhoto(controller: PhotoViewController) {
self.hidePhotoViewController(controller)
}
func usePhoto(controller: PhotoViewController, photo: UIImage) {
dismissViewControllerAnimated(true, completion: nil)
let imageData = UIImageJPEGRepresentation(photo, 1.0)
let base64 = imageData!.base64EncodedStringWithOptions([])
var assetCollection: PHAssetCollection?
var assetCollectionPlaceholder: PHObjectPlaceholder?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", self.assetCollectionName)
let collection : PHFetchResult = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
if let _: AnyObject = collection.firstObject {
assetCollection = collection.firstObject as? PHAssetCollection
savePhoto(base64, photo: photo, assetCollection: assetCollection!)
} else {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let createAlbumRequest : PHAssetCollectionChangeRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(self.assetCollectionName)
assetCollectionPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
}, completionHandler: { success, error in
if (success) {
let collectionFetchResult = PHAssetCollection.fetchAssetCollectionsWithLocalIdentifiers([assetCollectionPlaceholder!.localIdentifier], options: nil)
assetCollection = collectionFetchResult.firstObject as? PHAssetCollection
self.savePhoto(base64, photo: photo, assetCollection: assetCollection!)
}
})
}
}
private func savePhoto(imageData: String, photo: UIImage, assetCollection: PHAssetCollection) {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(photo)
let assetPlaceholder = assetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: assetCollection)
albumChangeRequest!.addAssets([assetPlaceholder!])
}, completionHandler: { (success, error) -> Void in
self.cameraManager.stopSession()
if let delegate = self.cameraViewControllerDelegate {
if success {
delegate.imageHasBeenTaken(self, imageData: imageData)
} else {
delegate.onError(self, error: (error?.localizedDescription)!)
}
}
})
}
func cropImage(image: UIImage) -> UIImage {
let barPart: CGFloat = (topBarHeight + bottomBarHeight) / self.view.bounds.size.height
return CropHelper.cropImage(image, widthRatio: aspectRatio.widthRatio, heightRatio: aspectRatio.heightRatio, verticalPartToCrop: barPart)
}
func extractRatio(ratioString: String) -> AspectRatio {
let ratios = ratioString.characters.split{$0 == ":"}.map(String.init)
return AspectRatio(widthRatio: Int(ratios[0])!, heightRatio: Int(ratios[1])!)
}
}
extension CameraViewController: UIPickerViewDataSource {
func numberOfComponentsInPickerView(colorPicker: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return aspectRatios.count
}
}
extension CameraViewController: UIPickerViewDelegate {
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return aspectRatios[row]
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
ratioField.text = aspectRatios[row]
self.aspectRatio = self.extractRatio(aspectRatios[row])
self.fitAspectRatio(self.aspectRatio)
}
}