Skip to content
This repository has been archived by the owner on Jul 1, 2022. It is now read-only.

Feature/pinch to zoom camera #214

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion ALCameraViewController.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@
attributes = {
LastSwiftMigration = 0700;
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0800;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = zero;
TargetAttributes = {
C4829FFA1CAEB16C00541D08 = {
Expand Down Expand Up @@ -438,14 +438,20 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
Expand Down Expand Up @@ -486,14 +492,20 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
Expand Down
4 changes: 2 additions & 2 deletions ALCameraViewController/Utilities/VolumeControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ public class VolumeControl {

var onVolumeChange: VolumeChangeAction?

init(view: UIView, onVolumeChange: VolumeChangeAction?) {
init(view: UIView, enableAudio: Bool = true, onVolumeChange: VolumeChangeAction?) {
self.onVolumeChange = onVolumeChange
view.addSubview(volumeView)
view.sendSubview(toBack: volumeView)

try? AVAudioSession.sharedInstance().setActive(true)
try? AVAudioSession.sharedInstance().setActive(enableAudio)
NotificationCenter.default.addObserver(self, selector: #selector(volumeChanged), name: NSNotification.Name(rawValue: changeKey), object: nil)
}

Expand Down
10 changes: 7 additions & 3 deletions ALCameraViewController/ViewController/CameraViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ open class CameraViewController: UIViewController {

var didUpdateViews = false
var allowCropping = false
var allowAudio = false
var animationRunning = false

var lastInterfaceOrientation : UIInterfaceOrientation?
Expand Down Expand Up @@ -158,11 +159,12 @@ open class CameraViewController: UIViewController {

private let allowsLibraryAccess: Bool

public init(croppingEnabled: Bool, allowsLibraryAccess: Bool = true, allowsSwapCameraOrientation: Bool = true, completion: @escaping CameraViewCompletion) {
public init(croppingEnabled: Bool, allowsLibraryAccess: Bool = true, allowsSwapCameraOrientation: Bool = true, allowsAudio: Bool = true, completion: @escaping CameraViewCompletion) {
self.allowsLibraryAccess = allowsLibraryAccess
super.init(nibName: nil, bundle: nil)
onCompletion = completion
allowCropping = croppingEnabled
allowAudio = allowsAudio
cameraOverlay.isHidden = !allowCropping
libraryButton.isEnabled = allowsLibraryAccess
libraryButton.isHidden = !allowsLibraryAccess
Expand Down Expand Up @@ -276,7 +278,9 @@ open class CameraViewController: UIViewController {
cameraView.startSession()
addCameraObserver()
addRotateObserver()
setupVolumeControl()
if(allowAudio) {
setupVolumeControl()
}
}

/**
Expand Down Expand Up @@ -349,7 +353,7 @@ open class CameraViewController: UIViewController {
* Attach the take of picture for any volume button.
*/
private func setupVolumeControl() {
volumeControl = VolumeControl(view: view) { [weak self] _ in
volumeControl = VolumeControl(view: view, enableAudio: allowAudio) { [weak self] _ in
guard let enabled = self?.cameraButton.isEnabled, enabled else {
return
}
Expand Down
49 changes: 24 additions & 25 deletions ALCameraViewController/ViewController/ConfirmViewController.xib
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13189.4" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13165.3"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ConfirmViewController" customModule="ALCameraViewController" customModuleProvider="target">
Expand All @@ -17,58 +22,52 @@
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oUR-U3-uEM">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<variation key="heightClass=compact" ambiguous="YES">
<rect key="frame" x="0.0" y="0.0" width="600" height="400"/>
</variation>
<variation key="heightClass=compact-widthClass=regular" ambiguous="YES">
<rect key="frame" x="0.0" y="0.0" width="800" height="400"/>
</variation>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
</scrollView>
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ASf-ZD-cIs">
<rect key="frame" x="224" y="526" width="44" height="44"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ASf-ZD-cIs">
<rect key="frame" x="101.5" y="588" width="64" height="64"/>
<constraints>
<constraint firstAttribute="height" constant="64" id="J7n-mn-Ebe"/>
<constraint firstAttribute="width" constant="64" id="YWt-e6-Bvy"/>
</constraints>
<state key="normal" image="confirmButton"/>
</button>
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yRi-ES-LfN">
<rect key="frame" x="328" y="526" width="44" height="44"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yRi-ES-LfN">
<rect key="frame" x="225.5" y="588" width="64" height="64"/>
<constraints>
<constraint firstAttribute="height" constant="64" id="FdJ-mW-Tx6"/>
<constraint firstAttribute="width" constant="64" id="urS-JS-i1S"/>
</constraints>
<state key="normal" image="retakeButton"/>
</button>
<view hidden="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" ambiguous="YES" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KYd-D9-K5d">
<rect key="frame" x="0.0" y="0.0" width="240" height="128"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<view hidden="YES" userInteractionEnabled="NO" alpha="0.0" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KYd-D9-K5d">
<rect key="frame" x="0.0" y="0.0" width="375" height="573"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view contentMode="scaleToFill" ambiguous="YES" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lnA-tb-Sap" customClass="CropOverlay" customModule="ALCameraViewController" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="240" height="128"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lnA-tb-Sap" customClass="CropOverlay" customModule="ALCameraViewController" customModuleProvider="target">
<rect key="frame" x="15" y="114" width="345" height="345"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" secondItem="lnA-tb-Sap" secondAttribute="height" multiplier="1:1" id="lv8-l9-lJq"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="lnA-tb-Sap" firstAttribute="centerY" secondItem="KYd-D9-K5d" secondAttribute="centerY" id="1uu-Bd-bz3"/>
<constraint firstAttribute="trailing" secondItem="oUR-U3-uEM" secondAttribute="trailing" id="7A6-HH-MEu"/>
<constraint firstAttribute="bottom" secondItem="yRi-ES-LfN" secondAttribute="bottom" constant="30" id="7jo-lC-34t"/>
<constraint firstAttribute="bottom" secondItem="yRi-ES-LfN" secondAttribute="bottom" constant="15" id="7jo-lC-34t"/>
<constraint firstItem="ASf-ZD-cIs" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" constant="-45" id="89L-rE-rmd"/>
<constraint firstAttribute="bottom" secondItem="oUR-U3-uEM" secondAttribute="bottom" id="8mU-82-Hf6"/>
<constraint firstItem="oUR-U3-uEM" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="92s-g3-wfK"/>
<constraint firstItem="KYd-D9-K5d" firstAttribute="leading" secondItem="yRi-ES-LfN" secondAttribute="trailing" id="CcA-mG-BLU"/>
<constraint firstItem="yRi-ES-LfN" firstAttribute="top" secondItem="KYd-D9-K5d" secondAttribute="bottom" id="Ck2-xk-HbG"/>
<constraint firstItem="yRi-ES-LfN" firstAttribute="top" secondItem="KYd-D9-K5d" secondAttribute="bottom" constant="15" id="Ck2-xk-HbG"/>
<constraint firstItem="oUR-U3-uEM" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Cuy-cw-jOk"/>
<constraint firstAttribute="bottom" secondItem="ASf-ZD-cIs" secondAttribute="bottom" constant="30" id="DZ5-WA-3ZQ"/>
<constraint firstAttribute="bottom" secondItem="ASf-ZD-cIs" secondAttribute="bottom" constant="15" id="DZ5-WA-3ZQ"/>
<constraint firstItem="lnA-tb-Sap" firstAttribute="top" relation="greaterThanOrEqual" secondItem="oUR-U3-uEM" secondAttribute="top" constant="100" id="GAL-ck-d0B"/>
<constraint firstAttribute="bottom" secondItem="yRi-ES-LfN" secondAttribute="bottom" constant="60" id="H4a-3r-r5r"/>
<constraint firstItem="oUR-U3-uEM" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="J3N-Vr-euu"/>
Expand Down
40 changes: 39 additions & 1 deletion ALCameraViewController/Views/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public class CameraView: UIView {

let focusView = CropOverlay(frame: CGRect(x: 0, y: 0, width: 80, height: 80))

let minimumZoom: CGFloat = 1.0
let maximumZoom: CGFloat = 3.0
var lastZoomFactor: CGFloat = 1.0

public var currentPosition = CameraGlobals.shared.defaultCameraPosition

public func startSession() {
Expand Down Expand Up @@ -99,6 +103,9 @@ public class CameraView: UIView {
lines.forEach { line in
line.alpha = 0
}

let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:)))
addGestureRecognizer(pinchGesture)
}

internal func focus(gesture: UITapGestureRecognizer) {
Expand Down Expand Up @@ -135,10 +142,41 @@ public class CameraView: UIView {
})
}

internal func pinch(gesture: UIPinchGestureRecognizer) {
guard let device = device 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)")
}
}

let newScaleFactor = minMaxZoom(gesture.scale * lastZoomFactor)

switch gesture.state {
case .began: fallthrough
case .changed: update(scale: newScaleFactor)
case .ended:
lastZoomFactor = minMaxZoom(newScaleFactor)
update(scale: lastZoomFactor)
default: break
}
}

private func createPreview() {

preview = AVCaptureVideoPreviewLayer(session: session)
preview.videoGravity = AVLayerVideoGravityResizeAspectFill
preview.videoGravity = AVLayerVideoGravityResizeAspect

preview.frame = bounds

layer.addSublayer(preview)
Expand Down
2 changes: 1 addition & 1 deletion Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ViewController: UIViewController {
}

@IBAction func openCamera(_ sender: AnyObject) {
let cameraViewController = CameraViewController(croppingEnabled: croppingEnabled, allowsLibraryAccess: libraryEnabled) { [weak self] image, asset in
let cameraViewController = CameraViewController(croppingEnabled: croppingEnabled, allowsLibraryAccess: libraryEnabled, allowsAudio: false) { [weak self] image, asset in
self?.imageView.image = image
self?.dismiss(animated: true, completion: nil)
}
Expand Down
Loading