HIT해
[Swift/SwiftUI] 높이가 동적으로 변하는 Text 입력란 만들기 본문
카카오톡 처럼 동적으로 변경되는 텍스트 입력창을 만들어야했고
같이 프로젝트를 진행하는 동료가 이를 구현하고자 3일동안 텍스트필드에 메달렸다.
밤낮으로 힘들어하는 동료를 돕고자 코드를 천천히 뜯어보았다.
UIViewRepresentable 안에서 TextField를 반환하고 updateUIView에서 입력된 텍스트를 기반으로 텍스트 필드의 frame을 변경하도록 만들었다.
makeUIView와 updateUIView에서 frame으로 높이조절을 하더라도 SwiftUI 코드에서 해당 TextField를 불러왔을때 조정된 크기가 아닌 고정 크기를 가진채 나왔다.
그 이유는 SwiftUI에서는 UIViewRepresntable 안에서 지정한 크기는 적용되지 않고 SwiftUI 코드에서 frame을 지정해야만 크기가 지정되기 때문이다.
그렇기에 크기를 지정할거라면 UIViewRepresntable 로 View를 반환하지 말고 SwiftUI 문법으로 작성된 코드로 만들어야한다.
struct SimpleCustomTextField: UIViewRepresentable {
@Binding var text: String
var placeholder: String
var placeholderColor: UIColor?
var textColor: UIColor?
var font: UIFont?
var maxLength: Int
var onEditingChanged: ((Bool) -> Void)?
class Coordinator: NSObject, UITextFieldDelegate {
var parent: SimpleCustomTextField
init(parent: SimpleCustomTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
if let text = textField.text, text.count > parent.maxLength {
textField.text = String(text.prefix(parent.maxLength))
}
parent.text = textField.text ?? ""
}
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.onEditingChanged?(true)
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.onEditingChanged?(false)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIView(context: Context) -> UITextField {
...
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
...
}
}
위의 코드상에서 frame 을 아무리 걸어도 적용되지 않는다.
import SwiftUI
struct MainView : View {
var body : some View{
SimpleCustomTextField.frame(...)
}
}
이렇게 해야만 크기가 변경되기에. 동적으로 크기를 변경하는 로직은 SwiftUI 소스코드 상에서 만들고 적용해야한다.
그렇기에 기본적으로 스크롤이 제공되고 줄바꿈 기능이 존재하는 TextEditor를 사용했다.
TextEditor(text: $text).frmae(width: 100 , height: dynamicHeight)
높이를 결정하는 로직으로는 두가지를 생각했다.
1.text.count로 해결하는 로직
2.text 너비를 계산해서 해결하는 로직
1의 경우 구현하는 난이도는 쉽지만 영어를 입력했을때 한글보다 비교적 사이즈가 작아 가로길이를 다 채우지 않아도 크기가 커지는 문제가 있고
2의 경우 영어, 한글 모두 동일하게 적용할 수 있는 이점이 있다.
해결 코드
struct ResizableTextView: View {
@Binding var text: String
let font: UIFont = UIFont(name: Font.body3Regular.customFont.rawValue, size: Font.body3Regular.size) ?? UIFont.systemFont(ofSize: 12)
// TextEditor 최대 너비
var maxTextWidth: CGFloat
// TextEditor 높이
@State private var textHeight: CGFloat = 48
var body: some View {
TextEditor(text: $text)
.frame(height: textHeight)
.padding(EdgeInsets(top: 10, leading: 16, bottom: 12, trailing: 16))
.onAppear {
calculateHeight()
}
.onChange(of: text) { _ in
calculateHeight()
}
}
private func calculateHeight() {
let lineCount = autoLineCount(text: text, font: font, maxTextWidth: maxTextWidth - 40)
...
}
private func autoLineCount(text: String, font: UIFont, maxTextWidth: CGFloat) -> CGFloat {
var lineCount: CGFloat = 0
text.components(separatedBy: "\n").forEach { line in
...
lineCount += ceil(currentTextWidth / maxTextWidth)
}
print("lineCount = \(lineCount)")
return lineCount
}
}
구현 모습
잘 작동되는 모습을 볼 수 이따
'Swift > UIKit 개발 노트' 카테고리의 다른 글
3D 오브젝트 애니메이션 제어를 위한 Delegate 패턴 구현하기 (0) | 2025.02.03 |
---|---|
개인프로젝트 진행 계획 (0) | 2025.02.02 |
[Swift/SwiftUI] 줄바꿈 단위 글자단위 <-> 어절단위 (0) | 2024.09.26 |
[Swift/TCA] binding 변수 처리하기 ( TCA 1.12.1 ) (0) | 2024.09.24 |
[Swift/TCA] warning: data race detected: @MainActor function at ComposableArchitecture/Binding+Observation.swift:164 was not called on the main thread 에러 해결하기 (0) | 2024.09.24 |