Swift(스위프트): 카메라 및 사진 라이브러리 권한 물어보기 + UIImagePickerController를 이용한 사진 추가 (스토리보드)
기본적으로 아래 글과 동일한 내용이나 사용성이 개선되었습니다.
코드 목록
코드에 대한 설명은 밑에 있습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import Photos | |
// MARK: – 사진 권한 물어보기 | |
private func photoAuth(isCamera: Bool, viewController: UIViewController, completion: @escaping () -> ()) { | |
// 경고 메시지 작성 | |
let sourceName = isCamera ? "카메라" : "사진 라이브러리" | |
let notDeterminedAlertTitle = "No Permission Status" | |
let notDeterminedMsg = "\(sourceName)의 권한 설정을 변경하시겠습니까?" | |
let restrictedMsg = "시스템에 의해 거부되었습니다." | |
let deniedAlertTitle = "Permission Denied" | |
let deniedMsg = "\(sourceName)의 사용 권한이 거부되었기 때문에 사용할 수 없습니다. \(sourceName)의 권한 설정을 변경하시겠습니까?" | |
let unknownMsg = "unknown" | |
// 카메라인 경우와 사진 라이브러리인 경우를 구분해서 권한 status의 원시값(Int)을 저장 | |
let status: Int = isCamera | |
? AVCaptureDevice.authorizationStatus(for: AVMediaType.video).rawValue | |
: PHPhotoLibrary.authorizationStatus().rawValue | |
// PHAuthorizationStatus, AVAuthorizationStatus의 status의 원시값은 공유되므로 같은 switch문에서 사용 | |
switch status { | |
case 0: | |
// .notDetermined – 사용자가 아직 권한에 대한 설정을 하지 않았을 때 | |
simpleDestructiveYesAndNo(viewController, message: notDeterminedMsg, title: notDeterminedAlertTitle, yesHandler: openSettings) | |
print("CALLBACK FAILED: \(sourceName) is .notDetermined") | |
case 1: | |
// .restricted – 시스템에 의해 앨범에 접근 불가능하고, 권한 변경이 불가능한 상태 | |
simpleAlert(viewController, message: restrictedMsg) | |
print("CALLBACK FAILED: \(sourceName) is .restricted") | |
case 2: | |
// .denied – 접근이 거부된 경우 | |
simpleDestructiveYesAndNo(viewController, message: deniedMsg, title: deniedAlertTitle, yesHandler: openSettings) | |
print("CALLBACK FAILED: \(sourceName) is .denied") | |
case 3: | |
// .authorized – 권한 허용된 상태 | |
print("CALLBACK SUCCESS: \(sourceName) is .authorized") | |
completion() | |
case 4: | |
// .limited (iOS 14 이상 사진 라이브러리 전용) – 갤러리의 접근이 선택한 사진만 허용된 경우 | |
print("CALLBACK SUCCESS: \(sourceName) is .limited") | |
completion() | |
default: | |
// 그 외의 경우 – 미래에 새로운 권한 추가에 대비 | |
simpleAlert(viewController, message: unknownMsg) | |
print("CALLBACK FAILED: \(sourceName) is unknwon state.") | |
} | |
} | |
// MARK: – 설정 앱 열기 | |
private func openSettings(action: UIAlertAction) -> Void { | |
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { | |
return | |
} | |
if UIApplication.shared.canOpenURL(settingsUrl) { | |
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in | |
print("Settings opened: \(success)") // Prints true | |
}) | |
} | |
} | |
// MARK: – photoAuth 함수를 main 스레드에서 실행 (UI 관련 문제 방지) | |
private func photoAuthInMainAsync(isCamera: Bool, viewController: UIViewController, completion: @escaping () -> ()) { | |
DispatchQueue.main.async { | |
photoAuth(isCamera: isCamera, viewController: viewController, completion: completion) | |
} | |
} | |
// MARK: – 사진 라이브러리의 권한을 묻고, 이후 () -> () 클로저를 실행하는 함수 | |
func authPhotoLibrary(_ viewController: UIViewController, completion: @escaping () -> ()) { | |
if #available(iOS 14, *) { | |
// iOS 14의 경우 사진 라이브러리를 읽기전용 또는 쓰기가능 형태로 설정해야 함 | |
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in | |
photoAuthInMainAsync(isCamera: false, viewController: viewController, completion: completion) | |
} | |
} else { | |
// Fallback on earlier versions | |
PHPhotoLibrary.requestAuthorization { status in | |
photoAuthInMainAsync(isCamera: false, viewController: viewController, completion: completion) | |
} | |
} | |
} | |
// MARK: – 카메라의 권한을 묻고, 이후 () -> () 클로저를 실행하는 함수 | |
func authDeviceCamera(_ viewController: UIViewController, completion: @escaping () -> ()) { | |
let notAvailableMsg = "카메라를 사용할 수 없습니다." | |
if UIImagePickerController.isSourceTypeAvailable(.camera) { | |
AVCaptureDevice.requestAccess(for: .video) { status in | |
photoAuthInMainAsync(isCamera: true, viewController: viewController, completion: completion) | |
} | |
} else { | |
// 시뮬레이터 등에서 카메라를 사용할 수 없는 경우 | |
DispatchQueue.main.async { | |
simpleAlert(viewController, message: notAvailableMsg) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class PhotoViewController: UIViewController { | |
@IBOutlet weak var imgView: UIImageView! | |
// 1) 이미지 피커 컨트롤러 추가 | |
let imagePickerController = UIImagePickerController() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// 2) 이미지 피커에 딜리게이트 생성 | |
imagePickerController.delegate = self | |
} | |
@IBAction func btnActCamera(_ sender: UIButton) { | |
// 5-1) 권한 관련 작업 후 콜백 함수 실행(카메라) | |
authDeviceCamera(self) { | |
self.imagePickerController.sourceType = .camera | |
self.present(self.imagePickerController, animated: true, completion: nil) | |
} | |
} | |
@IBAction func btnActPhotoLibrary(_ sender: UIButton) { | |
// 5-2) 권한 관련 작업 후 콜백 함수 실행(사진 라이브러리) | |
authPhotoLibrary(self) { | |
// .photoLibrary – Deprecated: Use PHPickerViewController instead. (iOS 14 버전 이상 지원) | |
self.imagePickerController.sourceType = .photoLibrary | |
self.present(self.imagePickerController, animated: true, completion: nil) | |
} | |
} | |
} | |
// 3) 이미지 피커 관련 프로토콜을 클래스 상속 목록에 추가 | |
extension PhotoViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { | |
// 4) 카메라 또는 사진 라이브러리에서 사진을 찍거나 선택했을 경우 실행할 내용 작성 | |
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { | |
if let image = info[.originalImage] as? UIImage { | |
imgView.image = image | |
} | |
dismiss(animated: true, completion: nil) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
func simpleAlert(_ controller: UIViewController, message: String) { | |
let alertController = UIAlertController(title: "Caution", message: message, preferredStyle: .alert) | |
let alertAction = UIAlertAction(title: "OK", style: .default, handler: nil) | |
alertController.addAction(alertAction) | |
controller.present(alertController, animated: true, completion: nil) | |
} | |
func simpleAlert(_ controller: UIViewController, message: String, title: String, handler: ((UIAlertAction) -> Void)?) { | |
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) | |
let alertAction = UIAlertAction(title: "OK", style: .default, handler: handler) | |
alertController.addAction(alertAction) | |
controller.present(alertController, animated: true, completion: nil) | |
} | |
func simpleDestructiveYesAndNo(_ controller: UIViewController, message: String, title: String, yesHandler: ((UIAlertAction) -> Void)?) { | |
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) | |
let alertActionNo = UIAlertAction(title: "No", style: .cancel, handler: nil) | |
let alertActionYes = UIAlertAction(title: "Yes", style: .destructive, handler: yesHandler) | |
alertController.addAction(alertActionNo) | |
alertController.addAction(alertActionYes) | |
controller.present(alertController, animated: true, completion: nil) | |
} | |
func simpleYesAndNo(_ controller: UIViewController, message: String, title: String, yesHandler: ((UIAlertAction) -> Void)?) { | |
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) | |
let alertActionNo = UIAlertAction(title: "No", style: .cancel, handler: nil) | |
let alertActionYes = UIAlertAction(title: "Yes", style: .default, handler: yesHandler) | |
alertController.addAction(alertActionNo) | |
alertController.addAction(alertActionYes) | |
controller.present(alertController, animated: true, completion: nil) | |
} |
https://gist.github.com/ayaysir/c1ba58ea822ebd80ca6eb39262e52efb#file-photoauth-swift
절차
0. info.plist에서 권한 허용을 묻는 메시지를 설정합니다.
1. 위의 코드 중 PhotoAuth.swift 파일을 프로젝트에 추가합니다.
- 이 파일을 추가하면 권한 설정을 자동으로 처리하고 클로저의 내용을 실행하는 아래 함수를 이용할 수 있습니다.
func authPhotoLibrary(_ viewController: UIViewController, completion: @escaping () -> ())
func authDeviceCamera(_ viewController: UIViewController, completion: @escaping () -> ())
simpleAlert(...)
함수는 경고창을 실행하는 함수입니다. 필요한 경우 추가해서 사용하면 됩니다.- 권한 여부에 따른 메시지를 따로 작성할 수 있도록 변수로 빼놨습니다.
2. 스토리보드에서 UI를 생성하고, 컨트롤러 파일과 연결합니다.
3. 위의 코드 목록 중 PhotoViewController.swift 파일을 참고하여 사진 선택 기능을 구현합니다.
- 이미지 피커 컨트롤러(
UIImagePickerController
)를 멤버 변수 목록에 추가합니다.- 참고로 사진 라이브러리에서 이미지 피커 컨트롤러는 deprecated 되고, 애플은 iOS 14이상부터는
PHPickerViewController
사용을 권장하고 있지만 일단 iOS 15에서도 이미지 피커는 사용 가능합니다. - 이미지 피커와 비교하면,
PHPickerViewController
는 복수 사진 선택 가능(이미지 피커는 한 개만 가능), 일부의 사진에만 권한 부여 가능(이미지 피커는 이 부분이 정상 작동하지 않습니다), 사진 선택시 편의 기능 추가 등의 차이가 있습니다.
- 참고로 사진 라이브러리에서 이미지 피커 컨트롤러는 deprecated 되고, 애플은 iOS 14이상부터는
viewDidLoad()
에서 이미지 피커에 딜리게이트(delegate
)를 할당합니다.- 이미지 피커를 사용하기 위해
UIImagePickerControllerDelegate
,UINavigationControllerDelegate
를 구현하는extension
을 추가합니다. - 카메라 또는 사진 라이브러리에서 사진을 찍거나 선택했을 경우 실행할 내용을
func imagePickerController(.....didFinishPickingMediaWithInfo:.....)
함수 내에 구현합니다.- 사진이 선택된 경우
info[.originalImage] as? UIImage
에 이미지가 담기게 되며, 이 이미지를imgView.image
에 할당하면 앱 화면에 선택한 이미지가 표시됩니다.
- 사진이 선택된 경우
- 버튼을 누르면 실행되는 함수에
authDeviceCamera(...)
또는authPhotoLibrary(...)
를 추가합니다.- 이 함수를 추가하면 권한 관련 작업을 먼저 실행하고, 권한 정보가 없거나 거부되었다면 그에 맞는 작업을 우선 실행합니다.
- 권한이 허용되었다면 클로저 함수를 실행합니다.
실행 화면
0개의 댓글