관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] PlaceholderTextView 구현 방법 (UITextView안에 placeholder 구현 방법) 본문

UI 컴포넌트 (swift)

[iOS - swift] PlaceholderTextView 구현 방법 (UITextView안에 placeholder 구현 방법)

jake-kim 2023. 7. 14. 01:36

구현된 PlaceholderTextView

PlaceholderTextView 구현 아이디어

  • UITextView에는 기본적으로 가지고 있는 placeholder 속성이 없기 때문에 커스텀이 필요
  • 잘못 구현하는 케이스
    • 1) UITextView하나만 가지고 텍스트 입력이 시작될 때 textColor와 text를 순간적으로 변경 -> 포커스 위치가 글씨 맨 오른쪽으로 가거나, 포커스 되는 순간 placeholder 값을 지워함 (placeholder는 한 글자 이상 입력될때 없어지도록 구현 불가(
    • 2) UILabel을 두어 UITextView위에 얹져서 구현 -> 깜빡거리는 포커스보다 UILabel이 위에 있기 때문에 깜빡거리는 포커스가 안보이는 현상 발생
  • UITextView안에 placeholder 전용으로 사용하는 UITextView를 별도로 두어서 구현
    • UITextView안에 UITextView를 두고 backgroundColor를 clear로 설정하면 text글씨가 있는 동시에 깜빡거리는 포커스가 이 위에 위치하므로 원하는 형태의 UI 구현이 가능

구현

  • UITextView안에 UITextView가 있어야 하므로 UITextView를 상속받는 뷰 선언
import UIKit

final class PlaceholderTextView: UITextView {
}
  • 매직넘버를 방지하기 위해 필요한 상수값 선언
private enum Const {
    static let backgroundColor = UIColor.lightGray.withAlphaComponent(0.3)
    static let cornerRadius = 14.0
    static let containerInset = UIEdgeInsets(top: 30, left: 24, bottom: 30, right: 24)
    static let placeholderColor = UIColor(red: 0, green: 0, blue: 0.098, alpha: 0.22)
}
  • placeholder 전용으로 사용할 UITextView 선언
    • 중요한점: 해당 뷰는 보여지기만 하고 인터렉션이 없어야 하므로, isUserInteractionEnabled와 isAccessibilityElement를 모두 비활성해야함
private let placeholderTextView: UITextView = {
    let view = UITextView()
    view.backgroundColor = .clear
    view.textColor = Const.placeholderColor
    view.isUserInteractionEnabled = false
    view.isAccessibilityElement = false
    return view
}()
  • 뷰들의 초기값 설정
init() {
    super.init(frame: .zero, textContainer: nil)
    setupUI()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
    fatalError("\(#function) has not been implemented")
}

func setupUI() {
    delegate = self
    
    backgroundColor = Const.backgroundColor
    
    layer.cornerRadius = Const.cornerRadius
    clipsToBounds = true
    
    textContainerInset = Const.containerInset
    contentInset = .zero
    
    addSubview(placeholderTextView)
    
    placeholderTextView.textContainerInset = Const.containerInset
    placeholderTextView.contentInset = contentInset
}
  • 외부에서 세팅할 수 있는 값 선언
var placeholderText: String? {
    didSet {
        placeholderTextView.text = placeholderText
        updatePlaceholderTextView() // 아래에서 계속 구현
    }
}
  • 위에서 등장한 updatePlaceholderTextView()는 핵심 부분
    • 텍스트가 추가되거나, 변경되거나 할 때 해당 메소드가 호출되어, palceholderTextView의 isHidden 필드와 frame값을 재정의해주는 뷰
    • frame값을 재정의 해주는것이 핵심 (placeholderTextView를 품고 있는 뷰의 크기는 UITextView내부에서 자동으로 크기를 늘려주거나 줄여줄기 때문에, 내부적으로 선언한 placeholderTextView도 이와 동일한 크기를 유지하기위해 frame값 업데이트가 계속 필요)
func updatePlaceholderTextView() {
    placeholderTextView.isHidden = !text.isEmpty
    accessibilityValue = text.isEmpty ? placeholderText ?? "" : text
    
    placeholderTextView.textContainer.exclusionPaths = textContainer.exclusionPaths
    placeholderTextView.textContainer.lineFragmentPadding = textContainer.lineFragmentPadding
    placeholderTextView.frame = bounds
}

(해당 메소드를 호출하는 부분 3곳)

final class PlaceholderTextView: UITextView {
	
    ...
    
    var placeholderText: String? {
        didSet {
            placeholderTextView.text = placeholderText
            updatePlaceholderTextView() // <-
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updatePlaceholderTextView() // <-
    }
}

extension PlaceholderTextView: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        updatePlaceholderTextView() // <-
    }
}

 

사용하는 곳

import UIKit

class ViewController: UIViewController {
    private let placeholderTextView = PlaceholderTextView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        placeholderTextView.placeholderText = "값을 입력해주세요"
        
        view.addSubview(placeholderTextView)
        
        placeholderTextView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            placeholderTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            placeholderTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            placeholderTextView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            placeholderTextView.heightAnchor.constraint(equalToConstant: 200),
        ])
    }
}

(완성)

구현된 PlaceholderTextView

* 전체 코드: https://github.com/JK0369/ExPlaceholderTextView.git

Comments