Skip to content

Instantly share code, notes, and snippets.

@skyofdwarf
Created June 29, 2022 02:29
Show Gist options
  • Save skyofdwarf/9e8c589ad1f9bcc87bed6b75dbd9df35 to your computer and use it in GitHub Desktop.
Save skyofdwarf/9e8c589ad1f9bcc87bed6b75dbd9df35 to your computer and use it in GitHub Desktop.
UITextView 동적 높이 변경

UITextView 동적 높이 변경

채팅 입력창 같은 경우 사용자가 입력한 메시지의 라인 수에 따라 채팅창 높이가 동적으로 변경되는 UI를 흔히 볼 수 있다. 메시지 입력 시 높이 조절과 스크롤이 이상하게되는 문제를 해결하다 발견한 내용을 짧게 남긴다.

레거시 코드는 UITextView.contentSize.height 를 텍스트높이로 사용하고 있었는데 텍스트가 여러줄이 되면서 정확한 값을 주지 않고 있었다. 검색해보니 iOS 7 이전에 사용하던 방식이라네.

그래서 -[NSLayoutManager usedRectForTextContainer:] 요놈을 사용하려고 했는데 값을 잘 주는가 싶더니 라인개행이 될 때 마다 한번씩 더 작은 값을 던져줘서 사용 포기, 왜 이러는거지? 어쩔 수 없이 역시나 -[NSAttributedString boundingRectWithSize:options:context:] 를 사용해야지.

채팅창을 최대 3줄로 만든다고 하면 대충 이런 식

func textViewDidChange(_ textView: UITextView) {
  let lineHeight = font.lineHeight
  let lineSpaing = textView.lineSpacing
  let insets = textView.textContainerInset.top + .bottom
  
  // inset 추가 없어도 4줄 이하 판별은 문제 없어 보임
  let lines = 4
  let heightFor4Lines = (lineHeight * lines) + (lineSpaing * (lines - 1))
  let margins  = 텍스트 뷰의 컨터이너 뷰 마진
  let paddings = 텍스트 뷰의 컨터이너 뷰 패딩
  
  let originalTextViewContainerHeight = // 텍스트뷰와 UI를 위해 감싸는 컨터이커 까지의 높이
  
  // textView.textContainerInset.left/right 값이 0이 아니라면 boundingRect 구할 때 적용해야 할 수도 있음
  let boudingRect = textView.attributedText.boudningRect...
  let textHeight = ceil(boudingRect.height)
  
  if textHeight < heightFor4Lines {
    // 1~3 라인은 동적으로 높이 변경
    let height = max(originalTextViewContainerHeight, max(defaultTextViewHeight, textHeight) + margins + paddings + insets)
    
    updateTextContainerHeight(height)    
  } else {
    // 4라인 이상은 마지막 높이(3라인) 유지하고 하단 스크롤만 시킴
    scrollTextViewToBottom()
  }
}
func updateTextContainerHeight(height) {
    guard pre.height != height else { return }
    // TODO: update height
    
    scrollTextViewToBottom()
  }
func scrollTextViewToBottom() {
    // TODO: scroll text view to bottom  
  }
}

요약하자면,

  1. line spacing, line height, inset, margins, padding 등으로 높이를 구하고
  2. 1~3 라인이고, 구한 높이값이 이전 높이와 다르다면 변경하고
  3. 텍스트뷰를 최하단으로 스크롤 시킨다.

텍스트뷰를 최하단으로 스크롤 시키는 이유는 라인 개행 시 텍스트뷰 상단에 윗 글자의 하단 부분이 살짝 보일 수 가 있는데 이 부분을 위로 올리기 위함이다. 물론 개행 후 하단에 스크롤 시킬 영역이 남아있어 가능한 일이겠지( 이 스크롤가능한 영역이 inset.bottom 값과 관련있나?. 다른 차원의 나는 두 값을 비교해 보자).

최하단 스크롤 내용은 채팅창 UI 디자인에 따라 필요없는 내용이 될 수 도 있겠지만, 필요할 때는 도움이 되는 작은 꿀팁 정도로 알아두자.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment