I'm new to xcode, and I'm trying to make an app where you log in and access a camera, where you can adjust manual controls.
There's a login/signup view controller and it crashes at the segue to the second view controller for the camera and manual controls (the code is mostly based off the Apple sample code for this): when run on an iPhone it then gives an error * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key durationLabel.'*
I've checked the storyboard and connections and all the outlets and actions seem fine. I deleted and made new connections for whatever outlet the error said was not "coding compliant", but it keeps giving this error for a new outlet (it was "isoLabel before, now its "durationLabel"). What exactly is causing this? Sorry for formatting errors. Picture attached as well. Thanks for any help
import UIKit
import FirebaseAuth
import AVFoundation
private var CapturingStillImageContext = 0 //### iOS < 10.0=
private var LensPositionContext = 0
private var ExposureDurationContext = 0
private var ISOContext = 0
private var DeviceWhiteBalanceGainsContext = 0
private enum AVCamManualSetupResult: Int {
case success
case cameraNotAuthorized
case sessionConfigurationFailed
}
extension AVCapturePhotoOutput: AVCapturePhotoOutputType {}
@objc(ViewController)
class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {
@IBOutlet weak var manualHUD: UIView!
@IBOutlet weak var focusHUD: UIView!
@IBOutlet weak var positionLabel: UILabel!
@IBOutlet weak var focusPositionSlider: UISlider!
@IBOutlet weak var focusPositionValue: UILabel!
@IBOutlet weak var exposureHUD: UIView!
@IBOutlet weak var durationLabel: UILabel!
@IBOutlet weak var exposureDurationSlider: UISlider!
@IBOutlet weak var exposureDurationValue: UILabel!
@IBOutlet weak var isoLabel: UILabel!
@IBOutlet weak var exposureISOSlider: UISlider!
@IBOutlet weak var exposureISOValue: UILabel!
@IBOutlet weak var whitebalanceHUD: UIView!
@IBOutlet weak var temperatureLabel: UILabel!
@IBOutlet weak var whitebalanceTemperatureSlider: UISlider!
@IBOutlet weak var temperatureValue: UILabel!
@IBOutlet weak var tintLabel: UILabel!
@IBOutlet weak var whitebalanceTintSlider: UISlider!
@IBOutlet weak var tintValue: UILabel!
@IBOutlet weak var previewView1: AVCamManualPreviewView!
@IBOutlet weak var cameraButton: RoundButton!
@IBOutlet weak var whiteImage: UIImageView!
@IBOutlet weak var logOut: UIButton!
@IBOutlet weak var switchSetting: UISegmentedControl!
private let kExposureDurationPower = 5.0
private let kExposureMinimumDuration = 1.0/1000
// Utilities.
private var setupResult: AVCamManualSetupResult = .success
private var isSessionRunning: Bool = false
private var backgroundRecordingID: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
let device1 = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
var timer = Timer()
dynamic var session: AVCaptureSession!
dynamic var stillImageOutput: AVCapturePhotoOutput?
var previewViewLayer: AVCaptureVideoPreviewLayer?
var sessionQueue: DispatchQueue!
override func viewDidLoad() {
super.viewDidLoad()
// Create the AVCaptureSession.
self.session = AVCaptureSession()
// Create a device discovery session
// Setup the preview view.
self.previewView1.session = self.session
// Communicate with the session and other session objects on this queue.
self.sessionQueue = DispatchQueue(label: "session queue", attributes: [])
self.setupResult = .success
switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) {
case .authorized:
// The user has previously granted access to the camera.
break
case .notDetermined:
// The user has not yet been presented with the option to grant video access.
// We suspend the session queue to delay session running until the access request has completed.
self.sessionQueue.suspend()
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) {granted in
if !granted {
self.setupResult = .cameraNotAuthorized
}
self.sessionQueue.resume()
}
default:
// The user has previously denied access.
self.setupResult = .cameraNotAuthorized
}
self.sessionQueue.async {
self.configureSession()
}
self.focusHUD.isHidden = true
self.exposureHUD.isHidden = true
self.whitebalanceHUD.isHidden = true
print(Auth.auth().currentUser?.email! as Any)
session = AVCaptureSession()
session!.sessionPreset = AVCaptureSessionPresetPhoto
stillImageOutput = AVCapturePhotoOutput()
let backCamera = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
if let input = try? AVCaptureDeviceInput(device: backCamera) {
if (session?.canAddInput(input))! {
session?.addInput(input)
if (session?.canAddOutput(stillImageOutput))! {
session?.addOutput(stillImageOutput!)
previewViewLayer = AVCaptureVideoPreviewLayer(session: session)
previewViewLayer?.frame = previewView1.bounds
previewView1.layer.addSublayer(previewViewLayer!)
session?.startRunning()
}
} else {
print("Error : captureSesssion.canAddInput")
}
} else {
print("Unknown Error")
}
}
private func addObservers() {
self.addObserver(self, forKeyPath: "device1.lensPosition", options: .new, context: &LensPositionContext)
self.addObserver(self, forKeyPath: "device1.exposureDuration", options: .new, context: &ExposureDurationContext)
self.addObserver(self, forKeyPath: "device1.ISO", options: .new, context: &ISOContext)
self.addObserver(self, forKeyPath: "device1.deviceWhiteBalanceGains", options: .new, context: &DeviceWhiteBalanceGainsContext)
if #available(iOS 10.0, *) {
} else {
self.addObserver(self, forKeyPath: "stillImageOutput.capturingStillImage", options: .new, context: &CapturingStillImageContext)
}
}
private func removeObservers() {
NotificationCenter.default.removeObserver(self)
self.removeObserver(self, forKeyPath: "device1.lensPosition", context: &LensPositionContext)
self.removeObserver(self, forKeyPath: "device1.exposureDuration", context: &ExposureDurationContext)
self.removeObserver(self, forKeyPath: "device1.ISO", context: &ISOContext)
self.removeObserver(self, forKeyPath: "device1.deviceWhiteBalanceGains", context: &DeviceWhiteBalanceGainsContext)
if #available(iOS 10.0, *) {
} else {
self.removeObserver(self, forKeyPath: "stillImageOutput.capturingStillImage", context: &CapturingStillImageContext)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.sessionQueue.async {
switch self.setupResult {
case .success:
// Only setup observers and start the session running if setup succeeded.
self.addObservers()
self.session?.startRunning()
self.isSessionRunning = (self.session?.isRunning)!
case .cameraNotAuthorized:
DispatchQueue.main.async {
let message = NSLocalizedString("AVCamManual doesn't have permission to use the camera, please change privacy settings", comment: "Alert message when the user has denied access to the camera" )
let alertController = UIAlertController(title: "AVCamManual", message: message, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)
alertController.addAction(cancelAction)
// Provide quick access to Settings.
let settingsAction = UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .default) {action in
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!)
} else {
UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
}
}
alertController.addAction(settingsAction)
self.present(alertController, animated: true, completion: nil)
}
case .sessionConfigurationFailed:
DispatchQueue.main.async {
let message = NSLocalizedString("Unable to capture media", comment: "Alert message when something goes wrong during capture session configuration")
let alertController = UIAlertController(title: "AVCamManual", message: message, preferredStyle: .alert)
let cancelAction = UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
}
override func viewDidDisappear(_ animated: Bool) {
self.sessionQueue.async {
if self.setupResult == .success {
self.session?.stopRunning()
self.removeObservers()
}
}
super.viewDidDisappear(animated)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
let deviceOrientation = UIDevice.current.orientation
if UIDeviceOrientationIsPortrait(deviceOrientation) || UIDeviceOrientationIsLandscape(deviceOrientation) {
let previewLayer = self.previewView1.layer as! AVCaptureVideoPreviewLayer
previewLayer.connection.videoOrientation = AVCaptureVideoOrientation(rawValue: deviceOrientation.rawValue)!
}
}
override var prefersStatusBarHidden : Bool {
return true
}
@IBAction func changeHUD(_ sender: Any) {
if switchSetting.selectedSegmentIndex == 0 {
self.focusPositionSlider.isEnabled = true
self.focusPositionSlider.isHidden = false
self.focusHUD.isHidden = false
self.exposureHUD.isHidden = true
self.whitebalanceHUD.isHidden = true
}
if switchSetting.selectedSegmentIndex == 1 {
self.exposureDurationSlider.isEnabled = true
self.exposureDurationSlider.isHidden = false
self.exposureISOSlider.isEnabled = true
self.exposureISOSlider.isHidden = false
self.exposureHUD.isHidden = false
self.focusHUD.isHidden = true
self.whitebalanceHUD.isHidden = true
}
else {
self.whitebalanceTemperatureSlider.isEnabled = true
self.whitebalanceTemperatureSlider.isHidden = false
self.whitebalanceTintSlider.isEnabled = true
self.whitebalanceTintSlider.isHidden = false
self.focusHUD.isHidden = true
self.exposureHUD.isHidden = true
self.whitebalanceHUD.isHidden = false
}
}
private func configureManualHUD() {
// Manual focus controls
self.focusPositionSlider.minimumValue = 0.0
self.focusPositionSlider.maximumValue = 1.0
self.focusPositionSlider.value = self.device1?.lensPosition ?? 0
// Use 0-1 as the slider range and do a non-linear mapping from the slider value to the actual device exposure duration
self.exposureDurationSlider.minimumValue = 0
self.exposureDurationSlider.maximumValue = 1
let exposureDurationSeconds = CMTimeGetSeconds(self.device1?.exposureDuration ?? CMTime())
let minExposureDurationSeconds = max(CMTimeGetSeconds(self.device1?.activeFormat.minExposureDuration ?? CMTime()), kExposureMinimumDuration)
let maxExposureDurationSeconds = CMTimeGetSeconds(self.device1?.activeFormat.maxExposureDuration ?? CMTime())
// Map from duration to non-linear UI range 0-1
let p = (exposureDurationSeconds - minExposureDurationSeconds) / (maxExposureDurationSeconds - minExposureDurationSeconds) // Scale to 0-1
self.exposureDurationSlider.value = Float(pow(p, 1 / kExposureDurationPower)) // Apply inverse power
self.exposureISOSlider.minimumValue = self.device1?.activeFormat.minISO ?? 0.0
self.exposureISOSlider.maximumValue = self.device1?.activeFormat.maxISO ?? 0.0
self.exposureISOSlider.value = self.device1?.iso ?? 0.0
self.exposureISOSlider.isEnabled = (self.device1?.exposureMode == .custom)
// Manual white balance controls
let whiteBalanceGains = self.device1?.deviceWhiteBalanceGains ?? AVCaptureWhiteBalanceGains()
let whiteBalanceTemperatureAndTint = self.device1?.temperatureAndTintValues(forDeviceWhiteBalanceGains: whiteBalanceGains) ?? AVCaptureWhiteBalanceTemperatureAndTintValues()
self.whitebalanceTemperatureSlider.minimumValue = 3000
self.whitebalanceTemperatureSlider.maximumValue = 8000
self.whitebalanceTemperatureSlider.value = whiteBalanceTemperatureAndTint.temperature
self.whitebalanceTintSlider.minimumValue = -150
self.whitebalanceTintSlider.maximumValue = 150
self.whitebalanceTintSlider.value = whiteBalanceTemperatureAndTint.tint
}
private func set(_ slider: UISlider, highlight color: UIColor) {
slider.tintColor = color
if slider === self.focusPositionSlider {
self.positionLabel.textColor = slider.tintColor
self.focusPositionValue.textColor = slider.tintColor
} else if slider === self.exposureDurationSlider {
self.durationLabel.textColor = slider.tintColor
self.exposureDurationValue.textColor = slider.tintColor
} else if slider === self.exposureISOSlider {
self.isoLabel.textColor = slider.tintColor
self.exposureISOValue.textColor = slider.tintColor
} else if slider === self.whitebalanceTemperatureSlider {
self.temperatureLabel.textColor = slider.tintColor
self.temperatureValue.textColor = slider.tintColor
} else if slider === self.whitebalanceTintSlider {
self.tintLabel.textColor = slider.tintColor
self.tintValue.textColor = slider.tintColor
}
}
@IBAction func sliderTouchBegan(_ slider: UISlider) {
self.set(slider, highlight: UIColor(red: 0.0, green: 122.0/255.0, blue: 1.0, alpha: 1.0))
}
@IBAction func sliderTouchEnded(_ slider: UISlider) {
self.set(slider, highlight: UIColor.gray)
}
private func configureSession() {
guard self.setupResult == .success else {
return
}
self.session?.beginConfiguration()
self.session?.sessionPreset = AVCaptureSessionPresetPhoto
DispatchQueue.main.async {
self.configureManualHUD()
}
}
@available(iOS 10.0, *)
private func currentPhotoSettings() -> AVCapturePhotoSettings? {
guard let stillImageOutput = self.stillImageOutput else {
return nil
}
var photoSettings: AVCapturePhotoSettings? = nil
if stillImageOutput.isLensStabilizationDuringBracketedCaptureSupported {
let bracketedSettings: [AVCaptureBracketedStillImageSettings]
bracketedSettings = [AVCaptureManualExposureBracketedStillImageSettings.manualExposureSettings(withExposureDuration: AVCaptureExposureDurationCurrent, iso: AVCaptureISOCurrent)]
if !stillImageOutput.availableRawPhotoPixelFormatTypes.isEmpty {
photoSettings = AVCapturePhotoBracketSettings(rawPixelFormatType: stillImageOutput.availableRawPhotoPixelFormatTypes[0].uint32Value, processedFormat: nil, bracketedSettings: bracketedSettings)
} else {
photoSettings = AVCapturePhotoBracketSettings(rawPixelFormatType: 0, processedFormat: [AVVideoCodecKey: AVVideoCodecJPEG], bracketedSettings: bracketedSettings)
}
(photoSettings as! AVCapturePhotoBracketSettings).isLensStabilizationEnabled = true
} else {
if !stillImageOutput.availableRawPhotoPixelFormatTypes.isEmpty {
photoSettings = AVCapturePhotoSettings(rawPixelFormatType: stillImageOutput.availableRawPhotoPixelFormatTypes[0].uint32Value, processedFormat: [AVVideoCodecKey : AVVideoCodecJPEG])
} else {
photoSettings = AVCapturePhotoSettings()
}
photoSettings?.flashMode = stillImageOutput.supportedFlashModes.contains(AVCaptureFlashMode.auto.rawValue as NSNumber) ? .auto : .off
}
if !(photoSettings?.availablePreviewPhotoPixelFormatTypes.isEmpty ?? true) {
photoSettings?.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: photoSettings!.availablePreviewPhotoPixelFormatTypes[0]]
}
photoSettings?.isAutoStillImageStabilizationEnabled = true
photoSettings?.isHighResolutionPhotoEnabled = true
return photoSettings
}
@IBAction func changeLensPosition(_ control: UISlider) {
do {
try self.device1!.lockForConfiguration()
self.device1!.setFocusModeLockedWithLensPosition(control.value, completionHandler: nil)
self.device1!.unlockForConfiguration()
} catch let error {
NSLog("Could not lock device for configuration: \(error)")
}
}
@IBAction func changeExposureDuration(_ control: UISlider) {
let p = pow(Double(control.value), kExposureDurationPower) // Apply power function to expand slider's low-end range
let minDurationSeconds = max(CMTimeGetSeconds(self.device1!.activeFormat.minExposureDuration), kExposureMinimumDuration)
let maxDurationSeconds = CMTimeGetSeconds(self.device1!.activeFormat.maxExposureDuration)
let newDurationSeconds = p * ( maxDurationSeconds - minDurationSeconds ) + minDurationSeconds; // Scale from 0-1 slider range to actual duration
do {
try self.device1!.lockForConfiguration()
self.device1!.setExposureModeCustomWithDuration(CMTimeMakeWithSeconds(newDurationSeconds, 1000*1000*1000), iso: AVCaptureISOCurrent, completionHandler: nil)
self.device1!.unlockForConfiguration()
} catch let error {
NSLog("Could not lock device for configuration: \(error)")
}
}
@IBAction func changeISO(_ control: UISlider) {
do {
try self.device1!.lockForConfiguration()
self.device1!.setExposureModeCustomWithDuration(AVCaptureExposureDurationCurrent, iso: control.value, completionHandler: nil)
self.device1!.unlockForConfiguration()
} catch let error {
NSLog("Could not lock device for configuration: \(error)")
}
}
private func setWhiteBalanceGains(_ gains: AVCaptureWhiteBalanceGains) {
do {
try self.device1!.lockForConfiguration()
let normalizedGains = self.normalizedGains(gains)
self.device1!.setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains(normalizedGains, completionHandler: nil)
self.device1!.unlockForConfiguration()
} catch let error {
NSLog("Could not lock device for configuration: \(error)")
}
}
@IBAction func changeTemperature(_: AnyObject) {
let temperatureAndTint = AVCaptureWhiteBalanceTemperatureAndTintValues(
temperature: self.whitebalanceTemperatureSlider.value,
tint: self.whitebalanceTintSlider.value
)
self.setWhiteBalanceGains(self.device1!.deviceWhiteBalanceGains(for: temperatureAndTint))
}
@IBAction func changeTint(_: AnyObject) {
let temperatureAndTint = AVCaptureWhiteBalanceTemperatureAndTintValues(
temperature: self.whitebalanceTemperatureSlider.value,
tint: self.whitebalanceTintSlider.value
)
self.setWhiteBalanceGains(self.device1!.deviceWhiteBalanceGains(for: temperatureAndTint))
}
private func normalizedGains(_ gains: AVCaptureWhiteBalanceGains) -> AVCaptureWhiteBalanceGains {
var g = gains
g.redGain = max(1.0, g.redGain)
g.greenGain = max(1.0, g.greenGain)
g.blueGain = max(1.0, g.blueGain)
g.redGain = min(self.device1!.maxWhiteBalanceGain, g.redGain)
g.greenGain = min(self.device1!.maxWhiteBalanceGain, g.greenGain)
g.blueGain = min(self.device1!.maxWhiteBalanceGain, g.blueGain)
return g
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func pressCamera(_ sender: Any) {
let settings = AVCapturePhotoSettings()
let previewLayer = self.previewView1.layer as! AVCaptureVideoPreviewLayer
let videoPreviewLayerVideoOrientation = previewLayer.connection.videoOrientation
let photoOutputConnection = self.stillImageOutput?.connection(withMediaType: AVMediaTypeVideo)
photoOutputConnection?.videoOrientation = videoPreviewLayerVideoOrientation
let previewPixelType = settings.availablePreviewPhotoPixelFormatTypes.first!
let previewFormat = [
kCVPixelBufferPixelFormatTypeKey as String: previewPixelType,
kCVPixelBufferWidthKey as String: 160,
kCVPixelBufferHeightKey as String: 160
]
settings.previewPhotoFormat = previewFormat
stillImageOutput?.capturePhoto(with: settings, delegate: self)
whiteImage.image = UIImage(named: "white")
//bool = false
timer = Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
}
// called every time interval from the timer
func delayedAction() {
whiteImage.image = UIImage(named: "asfalt-light")
}
@IBAction func doLogout(_ sender: Any) {
try! Auth.auth().signOut()
performSegue(withIdentifier: "logout", sender: self)
}
//call back from take picture
func capture(_ stillImageOutput: AVCapturePhotoOutput,didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
if let error = error {
print("error occure : \(error.localizedDescription)")
}
if let sampleBuffer = photoSampleBuffer,
let previewBuffer = previewPhotoSampleBuffer,
let dataImage = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: sampleBuffer, previewPhotoSampleBuffer: previewBuffer) {
print(UIImage(data: dataImage)?.size as Any)
}
else {
print("Unknown Error")
}
}
}