ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • UIAlertViewController 에서 여러 줄의 인풋(UITextView) 을 받는 방법이 없을까?
    iOS 2018. 10. 12. 14:38


    기본적으로 인풋을 받을 때는, UIAlertViewController에 UITextField 사용합니다.


    그런데 개발 스펙에 따라 인풋에 엔터가 포함될 때가 있는데, 이럴 때는 문제가 발생합니다.


    기본적으로 UITextField는 라인 하나일 때만 사용하고, UITextView는 글줄일 때 사용하는 인풋입니다.


    그런데 UIAlertViewController 는 textField만 추가할 수 있고, textView를 추가하는 메소드가 없습니다.


    따라서 해당 방식으로 구현하기 위해서는 어떤 방식이든 커스터마이제이션이 필요하죠.


    해당 부분을 커스터마이징하는 코드는 다음과 같습니다.

    https://qiita.com/star__hoshi/items/ea4712aeb7bf503abac2


    // textView を表示するための高さの担保のため、dummy で改行を表示する

    let message = "message" + "\n\n\n\n\n\n\n"

    let alert = UIAlertController(title: "title", message: message, preferredStyle: .alert)


    let textView = UITextView()

    textView.text = defaultText

    textView.layer.borderColor = UIColor.lightGray.cgColor

    textView.layer.borderWidth = 0.5

    textView.layer.cornerRadius = 6


    // textView を追加して Constraints を追加

    alert.view.addSubview(textView)

    textView.snp.makeConstraints { make in

        make.top.equalTo(75)

        make.left.equalTo(10)

        make.right.equalTo(-10)

        make.bottom.equalTo(-60)

    }


    // 画面が開いたあとでないと textView にフォーカスが当たらないため、遅らせて実行する

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {

        textView.becomeFirstResponder()

    }


    let okAction: UIAlertAction = UIAlertAction(

        title: "OK",

        style: UIAlertActionStyle.default,

        handler: { _ in

            print(textView.text)

        }

    )

    alert.addAction(okAction)




    스냅킷을 사용한 코드이고, 스냅킷 라이브러리 없이 해당 부분을 활용한다면 이렇게 되겠죠.


     let alert = UIAlertController(title:nil, message : "메시지.\n\n\n\n", preferredStyle: .alert)

                alert.view.autoresizesSubviews = true

                let margin : CGFloat = 8

                let textView : UITextView = {

                   let tv = UITextView(frame: CGRect.zero)

    //                tv.translatesAutoresizingMaskIntoConstraints = false

    //                tv.backgroundColor = UIColor.white

    //                tv.text = self.profileTextView.text

    //                tv.selectedTextRange = tv.textRange(from: tv.endOfDocument, to: tv.endOfDocument)

                    return tv

                }()


                alert.view.addSubview(textView)

                textView.translatesAutoresizingMaskIntoConstraints = false


                alert.view.topAnchor.constraint(equalTo: textView.topAnchor, constant: -64).isActive = true

                alert.view.bottomAnchor.constraint(equalTo: textView.bottomAnchor, constant: 64).isActive = true


                textView.leadingAnchor.constraint(equalTo: alert.view.leadingAnchor, constant: 8).isActive = true

                textView.trailingAnchor.constraint(equalTo: alert.view.trailingAnchor, constant: -8).isActive = true



                alert.addAction(UIAlertAction(title: "취소",style : .cancel))


                alert.addAction(UIAlertAction(title: "확인", style : .default){

                    (_) in

                    print(textView.text)

                                })

                self.present(alert, animated: false, completion: {

                    textView.becomeFirstResponder()

                })


    해당 코드를 찬찬히 뜯어보면 문제되는 부분이 단연코 드러납니다.


    일단 메시지에서 텍스트를 UIAlertController(title:nil, message : "메시지.\n\n\n\n", preferredStyle: .alert)

    로 뉴라인을 인위적으로 추가하는 것에 주목할 필요가 있습니다.


    이 커스터마이제이션은 좀 조잡합니다. 이렇게 메시지 파트에서 대충 엔터를 입력해서 늘인 다음, 눈대중으로 텍스트뷰가 이정도 마진 주면 되겠지~

    하고 오토레이아웃을 준 것에 불과하기 때문이죠.


    따라서 조금만 엔터를 잘못 계산해도. 텍스트뷰가 메시지를 침범하거나, 하단 버튼 레이아웃이 깨지는 불상사가 발생했습니다. 


    거기에 가로, 세로 및 다양한 디바이스에서의 호환성도 확실하지 않고요.


    더욱 치명적인 문제는, 텍스트뷰에 포커스를 주었을 시 자동으로 알럿뷰컨트롤러가 상단으로 올라가지 않는다는 것입니다.

    따라서 작은 디바이스에서나, 가로 모드에서 키보드에 버튼이 가려지는 현상도 일어납니다.



    고심을 한 결과, 알럿뷰에 텍스트뷰를 집어넣는다는 말도 안되는 방법을 포기하고, 새 뷰 컨트롤러를 만들어서 거기에 텍스트뷰를 박아넣는 방법을 택했습니다.


    비록 키보드 레이아웃에 따라 옵저버를 설정해주는 등의 귀찮은 작업이 있긴 하지만, 가장 뒤탈이 없는 방법이기 때문이죠.


      let vc = TextViewController()

                vc.delegate = self

                      let nc = UINavigationController(rootViewController: vc)

                DispatchQueue.main.async {

                    self.present(nc, animated: true, completion: nil)

                }






    이렇게 두고, 커스텀 델리게이션 프로토콜을 정의해 확인 버튼을 누르면 처리해주도록 했습니다.




    extension ViewController : TextDelegate {

        func didPText(pText: String, viewController: UIViewController) {

            DispatchQueue.main.async {

                viewController.dismiss(animated: true) {


                }

            }

        }

    }




    protocol TextDelegate: class {

        func didPText(pText: String, viewController : UIViewController)

    }


    class TextViewController: UIViewController {

        lazy var cancelButton : UIBarButtonItem = {

            let bbi = UIBarButtonItem(title: "취소", style: UIBarButtonItem.Style.plain, target: self, action: #selector(cancel))

            return bbi

        }()

        

        lazy var okButton : UIBarButtonItem = {

            let bbi = UIBarButtonItem(title: "확인", style: UIBarButtonItem.Style.plain, target: self, action: #selector(ok))

            

            return bbi

            

        }()

        lazy var textView : UITextView = {

           let tv = UITextView()

            tv.font = UIFont.systemFont(ofSize: 15)

            tv.translatesAutoresizingMaskIntoConstraints = false

            tv.isScrollEnabled = true

            

            return tv

        }()

        weak var delegate : TextDelegate?

        override func viewDidLoad() {

            self.navigationItem.leftBarButtonItem = cancelButton

            self.navigationItem.rightBarButtonItem = okButton

            setUpViews()

        }

        var bottomAnchor : NSLayoutConstraint?

        func setUpViews(){

            view.backgroundColor = UIColor.white

            view.addSubview(textView)

            textView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true

            textView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true

            textView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true

            bottomAnchor = textView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)

            bottomAnchor?.isActive = true

            

        }

        override func viewDidAppear(_ animated: Bool) {

            super.viewDidAppear(animated)

            textView.becomeFirstResponder()

        }

        @objc func cancel(){

            DispatchQueue.main.async {

                  self.dismiss(animated: true, completion: nil)

            }

        }

        @objc func ok(){

            let value = textView.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

           

            delegate?.didProfileText(profileText: value, viewController: self)

            

        }

        

        override func viewWillAppear(_ animated: Bool) {

            //  self.view.layoutIfNeeded()

            super.viewWillAppear(animated)

            

            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:

                UIResponder.keyboardWillShowNotification, object: nil)

            NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)

        }

        override func viewWillDisappear(_ animated: Bool) {

            super.viewWillDisappear(animated)

            NotificationCenter.default.removeObserver(

                self,

                name: UIResponder.keyboardWillHideNotification,

                object : nil

            )

            NotificationCenter.default.removeObserver(

                self,

                name: UIResponder.keyboardWillShowNotification,

                object: nil

            )

        }

        

        @objc func keyboardWillHide(notification: Notification){

            if let userInfo = notification.userInfo, let keyboardSize = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect{

                

                UIView.animate(withDuration: 0.25) {

                    self.bottomAnchor?.constant = 0

                    self.view.layoutIfNeeded()

                }

            }

            

        }

        @objc func keyboardWillShow(notification: Notification){

            if let userInfo = notification.userInfo, let keyboardSize = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect{

                

                UIView.animate(withDuration: 0.25) {

                    self.bottomAnchor?.constant = -keyboardSize.height

                    self.view.layoutIfNeeded()

                }

            }

            

        }

        

    }


Designed by Tistory.