LoginSignup
1
0

UIViewでUIImagePickerControllerを呼ぶ

Last updated at Posted at 2022-11-15

UIImagePickerControllerをUIViewControllerから表示させる方法について記述している記事は多くありましたが、UIViewからそれを行う記事があまり見当たらなかったので、今回記事を書いてみることにしました。

UIViewからUIImagePickerを表示させること自体がレアなケースだと思いますが、参考になれば幸いです。

動作イメージ

動作2.gif

なぜそれが必要になったのか

今回とあるアプリを作成している中で、UIViewControllerに UIStackViewとUICollectionView が置いてあり、UICollectionViewのそれぞれのCellをタップすると、別途作成しているUIViewがinsertされるといった実装を行っておりました。

なぜこのような実装について語ると長くなりますが、簡単に言えば異なるUIViewを画面遷移せずにViewControllerで表示したかった為です。
そんな中、あるUIViewでユーザーから写真を入力してもらう必要性がありました。
そこで、自分なりの正解に辿り着いたので、以下に記述します。

必要なもの

・UIImagePickerController
・UIPopoverPresentationControllerDelegate
・UIApplicationのExtension(最前面のViewControllerを判定する)
・UIImagePickerControllerDelegate(タップされた写真を判定)
・UINavigationControllerDelegate(タップされた写真を通知)

必要な処理

この実装にはUIViewで画面遷移を行う方法と、UIViewからViewControllerにタップイベントを通知して、VC側で画面遷移を行う方法があります。
UIView側で画面遷移を行うのは、少し愚直的であり、設計的にも良く無いと思いますが、今回は両方ご紹介します。

1. UIViewで画面遷移を行う方法

まずは、UIimagePickerControllerのインスタンスを作成します。

この中で、souceTypeや遷移の方法を設定します。
5行目については、.anyでも問題ありません。

private let imagePickerView: UIImagePickerController = {
        let picker = UIImagePickerController()
        picker.sourceType = .photoLibrary
        picker.modalPresentationStyle = .popover
        picker.popoverPresentationController?.permittedArrowDirections = .up
        return picker
    }()

次に、それを呼ぶためのトリガーとなるもの(今回はUIButton)を設定し、タップアクション等によって、以下のアクションを行うようにします。

@objc private func buttonDidTap() {
        imagePickerView.popoverPresentationController?.sourceView = self
        imagePickerView.popoverPresentationController?.delegate = self
        UIApplication.topViewController()?.present(imagePickerView, animated: true)
    }

※ここで記述しているUIApplication.topViewController()では最前面にあるViewControllerを判定し、それを用いてUIViewから.present()UIImagePickerController()を表示させています。

実装については下のコードをご覧いただけますと幸いです。

その他、
imagePickerView.delegate = selffunc imagePickerController(...)については、それらの動作で取得した写真をimageViewに当てはめてる処理になりますので、説明は省きます。

UIView側で画面遷移する際のコードの全体像

動作確認済みです。

StoryBoardを使用しない実装を行い、制約についてはSnapKitを使用しています。
レイアウトについてはあくまでサンプルアプリである為、その点ご容赦ください。

HogeUIView.swift
import UIKit
import SnapKit

class HogeUIView: UIView {
    private let titleLabel: UILabel = {
       let label = UILabel()
        label.text = "I'm UIView."
        label.font = .systemFont(ofSize: 15)
        label.textAlignment = .center
        return label
    }()
    
    private let addButton: UIButton = {
       let button = UIButton()
        button.backgroundColor = .blue
        button.setTitle("写真を追加します", for: .normal)
        button.addTarget(.none, action: #selector(buttonDidTap), for: .touchUpInside)
        return button
    }()
    
    private let imageView: UIImageView = {
       let image = UIImageView()
        image.backgroundColor = .gray
        image.contentMode = .scaleToFill
        return image
    }()
    
    private lazy var entireStack: UIStackView = {
       let stack = UIStackView(arrangedSubviews: [titleLabel, imageView, addButton])
        stack.axis = .vertical
        stack.distribution = .fill
        stack.spacing = 10
        stack.setCustomSpacing(30, after: imageView)
        stack.layer.borderWidth = 1
        stack.isLayoutMarginsRelativeArrangement = true
        stack.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
        return stack
    }()
    
    private let imagePickerView: UIImagePickerController = {
        let picker = UIImagePickerController()
        picker.sourceType = .photoLibrary
        picker.modalPresentationStyle = .popover
        picker.popoverPresentationController?.permittedArrowDirections = .up
        return picker
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupViews() {
        addSubview(entireStack)
        imagePickerView.delegate = self
        entireStack.snp.makeConstraints { make in
            make.width.equalTo(300)
            make.center.equalToSuperview()
        }
        
        addButton.snp.makeConstraints { make in
            make.height.equalTo(40)
        }
        
        imageView.snp.makeConstraints { make in
            make.height.equalTo(260)
        }
    }
}

extension HogeUIView: UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate {
    @objc private func buttonDidTap() {
        imagePickerView.popoverPresentationController?.sourceView = self
        imagePickerView.popoverPresentationController?.delegate = self
        UIApplication.topViewController()?.present(imagePickerView, animated: true)
    }

    func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
        guard let selectedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
        imageView.image = selectedImage
        UIApplication.topViewController()?.dismiss(animated: true)
    }
}
ViewController.swift
import UIKit
import SnapKit

class ViewController: UIViewController {
    private let label: UILabel = {
       let label = UILabel()
        label.text = "I'm ViewController."
        label.font = .systemFont(ofSize: 20)
        label.textAlignment = .center
        return label
    }()
    
    private lazy var entireStackView: UIStackView = {
       let stack = UIStackView(arrangedSubviews: [label])
        stack.axis = .vertical
        stack.distribution = .fill
        stack.alignment = .fill
        stack.spacing = 10
        stack.isLayoutMarginsRelativeArrangement = true
        stack.layoutMargins = UIEdgeInsets(top: 100, left: 20, bottom: 50, right: 20)
        return stack
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        view.backgroundColor = .white
    }
    
    private func setupViews() {
        view.addSubview(entireStackView)
        let view = HogeUIView()
        entireStackView.insertArrangedSubview(view, at: 1)
        entireStackView.snp.makeConstraints { make in
            make.width.equalToSuperview()
            make.height.equalTo(800)
            make.center.equalToSuperview()
        }
    }
}
UIApplication+.swift
extension UIApplication {
    static func topViewController() -> UIViewController? {
        let scenes = UIApplication.shared.connectedScenes
        let windowScene = scenes.first as? UIWindowScene
        let window = windowScene?.windows.first
        guard var top = window?.rootViewController else {
            return nil
        }
        while let next = top.presentedViewController {
            top = next
        }
        return top
    }
}

2. UIViewのタップイベントをVCに通知して、VCで画面遷移を行う方法

こちらについては、どのようにタップイベントをVCに通知すべきかについてのみ解説します。
その中で行うUIImagePicker等の表示・遷移のコードが重複するためです。

今回はRxSwiftを使って通知処理を書いています。

HogeView.swift (UIView側)
class HogeView: UIView {
var buttonDidTap: Observable<Void> {
    addButton.rx.tap.asObservable()
}

 private let addButton: UIButton = {
      let button = UIButton()
      button.layer.cornerRadius = 20
      button.backgroundColor = .systemTeal
      button.setTitle("写真を選択", for: .normal)
      return button
  }()
ViewController.swift
class ViewController: UIViewController {

//これをViewDidLoad内で呼んでください。
func bind() {
   customView.buttonDidTap
   //customViewはUIViewをインスタンス化したものです
     .subscribe(onNext: { [weak self] _ in
        self.onTapedButton()//タップされた際の処理
    })
    .disposed(by: disposeBag)
}

あとは、UIView側で画面遷移を行うコード内のdelegeteの記述やタップアクションをこちらに移行することで、動作します。
さらに、こちらの場合はUIApplicationのExtensionは不要となり、

- UIApplication.topViewController()?.dismiss(animated: true)
+ present(imagePickerController, animated: true, completion: nil)

このように記述することができます。

※UIView側でsetupView()で記述しているimagePickerView.delegate = selfもVC側に書くことを忘れないように注意ください。

最後に

今回の実装で、お役に立てれば幸いです。
また、おそらくこれ以外の実装方法も多くあると思いますので参考までにお願いします。

参考文献

Apple Developer - UIImagePicker

Apple Developer - UIPopoverPresentationController

StackOverFlow

Qiita

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0