관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 사진 선택 권한 (limited, presentLimitedLibraryPicker, Photos, PhotosUI, select photos) 본문

iOS 응용 (swift)

[iOS - swift] 사진 선택 권한 (limited, presentLimitedLibraryPicker, Photos, PhotosUI, select photos)

jake-kim 2023. 3. 24. 02:32

사진 선택 > 사진을 더 추가할 수 있는 버튼 노출

사진 선택 권한

  • iOS 14+
  • 사진 선택 권한을 사용자가 선태하면 limited 상태로 저장

  • 사진 권한 message 문구를 위해 info.plist에 아래 내용 추가
    • 실제 value에는 구체적으로 적어야 리젝 x

  • 보통 사진 권한은 아래처럼 요청
    • 파라미터에는 .readWrite와 .addOnly가 있는데 addOnly는 삭제가 불가능한 접근 권한이고, readWrite는 삭제가 가능한 접근 권한
private var status: PHAuthorizationStatus {
    PHPhotoLibrary.authorizationStatus(for: .readWrite)
}
  • status의 종류는 5가지
    • limited는 사용자가 "사진 선택"을 누르고 사진을 선택하고나서 done 버튼을 누른 이후부터 limited 상태로 변경
private func printStatus() {
    switch status {
    case .restricted:
        print("restricted")
    case .denied:
        print("denied")
    case .authorized:
        print("authorized")
    case .limited:
        print("limited")
    case .notDetermined:
        print("notDetermined")
    @unknown default:
        print("@unknown")
    }
}
  • 예제에 사용할 UI 준비
    • photoButton - 사진 권한 요청
    • presentMorePhotoButton - limited 상태가 될때, 사용자에게 limited 상태이니, 사진을 더 고를 수 있도록 버튼 제공
    • settingButton - limited 상태가 될때, 사용자에게 limited 상태이니, 아예 모든 사진을 허용할 수 있도록 하는 버튼
import UIKit
import Photos
import PhotosUI

class ViewController: UIViewController {
    private let photoButton: UIButton = {
        let button = UIButton()
        button.setTitle("사진", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.setTitleColor(.blue, for: .highlighted)
        button.addTarget(self, action: #selector(tap), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    private let presentMorePhotoButton: UIButton = {
        let button = UIButton()
        button.setTitle("사진 더 추가하기(현재 limited 상태)", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.setTitleColor(.blue, for: .highlighted)
        button.addTarget(self, action: #selector(tapMorePhoto), for: .touchUpInside)
        button.isHidden = true
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    private let settingButton: UIButton = {
        let button = UIButton()
        button.setTitle("모든 사진 허용 - 설정 이동(현재 limited 상태)", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.setTitleColor(.blue, for: .highlighted)
        button.addTarget(self, action: #selector(tapGoSetting), for: .touchUpInside)
        button.isHidden = true
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.addSubview(photoButton)
        view.addSubview(presentMorePhotoButton)
        view.addSubview(settingButton)
        
        NSLayoutConstraint.activate([
            photoButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            photoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ])
        NSLayoutConstraint.activate([
            presentMorePhotoButton.topAnchor.constraint(equalTo: photoButton.bottomAnchor, constant: 16),
            presentMorePhotoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ])
        NSLayoutConstraint.activate([
            settingButton.topAnchor.constraint(equalTo: presentMorePhotoButton.bottomAnchor, constant: 16),
            settingButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        ])
    }
    
...
  • 공통으로 여러곳에서 사용되는 프로퍼티는 computed property로 두고 각 버튼의 탭 이벤트 정의
    • tap() 메소드가 핵심 - 권한을 요청한 후에 사용자가 사진 선택 권한을 선택하고, 위로 올라온 사진첩에서 특정 사진을 선택 후 done 버튼을 누를때 아래 클로저가 실행
    • delegate를 설정하여, 앞으로도 앨범에서 사진을 선택한 후 done 버튼을 눌렀을때 이벤트를 캐치하기 위함
      (보통 사진이 추가되면 PHAssets같은 것으로 새로운 이미지를 로드하여 사용해야하기 때문에 델리게이트 필요)
private var status: PHAuthorizationStatus {
    PHPhotoLibrary.authorizationStatus(for: .readWrite)
}
private var isLimited: Bool {
    status == .limited
}
    
@objc private func tap() {
    printStatus()
    guard status == .notDetermined else { return }
    PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
        self.printStatus()
        
        DispatchQueue.main.async {
            self.updateButtonAssociatedLimitedStatus()
        }
        
        if self.isLimited {
            PHPhotoLibrary.shared().register(self)
        }
    }
}

@objc private func tapMorePhoto() {
    PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self)
}

@objc private func tapGoSetting() {
    guard
        let url = URL(string: UIApplication.openSettingsURLString),
        UIApplication.shared.canOpenURL(url)
    else { return }
        
    UIApplication.shared.open(url, completionHandler: { (success) in
        print("finished")
    })
}

private func printStatus() {
    switch status {
    case .restricted:
        print("restricted")
    case .denied:
        print("denied")
    case .authorized:
        print("authorized")
    case .limited:
        print("limited")
    case .notDetermined:
        print("notDetermined")
    @unknown default:
        print("@unknown")
    }
}

private func showPresentMorePhotoButtonIfNeeded() {
    presentMorePhotoButton.isHidden = !(status == .limited)
}

private func updateButtonAssociatedLimitedStatus() {
    presentMorePhotoButton.isHidden = !isLimited
    settingButton.isHidden = presentMorePhotoButton.isHidden
}
  • viewDidLoad에서도 이미 limited 상태이면은 바로 델리게이트 설정
    • viewDidLoad에서 limited 분기문을 넣는 이유?
    • PHPhotoLibrary.shared().register(self)를 호출하면 바로 권한 물어보므로 isLimited 상태일때만 delegate 설정
    if isLimited {
        PHPhotoLibrary.shared().register(self)
    }
  • delegate 구현
extension ViewController: PHPhotoLibraryChangeObserver {
    func photoLibraryDidChange(_ changeInstance: PHChange) {
        // done 버튼 누른 경우 메소드 실행
        print("test>", changeInstance)
    }
}

시스템에서 자동으로 호출되는 팝업

  • 사진 선택 옵션을 선택하면 애플은 자동으로 앱을 다시 실행할때, 사진 선택 권한인 상태(= limited)에서 다시 사진 권한을 요청하면 아래 팝업이 노출
  • 간헐적으로 아래 팝업이 안보이는 케이스가 있으므로 주의

https://swiftsenpai.com/development/photo-library-permission/

  • 비활성화 방법?
    • info.plist에 아래 key로하고, value를 YES로 하면 자동으로 팝업 노출이 비활성화됨
NSPhotoLibraryAddUsageDescription

주의사항

  • 권한 status를 얻는 메소드가 2가지가 있는데, 사진 선택 권한인 상태에서 파리미터가 없는 authorizationStatus()는 authorized를 리턴하므로 주의
// 현재 사용자가 사진 선택 권한을 선택한 상태

PHPhotoLibrary.authorizationStatus(for: .readWrite) // denied
PHPhotoLibrary.authorizationStatus() // authorized

 

* 전체 코드: https://github.com/JK0369/ExPhotoPermission_1

 

* 참고

https://swiftsenpai.com/development/photo-library-permission/

https://developer.apple.com/documentation/bundleresources/information_property_list/nsphotolibraryaddusagedescription

https://developer.apple.com/documentation/photokit/delivering_an_enhanced_privacy_experience_in_your_photos_app

 

Comments