I'm currently learning MVVM and RxSwift. I have an image I want to passed in, in my main view controller. I was successful using mvc and RxSwift, but since MVVM is new to me. I don't know how implemented in MVVM. This is my code.
// This is the work version MVC RxSwift
class GProfilePickAlertVC: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private let selectedSubjectPhoto = PublishSubject<UIImage>()
var selectedPhoto: Observable<UIImage> {
return selectedSubjectPhoto.asObservable()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let editedImage = info[.editedImage] as? UIImage {
self.selectedSubjectPhoto.onNext(editedImage)
dismiss(animated: true, completion: nil)
}
dismiss(animated: true, completion: nil)
}
}
class BasicInfoViewController: UITableViewController {
let profileImageView = GProfileImage(img: #imageLiteral(resourceName: "ic-tab-profile"), renderingMode: .alwaysTemplate)
@objc private func handlePickPhoto() {
let pickPhotoVC = GProfilePickAlertVC()
pickPhotoVC.selectedPhoto.subscribe(onNext: { image in
self.profileImageView.clipsToBounds = true
self.profileImageView.image = image
}).disposed(by: disposeBag)
pickPhotoVC.modalPresentationStyle = .overFullScreen
pickPhotoVC.modalTransitionStyle = .crossDissolve
present(pickPhotoVC, animated: true)
}
}
// This is my MVVM setup
struct BasicInfoViewModel {
let selectedImage: BehaviorRelay<UIImage>
}
class BasicInfoViewController: UITableViewController {
let disposeBag = DisposeBag()
var BasicInfoViewModel: BasicInfoViewModel!
}
Here's what I ended up with. There really isn't much for a view model to do in this instance since there is only one line of logic.
The basic structure of the code is the same as your MVC example except my code is using RxImagePickerDelegateProxy
instead of GProfilePickAlertVC
. The former is a generic delegate that will automatically be attached to any UIImagePickerController by the Rx system as needed.
The imagePickerScene(on:modalPresentationStyle:modalTransitionStyle:)
function takes care of presenting and dismissing the image picker. It acts as a coordinator.
The castOrThrow(_:_:)
function is a generic helper to handle casting. You can use it all over the place.
final class BasicInfoViewController: UIViewController {
private let profileImageView = GProfileImage(img: #imageLiteral(resourceName: "ic-tab-profile"), renderingMode: .alwaysTemplate)
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let imagePicker = imagePickerScene(
on: self,
modalPresentationStyle: .overFullScreen,
modalTransitionStyle: .crossDissolve
)
pickPhotoButton.rx.tap
.flatMapLatest { Observable.create(imagePicker) }
.compactMap { $0[.editedImage] as? UIImage }
.bind { image in
self.profileImageView.clipsToBounds = true
self.profileImageView.image = image
}
.disposed(by: disposeBag)
}
}
func imagePickerScene(on presenter: UIViewController, modalPresentationStyle: UIModalPresentationStyle? = nil, modalTransitionStyle: UIModalTransitionStyle? = nil) -> (_ observer: AnyObserver<[UIImagePickerController.InfoKey: AnyObject]>) -> Disposable {
return { [weak presenter] observer in
let controller = UIImagePickerController()
if let presentationStyle = modalPresentationStyle {
controller.modalPresentationStyle = presentationStyle
}
if let transitionStyle = modalTransitionStyle {
controller.modalTransitionStyle = transitionStyle
}
presenter?.present(controller, animated: true)
return controller.rx.didFinishPickingMediaWithInfo
.do(onNext: { _ in
presenter?.dismiss(animated: true)
})
.subscribe(observer)
}
}
final class RxImagePickerDelegateProxy: DelegateProxy<UIImagePickerController, UINavigationControllerDelegate & UIImagePickerControllerDelegate>, DelegateProxyType, UINavigationControllerDelegate & UIImagePickerControllerDelegate {
static func currentDelegate(for object: UIImagePickerController) -> (UIImagePickerControllerDelegate & UINavigationControllerDelegate)? {
return object.delegate
}
static func setCurrentDelegate(_ delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?, to object: UIImagePickerController) {
object.delegate = delegate
}
static func registerKnownImplementations() {
self.register { RxImagePickerDelegateProxy(parentObject: $0, delegateProxy: RxImagePickerDelegateProxy.self) }
}
}
extension Reactive where Base: UIImagePickerController {
var didFinishPickingMediaWithInfo: Observable<[UIImagePickerController.InfoKey: AnyObject]> {
return RxImagePickerDelegateProxy.proxy(for: base)
.methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:)))
.map({ (a) in
return try castOrThrow(Dictionary<UIImagePickerController.InfoKey, AnyObject>.self, a[1])
})
}
}
func castOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T {
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}