iOS

iOS) Dynamic TextView의 Underline - TextView에 밑줄 추가하기

ScutiUY 2021. 3. 5. 10:56

textField에 under line을 생성하는 예제는 많다.

dynamic textView를 생성하는 예제도 많다.

둘을 합쳐보자.

 

우선 textView에 under line을 추가하는 코드이다.

func underLine() {
    let border = CALayer()
    border.frame = CGRect(x: 0, y: self.frame.size.height-1, width: self.frame.width, height: 1)
    border.borderWidth = 1
    border.backgroundColor = UIColor.black.cgColor
    self.layer.addSublayer(border)
}

 

간단하게 layer를 textView의 frame에 맞추어 만들어 sublayer로 추가 하는것이다.

 

참고로 textView의 frame을 얻기 위해  viewDidLoad() 가 아닌 viewWillLayoutSubviews()에서 추가해 주거나, textView를 추가 한 후 layoutifNeeded()를 통해 view의 layout을 업데이트 해준 후 추가해 주어야 한다.

 

 

textView의 크기를 text에 맞추어 갱신하는 코드이다.

func automaticallySizeToFit(superFrame: CGSize) {

      let initalSize = CGSize(width: superFrame.width, height: 1000)
      let estimateSize = dynamicTextView.sizeThatFits(initalSize)
      dynamicTextView.constraints.forEach { (constraint) in
          if constraint.firstAttribute == .height {
              constraint.constant = estimateSize.height
          }
      }
}
    

 

 

sizeTahtFits() 함수는 적용되는 view에 대해 최적화 된 size를 리턴 해준다.

그 후 textView의 height constraint를 찾아 최적화 된 size의 height로 변환 해준다.

 

 

자 그럼 dynamicTextView에 layer를 추가 해서 밑줄이 있는 dynamicTextView를 만들어보자.

 

 

밑줄이 변화되는 view의 크기에 따라서 움직이지 않는다.

layer는 view가 아니다. layer를 생성시 autoLayout이 아닌 '값'으로 지정해야 한다.

따라서 밑줄은 생성시 frame 값 그대로 남아있게 된다.

 

textView의 bottom에 밑줄이 고정되는 형태로 만들어보자.

 

 

textViewDelegate를 통해 view 크기가 업데이트 될때 layer도 같이 업데이트 해보기

layer를 global property로 생성하여 textViewDidChange(_ textView: UITextView)에서 업데이트 해줘보자.

물론 객체지향에 어긋나지만 일단 해보자.

 

 

func automaticallySizeToFit(superFrame: CGSize) {
        let initalSize = CGSize(width: 350, height: 1000)
        let estimateSize = dynamicTextView.sizeThatFits(initalSize)
        
        // 요기서 밑줄 업데이트
        border.frame = CGRect(x: 0, y: estimateSize.height - 3, width: dynamicTextView.frame.width, height: 1)
        
        dynamicTextView.constraints.forEach { (constraint) in
            if constraint.firstAttribute == .height {
                constraint.constant = estimateSize.height
                
            }
        }
    }

실패~

 

우리에겐 autoLayout이 필요하다.

 

CALayer가 아닌 UIView의 형태로 underline 만들기

view형태의 underline을 만들어 autoLayout을 적용시킨다면 되지 않을까...

 

func underLine() {
        
        let underLineView = UIView()
        
        self.addSubview(underLineView)
        
        underLineView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            underLineView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            underLineView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            underLineView.widthAnchor.constraint(equalToConstant: 100),
            underLineView.heightAnchor.constraint(equalToConstant: 1)
        ])

        underLineView.layer.borderWidth = 1
        underLineView.layer.backgroundColor = UIColor.black.cgColor
        
    }

UITextView에 extension을 통해 추가 해주었다.

 

결과는...?

읭?..

 

아마 textView의 scrollView의 재사용 그 머시기 때문에 topAnchor 바로 위에 bottom Anchor가 잡히지 않았나 싶다..

 

최후의 수단 StackView로 감싸기

마지막 방법은 textView와 UnderLine 형태의 View를 stackView로 감싸는 것이다.

 

 

    var containerStackView: UIStackView = {
        var stackView = UIStackView()
        stackView.backgroundColor = .blue
        stackView.axis = .vertical
        stackView.alignment = .fill
        stackView.distribution = .fill
        stackView.spacing = 0
        return stackView
    }()
    
    var textView: UITextView = {
        var textView = UITextView()
        textView.text = "dynamic textView with underline"
        textView.font = UIFont.boldSystemFont(ofSize: 20)
        return textView
    }()
    var underLine: UIView = {
        var view = UIView()
        view.layer.borderWidth = 1
        view.layer.borderColor = UIColor.black.cgColor
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        textView.delegate = self
        
        view.addSubview(containerStackView)
        
        containerStackView.addArrangedSubview(textView)
        containerStackView.addArrangedSubview(underLine)
        
        containerStackView.translatesAutoresizingMaskIntoConstraints = false
        textView.translatesAutoresizingMaskIntoConstraints = false
        underLine.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            
            containerStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            containerStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            containerStackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
            
            textView.widthAnchor.constraint(equalTo: containerStackView.widthAnchor),
            textView.heightAnchor.constraint(equalToConstant: 100),
            
            underLine.widthAnchor.constraint(equalTo: containerStackView.widthAnchor),
            underLine.heightAnchor.constraint(equalToConstant: 1)
            
            
        ])
        
        view.layoutIfNeeded()
        
        textViewDidChange(textView)
        
    }

 

 

성공