diff --git a/Demo/nb.lproj/Localizable.strings b/Demo/nb.lproj/Localizable.strings deleted file mode 100644 index a26508c..0000000 --- a/Demo/nb.lproj/Localizable.strings +++ /dev/null @@ -1,16 +0,0 @@ -/* - Localizable.strings - Finjinon - - Created by Sørensen, Johan on 26.06.15. - Copyright (c) 2015 FINN.no AS. All rights reserved. -*/ - -"OK" = "OK"; -"Camera access denied, please enable it in the Settings app to continue" = "Denne appen har ikke tilgang til ditt kamera. Du kan gi appen tilgang i enhetens personvern instillinger"; -"Off" = "Av"; -"Done" = "Ferdig"; -"Photos" = "Bilder"; -"On" = "På"; -"Auto" = "Auto"; -"Take a picture" = "Ta et bilde"; diff --git a/Finjinon.podspec b/Finjinon.podspec index c20617f..78b13fa 100644 --- a/Finjinon.podspec +++ b/Finjinon.podspec @@ -13,11 +13,11 @@ Finjinon is a custom AVFoundation based camera UI, focused on quickly adding sev s.platform = :ios, '10.0' s.swift_version = '4.2' s.source = { :git => "https://github.com/finn-no/Finjinon.git", :tag => s.version } - s.resources = 'Sources/Resources/*.xcassets' + s.resources = 'Sources/Resources/*.{xcassets,lproj}' s.resource_bundles = { - 'Finjinon' => ['Sources/Resources/*.xcassets'] + 'Finjinon' => ['Sources/Resources/*.xcassets', 'Sources/Resources/*.lproj'] } s.requires_arc = true - s.source_files = "Sources/*.swift" + s.source_files = "Sources/**/*.swift" s.frameworks = "Foundation" end diff --git a/Finjinon.xcodeproj/project.pbxproj b/Finjinon.xcodeproj/project.pbxproj index c23c65f..c53de7f 100644 --- a/Finjinon.xcodeproj/project.pbxproj +++ b/Finjinon.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ CF9B327D229829AD00CCB8F9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B3271229829AD00CCB8F9 /* ViewController.swift */; }; CF9B327E229829AD00CCB8F9 /* LaunchScreen.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3272229829AD00CCB8F9 /* LaunchScreen.strings */; }; CF9B327F229829AD00CCB8F9 /* Main.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3274229829AD00CCB8F9 /* Main.strings */; }; - CF9B3280229829AD00CCB8F9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3276229829AD00CCB8F9 /* Localizable.strings */; }; CF9B3281229829AD00CCB8F9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3278229829AD00CCB8F9 /* Assets.xcassets */; }; CF9B3282229829AD00CCB8F9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */; }; CF9B3283229829AD00CCB8F9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B327B229829AD00CCB8F9 /* AppDelegate.swift */; }; @@ -28,6 +27,11 @@ CF9B3299229829E900CCB8F9 /* PhotoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B328E229829E900CCB8F9 /* PhotoCollectionViewCell.swift */; }; CF9B329A229829E900CCB8F9 /* PhotoStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B328F229829E900CCB8F9 /* PhotoStorage.swift */; }; CF9B329F229829FC00CCB8F9 /* FinjinonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF9B329D229829FC00CCB8F9 /* FinjinonTests.swift */; }; + CFBD7133229D6499003B1BD9 /* LightingCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD7131229D6499003B1BD9 /* LightingCondition.swift */; }; + CFBD7134229D6499003B1BD9 /* LowLightService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD7132229D6499003B1BD9 /* LowLightService.swift */; }; + CFBD7138229D66C0003B1BD9 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD7137229D66C0003B1BD9 /* StringExtensions.swift */; }; + CFBD7139229D6876003B1BD9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CFBD713B229D6876003B1BD9 /* Localizable.strings */; }; + CFBD713E229D68FF003B1BD9 /* LowLightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBD713D229D68FF003B1BD9 /* LowLightView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -75,7 +79,6 @@ CF9B3271229829AD00CCB8F9 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; CF9B3273229829AD00CCB8F9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/LaunchScreen.strings; sourceTree = ""; }; CF9B3275229829AD00CCB8F9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Main.strings; sourceTree = ""; }; - CF9B3277229829AD00CCB8F9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; CF9B3278229829AD00CCB8F9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CF9B327A229829AD00CCB8F9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CF9B327B229829AD00CCB8F9 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -93,6 +96,12 @@ CF9B3290229829E900CCB8F9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CF9B329D229829FC00CCB8F9 /* FinjinonTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FinjinonTests.swift; sourceTree = ""; }; CF9B329E229829FC00CCB8F9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CFBD7131229D6499003B1BD9 /* LightingCondition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightingCondition.swift; sourceTree = ""; }; + CFBD7132229D6499003B1BD9 /* LowLightService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LowLightService.swift; sourceTree = ""; }; + CFBD7137229D66C0003B1BD9 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; + CFBD713A229D6876003B1BD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + CFBD713C229D687C003B1BD9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + CFBD713D229D68FF003B1BD9 /* LowLightView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LowLightView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -146,6 +155,7 @@ isa = PBXGroup; children = ( CF9B3288229829E900CCB8F9 /* Assets.xcassets */, + CFBD713B229D6876003B1BD9 /* Localizable.strings */, ); path = Resources; sourceTree = ""; @@ -156,7 +166,6 @@ CF9B3271229829AD00CCB8F9 /* ViewController.swift */, CF9B3272229829AD00CCB8F9 /* LaunchScreen.strings */, CF9B3274229829AD00CCB8F9 /* Main.strings */, - CF9B3276229829AD00CCB8F9 /* Localizable.strings */, CF9B3278229829AD00CCB8F9 /* Assets.xcassets */, CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */, CF9B327B229829AD00CCB8F9 /* AppDelegate.swift */, @@ -168,17 +177,19 @@ CF9B3285229829E900CCB8F9 /* Sources */ = { isa = PBXGroup; children = ( - CF414C26229A9A91008980E0 /* Resources */, CF9B328A229829E900CCB8F9 /* Finjinon.h */, CF9B3290229829E900CCB8F9 /* Info.plist */, CF9B328C229829E900CCB8F9 /* AssetResolver.swift */, CF9B3287229829E900CCB8F9 /* Buttons.swift */, CF9B3289229829E900CCB8F9 /* CaptureManager.swift */, CF9B328D229829E900CCB8F9 /* ImagePickerAdapter.swift */, + CFBD713F229D690C003B1BD9 /* LowLight */, CF9B328B229829E900CCB8F9 /* PhotoCaptureViewController.swift */, CF9B328E229829E900CCB8F9 /* PhotoCollectionViewCell.swift */, CF9B3286229829E900CCB8F9 /* PhotoCollectionViewLayout.swift */, CF9B328F229829E900CCB8F9 /* PhotoStorage.swift */, + CFBD7137229D66C0003B1BD9 /* StringExtensions.swift */, + CF414C26229A9A91008980E0 /* Resources */, ); path = Sources; sourceTree = ""; @@ -192,6 +203,16 @@ path = Tests; sourceTree = ""; }; + CFBD713F229D690C003B1BD9 /* LowLight */ = { + isa = PBXGroup; + children = ( + CFBD7132229D6499003B1BD9 /* LowLightService.swift */, + CFBD713D229D68FF003B1BD9 /* LowLightView.swift */, + CFBD7131229D6499003B1BD9 /* LightingCondition.swift */, + ); + path = LowLight; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -316,7 +337,6 @@ buildActionMask = 2147483647; files = ( CF9B327E229829AD00CCB8F9 /* LaunchScreen.strings in Resources */, - CF9B3280229829AD00CCB8F9 /* Localizable.strings in Resources */, CF9B3281229829AD00CCB8F9 /* Assets.xcassets in Resources */, CF9B3282229829AD00CCB8F9 /* LaunchScreen.storyboard in Resources */, CF9B327F229829AD00CCB8F9 /* Main.strings in Resources */, @@ -328,6 +348,7 @@ buildActionMask = 2147483647; files = ( CF9B3293229829E900CCB8F9 /* Assets.xcassets in Resources */, + CFBD7139229D6876003B1BD9 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -357,10 +378,14 @@ CF9B3296229829E900CCB8F9 /* PhotoCaptureViewController.swift in Sources */, CF9B3291229829E900CCB8F9 /* PhotoCollectionViewLayout.swift in Sources */, CF9B3292229829E900CCB8F9 /* Buttons.swift in Sources */, + CFBD7134229D6499003B1BD9 /* LowLightService.swift in Sources */, CF9B3294229829E900CCB8F9 /* CaptureManager.swift in Sources */, CF9B329A229829E900CCB8F9 /* PhotoStorage.swift in Sources */, + CFBD713E229D68FF003B1BD9 /* LowLightView.swift in Sources */, + CFBD7138229D66C0003B1BD9 /* StringExtensions.swift in Sources */, CF9B3299229829E900CCB8F9 /* PhotoCollectionViewCell.swift in Sources */, CF9B3297229829E900CCB8F9 /* AssetResolver.swift in Sources */, + CFBD7133229D6499003B1BD9 /* LightingCondition.swift in Sources */, CF9B3298229829E900CCB8F9 /* ImagePickerAdapter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -410,20 +435,21 @@ name = Main.strings; sourceTree = ""; }; - CF9B3276229829AD00CCB8F9 /* Localizable.strings */ = { + CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( - CF9B3277229829AD00CCB8F9 /* nb */, + CF9B327A229829AD00CCB8F9 /* Base */, ); - name = Localizable.strings; + name = LaunchScreen.storyboard; sourceTree = ""; }; - CF9B3279229829AD00CCB8F9 /* LaunchScreen.storyboard */ = { + CFBD713B229D6876003B1BD9 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - CF9B327A229829AD00CCB8F9 /* Base */, + CFBD713A229D6876003B1BD9 /* nb */, + CFBD713C229D687C003B1BD9 /* en */, ); - name = LaunchScreen.storyboard; + name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ diff --git a/Sources/CaptureManager.swift b/Sources/CaptureManager.swift index b520224..9672a2d 100644 --- a/Sources/CaptureManager.swift +++ b/Sources/CaptureManager.swift @@ -10,7 +10,14 @@ enum CaptureManagerViewfinderMode { case window } +protocol CaptureManagerDelegate: AnyObject { + func captureManager(_ manager: CaptureManager, didDetectLightingCondition: LightingCondition) +} + + class CaptureManager: NSObject { + weak var delegate: CaptureManagerDelegate? + let previewLayer: AVCaptureVideoPreviewLayer var flashMode: AVCaptureDevice.FlashMode { return cameraDevice?.flashMode ?? .off @@ -37,6 +44,10 @@ class CaptureManager: NSObject { fileprivate var cameraDevice: AVCaptureDevice? fileprivate var stillImageOutput: AVCaptureStillImageOutput? fileprivate var orientation = AVCaptureVideoOrientation.portrait + private var lastVideoCaptureTime = CMTime() + private let lowLightService = LowLightService() + + /// Array of vision requests override init() { session.sessionPreset = AVCaptureSession.Preset.photo @@ -169,7 +180,7 @@ class CaptureManager: NSObject { // MARK: - Private methods fileprivate func accessDeniedError(_ code: Int = FinjinonCameraAccessErrorDeniedCode) -> NSError { - let info = [NSLocalizedDescriptionKey: NSLocalizedString("Camera access denied, please enable it in the Settings app to continue", comment: "")] + let info = [NSLocalizedDescriptionKey: "finjinon.cameraAccessDenied".localized()] return NSError(domain: FinjinonCameraAccessErrorDomain, code: code, userInfo: info) } @@ -218,6 +229,12 @@ class CaptureManager: NSObject { self.session.addOutput(self.stillImageOutput!) } + let videoOutput = self.makeVideoDataOutput() + + if self.session.canAddOutput(videoOutput) { + self.session.addOutput(videoOutput) + } + if let cameraDevice = self.cameraDevice { if cameraDevice.isFocusModeSupported(.continuousAutoFocus) { do { @@ -251,4 +268,34 @@ class CaptureManager: NSObject { return AVCaptureDevice.default(for: AVMediaType.video) } + + private func makeVideoDataOutput() -> AVCaptureVideoDataOutput { + let output = AVCaptureVideoDataOutput() + output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] + output.alwaysDiscardsLateVideoFrames = true + output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "no.finn.finjinon-sample-buffer")) + return output + } +} + +// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate + +extension CaptureManager: AVCaptureVideoDataOutputSampleBufferDelegate { + func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { + let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer) + let fps: Int32 = 1 // Create pixel buffer and call the delegate 1 time per second + + guard (time - lastVideoCaptureTime) >= CMTime.init(value: 1, timescale: fps) else { + return + } + + lastVideoCaptureTime = time + + if let lightningCondition = lowLightService.getLightningCondition(from: sampleBuffer) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.captureManager(self, didDetectLightingCondition: lightningCondition) + } + } + } } diff --git a/Sources/LowLight/LightingCondition.swift b/Sources/LowLight/LightingCondition.swift new file mode 100644 index 0000000..e9515e1 --- /dev/null +++ b/Sources/LowLight/LightingCondition.swift @@ -0,0 +1,20 @@ +// +// Copyright (c) 2019 FINN.no AS. All rights reserved. +// + +enum LightingCondition: String { + case low + case normal + case high + + init(value: Double) { + switch Int(round(value)) { + case Int.min..<3: + self = .low + case 14...Int.max: + self = .high + default: + self = .normal + } + } +} diff --git a/Sources/LowLight/LowLightService.swift b/Sources/LowLight/LowLightService.swift new file mode 100644 index 0000000..804cd59 --- /dev/null +++ b/Sources/LowLight/LowLightService.swift @@ -0,0 +1,61 @@ +// +// Copyright (c) 2019 FINN.no AS. All rights reserved. +// + +import CoreMedia + +final class LowLightService { + private let maxNumberOfResults = 3 + private var results = [LightingCondition]() + + func getLightningCondition(from sampleBuffer: CMSampleBuffer) -> LightingCondition? { + let rawMetadata = CMCopyDictionaryOfAttachments( + allocator: nil, + target: sampleBuffer, + attachmentMode: CMAttachmentMode(kCMAttachmentMode_ShouldPropagate) + ) + + do { + let metadata = CFDictionaryCreateCopy(nil, rawMetadata) as NSDictionary + let exifData: NSDictionary = try metadata.value(forKey: "{Exif}") + let fNumber: Double = try exifData.value(forKey: kCGImagePropertyExifFNumber) + let exposureTime: Double = try exifData.value(forKey: kCGImagePropertyExifExposureTime) + let isoSpeedRatings: NSArray = try exifData.value(forKey: kCGImagePropertyExifISOSpeedRatings) + + guard let isoSpeedRating = isoSpeedRatings[0] as? Double else { + throw MetatataError() + } + + let explosureValue = log2((100 * fNumber * fNumber) / (exposureTime * isoSpeedRating)) + let lightningCondition = LightingCondition(value: explosureValue) + + results.append(lightningCondition) + + if results.count == maxNumberOfResults + 1 { + results = Array(results.dropFirst()) + } + + return results.count > 1 && Set(results).count == 1 ? lightningCondition : nil + } catch { + return nil + } + } +} + +// MARK: - Private types + +private extension NSDictionary { + func value(forKey key: CFString) throws -> T { + return try value(forKey: key as String) + } + + func value(forKey key: String) throws -> T { + guard let value = self[key] as? T else { + throw MetatataError() + } + + return value + } +} + +private struct MetatataError: Error {} diff --git a/Sources/LowLight/LowLightView.swift b/Sources/LowLight/LowLightView.swift new file mode 100644 index 0000000..db40f07 --- /dev/null +++ b/Sources/LowLight/LowLightView.swift @@ -0,0 +1,65 @@ +// +// Copyright (c) 2019 FINN.no AS. All rights reserved. +// + +import UIKit + +final class LowLightView: UIView { + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.image = UIImage( + named: "LowLightIcon", + in: Bundle(for: LowLightView.self), + compatibleWith: nil + ) + return imageView + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .white + label.numberOfLines = 0 + return label + }() + + var text: String? { + get { return textLabel.text } + set { textLabel.text = newValue } + } + + // MARK: - Init + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + // MARK: - Setup + + private func setup() { + backgroundColor = UIColor.black.withAlphaComponent(0.8) + layer.cornerRadius = 8 + + addSubview(iconImageView) + addSubview(textLabel) + + let spacing: CGFloat = 8 + + NSLayoutConstraint.activate([ + iconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: spacing), + iconImageView.centerYAnchor.constraint(equalTo: centerYAnchor), + + textLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: spacing), + textLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -spacing), + textLabel.topAnchor.constraint(equalTo: topAnchor, constant: spacing), + textLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -spacing) + ]) + } +} diff --git a/Sources/PhotoCaptureViewController.swift b/Sources/PhotoCaptureViewController.swift index b66b2cb..6a036ae 100644 --- a/Sources/PhotoCaptureViewController.swift +++ b/Sources/PhotoCaptureViewController.swift @@ -31,8 +31,8 @@ public protocol PhotoCaptureViewControllerDelegate: NSObjectProtocol { open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayoutDelegate { open weak var delegate: PhotoCaptureViewControllerDelegate? - /// Optional instance confirming to the ImagePickerAdapter-protocol to allow selecting an image from the library. - /// The default implementation will present a UIImagePickerController. Setting this to nil, will remove the library-button. + /// Optional instance confirming to the ImagePickerAdapter-protocol to allow selecting an image from the library. + /// The default implementation will present a UIImagePickerController. Setting this to nil, will remove the library-button. open var imagePickerAdapter: ImagePickerAdapter? = ImagePickerControllerAdapter() { didSet { updateImagePickerButton() @@ -56,6 +56,13 @@ open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayo fileprivate let buttonMargin: CGFloat = 12 fileprivate var orientation: UIDeviceOrientation = .portrait + private lazy var lowLightView: LowLightView = { + let view = LowLightView() + view.translatesAutoresizingMaskIntoConstraints = false + view.isHidden = true + return view + }() + private var viewFrame = CGRect.zero private var viewBounds = CGRect.zero private var subviewSetupDone = false @@ -137,7 +144,7 @@ open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayo let icon = UIImage(named: "LightningIcon", in: Bundle(for: PhotoCaptureViewController.self), compatibleWith: nil) flashButton.setImage(icon, for: .normal) - flashButton.setTitle(NSLocalizedString("Off", comment: "flash off"), for: .normal) + flashButton.setTitle("finjinon.off".localized(), for: .normal) flashButton.addTarget(self, action: #selector(flashButtonTapped(_:)), for: .touchUpInside) flashButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .footnote) flashButton.tintColor = UIColor.white @@ -190,15 +197,22 @@ open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayo captureButton.addTarget(self, action: #selector(capturePhotoTapped(_:)), for: .touchUpInside) containerView.addSubview(captureButton) captureButton.isEnabled = false - captureButton.accessibilityLabel = NSLocalizedString("Take a picture", comment: "") + captureButton.accessibilityLabel = "finjinon.captureButton".localized() closeButton.frame = CGRect(x: captureButton.frame.maxX, y: captureButton.frame.midY - 22, width: viewBounds.width - captureButton.frame.maxX, height: 44) closeButton.addTarget(self, action: #selector(doneButtonTapped(_:)), for: .touchUpInside) - closeButton.setTitle(NSLocalizedString("Done", comment: ""), for: .normal) + closeButton.setTitle("finjinon.done".localized(), for: .normal) closeButton.tintColor = UIColor.white closeButton.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) containerView.addSubview(closeButton) + view.addSubview(lowLightView) + NSLayoutConstraint.activate([ + lowLightView.bottomAnchor.constraint(equalTo: collectionView.topAnchor, constant: -16), + lowLightView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + lowLightView.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, multiplier: 0.8) + ]) + updateImagePickerButton() previewView.alpha = 0.0 @@ -217,6 +231,8 @@ open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayo self.previewView.alpha = 1.0 }) } + + captureManager.delegate = self } private func updateImagePickerButton() { @@ -231,7 +247,7 @@ open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayo if pickerButton == nil { pickerButton = UIButton(frame: buttonRect) - pickerButton!.setTitle(NSLocalizedString("Photos", comment: "Select from Photos button title"), for: .normal) + pickerButton!.setTitle("finjinon.photos".localized(), for: .normal) let icon = UIImage(named: "PhotosIcon", in: Bundle(for: PhotoCaptureViewController.self), compatibleWith: nil) pickerButton!.setImage(icon, for: .normal) pickerButton!.addTarget(self, action: #selector(presentImagePickerTapped(_:)), for: .touchUpInside) @@ -356,11 +372,11 @@ open class PhotoCaptureViewController: UIViewController, PhotoCollectionViewLayo captureManager.changeFlashMode(mode) { switch mode { case .off: - self.flashButton.setTitle(NSLocalizedString("Off", comment: "flash off"), for: .normal) + self.flashButton.setTitle("finjinon.off".localized(), for: .normal) case .on: - self.flashButton.setTitle(NSLocalizedString("On", comment: "flash on"), for: .normal) + self.flashButton.setTitle("finjinon.on".localized(), for: .normal) case .auto: - self.flashButton.setTitle(NSLocalizedString("Auto", comment: "flash Auto"), for: .normal) + self.flashButton.setTitle("finjinon.auto".localized(), for: .normal) } } } @@ -568,6 +584,18 @@ extension PhotoCaptureViewController: UICollectionViewDelegate { } } +extension PhotoCaptureViewController: CaptureManagerDelegate { + func captureManager(_ manager: CaptureManager, didDetectLightingCondition lightingCondition: LightingCondition) { + if lightingCondition == .low { + lowLightView.text = "finjinon.lowLightMessage".localized() + lowLightView.isHidden = false + } else { + lowLightView.text = nil + lowLightView.isHidden = true + } + } +} + extension UIView { public func rotateToCurrentDeviceOrientation() { rotateToDeviceOrientation(UIDevice.current.orientation) @@ -586,4 +614,3 @@ extension UIView { } } } - diff --git a/Sources/Resources/Assets.xcassets/Contents.json b/Sources/Resources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Sources/Resources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sources/Resources/Assets.xcassets/LowLightIcon.imageset/Contents.json b/Sources/Resources/Assets.xcassets/LowLightIcon.imageset/Contents.json new file mode 100644 index 0000000..b235823 --- /dev/null +++ b/Sources/Resources/Assets.xcassets/LowLightIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LowLightIcon.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sources/Resources/Assets.xcassets/LowLightIcon.imageset/LowLightIcon.pdf b/Sources/Resources/Assets.xcassets/LowLightIcon.imageset/LowLightIcon.pdf new file mode 100644 index 0000000..1a06bdf Binary files /dev/null and b/Sources/Resources/Assets.xcassets/LowLightIcon.imageset/LowLightIcon.pdf differ diff --git a/Sources/Resources/en.lproj/Localizable.strings b/Sources/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..47704f5 --- /dev/null +++ b/Sources/Resources/en.lproj/Localizable.strings @@ -0,0 +1,14 @@ +/* + Localizable.strings + Finjinon + Copyright © 2019 FINN.no. All rights reserved. +*/ + +"finjinon.cameraAccessDenied" = "Camera access denied, please enable it in the Settings app to continue"; +"finjinon.done" = "Done"; +"finjinon.photos" = "Photos"; +"finjinon.on" = "On"; +"finjinon.off" = "Off"; +"finjinon.auto" = "Auto"; +"finjinon.captureButton" = "Take a picture"; +"finjinon.lowLightMessage" = "Your image seems a bit too dark.\nTurn on the light or go outside."; diff --git a/Sources/Resources/nb.lproj/Localizable.strings b/Sources/Resources/nb.lproj/Localizable.strings new file mode 100644 index 0000000..924e623 --- /dev/null +++ b/Sources/Resources/nb.lproj/Localizable.strings @@ -0,0 +1,14 @@ +/* + Localizable.strings + Finjinon + Copyright © 2019 FINN.no. All rights reserved. +*/ + +"finjinon.cameraAccessDenied" = "Denne appen har ikke tilgang til ditt kamera. Du kan gi appen tilgang i enhetens personvern instillinger"; +"finjinon.done" = "Ferdig"; +"finjinon.photos" = "Bilder"; +"finjinon.on" = "På"; +"finjinon.off" = "Av"; +"finjinon.auto" = "Auto"; +"finjinon.captureButton" = "Ta et bilde"; +"finjinon.lowLightMessage" = "Your image seems a bit too dark.\nTurn on the light or go outside."; diff --git a/Sources/StringExtensions.swift b/Sources/StringExtensions.swift new file mode 100644 index 0000000..cd8944e --- /dev/null +++ b/Sources/StringExtensions.swift @@ -0,0 +1,12 @@ +// +// Copyright (c) 2019 FINN.no AS. All rights reserved. +// + +import Foundation + +extension String { + func localized() -> String { + let bundle = Bundle(for: PhotoCaptureViewController.self) + return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "") + } +}