DocumentPickerController를 rxSwift를 이용하여 호출하고 사용을 해보려고 합니다.
DocumentPickerController는 아이폰 내부의 파일을 가져와서 업로드를 하거나, 단순히 보여주기 위해서 사용을 합니다.
// psudo code
let picker = DocumentPickerController()
self.present(picker, animated: true, completion: nil)
현재 뷰컨트롤러에서 이렇게 호출하면 됩니다.
다만 picker를 생성할때 어떤 확장자를 읽어올지 파라미터로 넘기면 됩니다.
if #available(iOS 14.0, *) {
return UIDocumentPickerViewController(forOpeningContentTypes: getUTIs(with: Constants.compositeContentsList))
} else {
return UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
}
UTI를 넘기는 방식으로 14부터는 변경이 되었으니 이런식으로 분기가 필요합니다.
우리가 변경하려고 하는 delegate는 아래와 같은데, 파일을 선택할때, 취소버튼을 눌렀을때 실행되는 딜리게이트 프로토콜을 Rx로 변경하는 것이 목표 입니다.
public protocol UIDocumentPickerDelegate : NSObjectProtocol {
@available(iOS 11.0, *)
optional func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
// called if the user dismisses the document picker without selecting a document (using the Cancel button)
@available(iOS 8.0, *)
optional func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
@available(iOS, introduced: 8.0, deprecated: 11.0)
optional func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
}
딜리게이트를 사용하기 위해서는 크게 두가지가 필요합니다.
1. DelegateProxy 클래스
2. UIDocumentPickerViewController의 Reactive extension
class RxUIDocumentPickerDelegateProxy : DelegateProxy<UIDocumentPickerViewController, UIDocumentPickerDelegate>,
DelegateProxyType,
UIDocumentPickerDelegate {
static func registerKnownImplementations() {
self.register { (pickerview) -> RxUIDocumentPickerDelegateProxy in
RxUIDocumentPickerDelegateProxy(parentObject: pickerview, delegateProxy: self)
}
}
static func currentDelegate(for object: UIDocumentPickerViewController) -> UIDocumentPickerDelegate? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: UIDocumentPickerDelegate?, to object: UIDocumentPickerViewController) {
object.delegate = delegate
}
}
extension Reactive where Base: UIDocumentPickerViewController {
public var delegate: DelegateProxy<UIDocumentPickerViewController, UIDocumentPickerDelegate> {
return RxUIDocumentPickerDelegateProxy.proxy(for: base)
}
public var didPickDocumentsAt: Observable<[URL]> {
return delegate.methodInvoked(#selector(UIDocumentPickerDelegate.documentPicker(_:didPickDocumentsAt:)))
.map { (values: [Any]) in
values.last as! [URL]
}
}
public var documentPickerWasCancelled: Observable<()> {
return delegate.methodInvoked(#selector(UIDocumentPickerDelegate.documentPickerWasCancelled(_:)))
.map {_ in () }
}
}
delegate로 이벤트가 발생하면 프록시를 통해서 RX 이벤트로 바꿔서 전달한다는 컨셉입니다.
다시금 rx라는 이름이 왜 적절한가를 알게됩니다.
//did pick
picker.rx.didPickDocumentsAt
.asObservable()
.bind { url in
print("url: \(String(describing: url.first))")
}.disposed(by: disposeBag)
//cancelled
picker.rx.documentPickerWasCancelled
.asObservable()
.bind { _ in
print("cancelled. ")
}.disposed(by: disposeBag)
delegate를 별도로 채택할 필요없이, 호출한 부분에서 깔끔하게 사용이 가능합니다.
사용시 주의할점 두가지.
첫째,
picker.delegate = self
를 사용하지 않는다.
둘째,
기존의 UIDocumentPickerDelegate를 채택하여 사용하지 않는다.
-> 주의사항을 어기면, app crash가 발생합니다.
Legacy에 있는 Delegate들도 이렇게 변경하여 사용할 수 있겠습니다.
끝.