• TextView 를 UIView 위에 올려 버튼, ScrollView등 다양한 UI 작업

func textViewDidChange(_ textView: UITextView) {
    let lineHeight = textView.font?.lineHeight ?? 0
    let maxHeight = lineHeight * 3 + textView.textContainerInset.top + textView.textContainerInset.bottom
    let width = textView.bounds.width > 0 ? textView.bounds.width : UIScreen.main.bounds.width - 50
    let estimatedSize = textView.sizeThatFits(CGSize(width: width, height: .infinity))
    let finalHeight = min(estimatedSize.height, maxHeight)
    textView.isScrollEnabled = estimatedSize.height > maxHeight
    textViewHeightConstraint?.update(offset: finalHeight)
    let topPadding: CGFloat = 10
    let bottomPadding: CGFloat = 10
    let contentHeight = max(finalHeight, 21)
    let totalHeight = topPadding + contentHeight + bottomPadding
    onHeightChange?(totalHeight)
    UIView.animate(withDuration: 0.1) {
        self.superview?.layoutIfNeeded()
    }
}

textView 안에서 글을 입력하게 되면, 해당 글씨의 lineHeight를 계산해 3줄까지를 maxHeight 값으로 기준을 잡고, 그 기준을 넘지 않았을떄(1줄 또는 2줄일때) 는 계산된 height에 따라 onHeightChange 클로져를 통해 CustomTextView의 높이를 바꿔주고, maxHeight에 도달했을때는 더이상 변화되지 않고, textView가 scroll 되게 구성해주었습니다.

func movePlusButtonToBottom(_ move: Bool) {
    plusButtonTopConstraint?.deactivate()
    plusButton.snp.makeConstraints {
        if move {
            plusButtonTopConstraint = $0.top.equalTo(tagScrollView.snp.top).offset(6).constraint
            
            tagScrollView.isHidden = false
            tagStackView.isHidden = false
            
        } else {
            plusButtonTopConstraint = $0.top.equalToSuperview().offset(10).constraint
            tagScrollView.isHidden = true
            tagStackView.isHidden = true
        }
    }
    UIView.animate(withDuration: 0.3) {
        self.layoutIfNeeded()
    }
}

이 함수는 plusButton이 keyboard가 올라가고 내려갈때 레이아웃을 바꿔주기위해 만들어준 함수입니다. 여기서 move의 매개변수는,

keyboard observer 가 동작할때 받습니다.

만약 keyboard가 올라오지 않는 상태라면, textView의 top에 10으로 고정시켜두었다가, keyboard가 동작하면 하단에 있던 tagScrollView 의 top으로 constraint 를 옮겨지도록 위치를 조정해 주었습니다.

func addTag(title: String) {
    let tagView = TagListView(title: title)
    tagView.onTap = { [weak self, weak tagView] in
        guard let self, let tagView else { return }
        self.tagStackView.removeArrangedSubview(tagView)
        tagView.removeFromSuperview()
        self.updateTagVisibility()
    }
    tagStackView.addArrangedSubview(tagView)
    updateTagVisibility()
}

private func updateTagVisibility() {
    tagScrollView.isHidden = tagStackView.arrangedSubviews.isEmpty
}

addTag 함수는, customTextView 하단에 해쉬태그 버튼들을 달아주기 위한 함수입니다.

미리 객체로 만들어둔 TagListView에 onTap 클로져를 통해 각 버튼 하나하나의 동작을 할당해주는 함수로, VC에서 각 버튼 개수만큼 추가를 해주면 되도록 만들었습니다.

vc 에서 textView.addTag("Swift") 를 하면 Swift 태그 버튼이 ScrollView의 stackView 안에 포함이 되며, 여러번 사용하면, 각 버튼들이 순서대로 스택뷰에 담기도록 구현해주었습니다.

이때 만들어지는 과정을 살펴보면,

  1. CustomTextView 는 tagView를 생성하고, tagStackView에 추가한다.