InputTextView

이세진·2022년 6월 24일
0

iOS

목록 보기
27/46
post-custom-banner

생성일: 2022년 1월 29일 오전 1:06

  • 기본적으로 apple에서는 TextView에서 placeholder를 설정하는 기능을 제공하지 않는다. ⇒ 커스텀으로 제작 해야한다.

원하는 기능

  1. 텍스트 뷰에 “Enter caption” 이라는 placeholder 을 넣고, 사용자가 글자를 입력하면 placeholder는 사라지도록 한다.
  2. 사용자가 입력한 글자 수를 텍스트 뷰 하단에 보여주도록 하고 지정된 글자수(100글자)를 넘어가게 되면 추후 입력된 글자들은 사라지도록 한다.

⇒ 이를 위해 InputTextView.swift 파일을 생성하고 해당 텍스트 뷰는 UploadPostController.swift에서 생성하여 사용하였다.

UploadPostController.swift

import UIKit

class UploadPostsController: UIViewController {
    
    //MARK: - Properties
		private lazy var captionTextView: InputTextView = {
        let tv = InputTextView()
        tv.placeholderText = "Enter caption..."
        tv.font = UIFont.systemFont(ofSize: 16)
        tv.delegate = self
        return tv
    }()
		
		// 입력한 글자수를 보여주는 Label
		private let characterCountLabel: UILabel = {
        let label = UILabel()
        label.textColor = .lightGray
        label.font = UIFont.systemFont(ofSize: 14)
        label.text = "0/100"
        return label
    }()

		... 중략 ...
		//MARK: - Helpers
		// 100글자 이상 입력하면 넘긴 부분 만큼 지우기
    func checkMaxLength(_ textView: UITextView) {
        if (textView.text.count) > 100 {
            textView.deleteBackward()
        }
    }
}

//MARK: - UITextViewDelegate
// 텍스트 뷰가 변화하면 이를 트래킹하여 입력한 글자 수를 화면에 띄우도록 한다. 이를 위해 UITextViewDelegate를 불러온다.
extension UploadPostsController: UITextViewDelegate {
    
    func textViewDidChange(_ textView: UITextView) {
        checkMaxLength(textView)
        let count = textView.text.count
        characterCountLabel.text = "\(count)/100"
    }
    
}
  • 텍스트 뷰의 변화를 트래킹하기 위하여 UITextViewDelegate 프로토콜과 textViewDidChange 함수를 불러온다.
  • textView가 변화하면(사용자가 입력하면) checkMaxLength 함수를 통해 100글자가 넘는지 확인하고 넘는다면 입력받은 글자를 삭제한다
  • 텍스트뷰에 입력된 총 글자 수를 count로 받아와서 characterCountLabel 에 띄워준다.

InputTextView.swift

import UIKit

/// placeholder를 포함한 텍스트 뷰
class InputTextView: UITextView {
    
    //MARK: - Properties
    
    var placeholderText: String? {
        didSet { placeholderLabel.text = placeholderText }
    }
    
    private let placeholderLabel: UILabel = {
        let label = UILabel()
        label.textColor = .lightGray
        return label
    }()
    
    //MARK: - Lifecycle
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        
        addSubview(placeholderLabel)
        placeholderLabel.anchor(top: topAnchor, left: leftAnchor, paddingTop: 6, paddingLeft: 6)
        
        // 텍스트 뷰에 변화가 생기는 것을 트래킹하여 placeholder 제거하기
        NotificationCenter.default.addObserver(self, selector: #selector(handleTextDidChange), name: UITextView.textDidChangeNotification, object: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    //MARK: - Actions
    
    @objc func handleTextDidChange() {
        // 텍스트뷰가 비어있으면 placeholder가 보여지게하고 글씨가 한개라도 있다면 placeholder 숨기기
        placeholderLabel.isHidden = !text.isEmpty
    }
}

InputTextView는 내부적으로 위와 같이 구현한다.

  • placeholder 역할을 할 placeholderLabel 을 생성하고, 외부에서(InputTextView를 사용하는) placeholder에 들어갈 문자열을 입력받으면 해당 문자열이 placeholderLabel.text가 되도록 didSet을 사용하여 구현한다.
  • 텍스트 뷰에 변화가 생기는 것을 트래킹 하여 글자가 하나라도 입력되면 placeholder를 제거하도록 한다.
  • 이를 위해 NotificationCenter.default.addObserver(self, selector: #selector(handleTextDidChange), name: UITextView.textDidChangeNotification, object: nil) 를 통해서 addObserver를 생성하여 텍스트뷰의 텍스트 변화를 트래킹 한다.

중요한 점

텍스트 뷰 안의 placeholder가 글자가 입력되면 사라지도록 하는 로직을 위해 InputTextView를 트래킹하도록 클래스 내부에 addObserver 를 추가하였는데 이번에 구현한 텍스트 뷰를 사용하는 UploadPostController에서도 해당 텍스트 뷰를 계속 트래킹하기 위해 UITextViewDelegate 프로토콜과 textViewDidChange() 함수를 사용하였다.

⇒ 이중으로 트래킹 하는 것이기 때문에 비효율적이지 않느냐는 의문점이 생김

⇒ 실제로 InputTextView 내부의 addObserver를 없애고 해당 텍스트 뷰를 생성하여 사용하는 외부의 컨트롤러의 textViewDidChange()에서 텍스트 뷰의 글자가 0개가 되면 placeholder를 hidden하도록 설정이 가능하다. (물론 placeholderLabel을 private로 설정하면 안된다.)

⇒ 하지만 굳이 내부와 외부에서 트래킹하도록 구현한데에는 이유가 있다.

  • 다른 파일에서 placeholder 기능을 포함한 텍스트 뷰가 필요하여 InputTextView를 사용한다면 그때도 외부(InputTextView객체를 생성한 클래스)에서 UITextViewDelegate 프로토콜을 불러오고 textViewDidChange에서 placeholder를 hidden 시키는 작업을 거쳐야 한다.
  • 이번 사례에서는 외부에서 텍스트 뷰의 총 글자수를 화면에 보여주는 기능이 필요하였기에 textViewDidChange함수가 필수적으로 필요하였지만 다음에 사용할 때에는 이러한 요구가 없을 수 있기 때문에 내부적으로 placeholder를 제거하는 로직이 필요하다 ⇒ addObserver로 내부적으로 트래킹하여 placeholder를 hidden 하도록 구현하였다.
  • 이러한 과정은 객체지향 프로그래밍의 핵심인 Encapsulation 과 직결된다.

번외

  • Swift에서 객체 or 뷰 등의 변화를 트래킹 하는 방법
    1. addTarget()
      • 버튼 UI의 변화(터치)를 트래킹하는데 주로 사용
    2. delegate를 이용한 DidChange, shouldChange 등의 함수 불러와서 사용하기
      • 예를 들어 UITextViewDelegate 프로토콜을 불러와서 textViewDidChange() 함수를 이용하면, 텍스트 뷰가 변화하는 것을 트래킹 할 수 있다.
    3. NotificationCenter.default.addObserver(observer: Any, selector: selector, name: NSNotification.name?, object: Any?) 를 사용하여 addObserver 하기

실행 화면

profile
나중은 결코 오지 않는다.
post-custom-banner

0개의 댓글