-
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()
}
}
}
}
반응형'iOS' 카테고리의 다른 글
GCD의 개념 (0) 2018.11.30 [iOS] iOS 면접 질문 인터뷰 모음. (0) 2018.11.30 텍스트 리더가 앱스토어에 출시되었습니다. (0) 2018.10.03 UITableView를 스냅킷으로 구현하면서 생겼던 이슈 (0) 2018.09.30 텍본리더 개발 이슈 - 로딩바의 출력에 rxSwift를 도입 (1) 2018.09.30