-
Notifications
You must be signed in to change notification settings - Fork 0
/
TextMaster.swift
150 lines (124 loc) · 4.45 KB
/
TextMaster.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import SwiftUI
struct TextMaster: View {
@Binding var text: String
@State private var dynamicHeight: CGFloat
let isFocused: FocusState<Bool>.Binding
let minLine: Int
let maxLine: Int
let font: UIFont
let becomeFirstResponder: Bool
init(
text: Binding<String>,
isFocused: FocusState<Bool>.Binding,
minLine: Int = 1,
maxLine: Int,
fontSize: CGFloat,
becomeFirstResponder: Bool = false)
{
_text = text
self.isFocused = isFocused
self.minLine = minLine
self.maxLine = maxLine
self.becomeFirstResponder = becomeFirstResponder
let font = UIFont.systemFont(ofSize: fontSize)
self.font = font
_dynamicHeight = State(initialValue: font.lineHeight * CGFloat(minLine) + 16) // textContainerInset 디폴트 값은 top, bottom 으로 각각 패딩 8 씩 들어감
}
var body: some View {
UITextViewRepresentable(
text: $text,
dynamicHeight: $dynamicHeight,
isFocused: isFocused,
minLine: minLine,
maxLine: maxLine,
font: font,
becomeFirstResponder: becomeFirstResponder)
.frame(height: dynamicHeight)
.focused(isFocused)
.border(isFocused.wrappedValue ? Color.blue : Color.gray, width: 1)
}
}
fileprivate struct UITextViewRepresentable: UIViewRepresentable {
@Binding var text: String
@Binding var dynamicHeight: CGFloat
let isFocused: FocusState<Bool>.Binding
let minLine: Int
let maxLine: Int
let font: UIFont
let becomeFirstResponder: Bool
func makeUIView(context: UIViewRepresentableContext<UITextViewRepresentable>) -> UITextView {
let textView = UITextView(frame: .zero)
textView.delegate = context.coordinator
textView.font = font
textView.backgroundColor = .clear
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textView.isScrollEnabled = false
textView.bounces = false
if becomeFirstResponder {
textView.becomeFirstResponder()
}
return textView
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewRepresentable>) {
guard uiView.text == self.text else { // 외부에서 주입되는 텍스트에 대한 반응을 위해 필요
uiView.text = self.text
return
}
}
func makeCoordinator() -> UITextViewRepresentable.Coordinator {
Coordinator(
text: $text,
isFocused: isFocused,
dynamicHeight: $dynamicHeight,
minHeight: font.lineHeight * CGFloat(minLine) + 16,
maxHeight: font.lineHeight * CGFloat(maxLine + (maxLine > minLine ? 1 : .zero)) + 16)
}
final class Coordinator: NSObject, UITextViewDelegate {
@Binding var text: String
@Binding var dynamicHeight: CGFloat
let isFocused: FocusState<Bool>.Binding
let minHeight: CGFloat
let maxHeight: CGFloat
init(
text: Binding<String>,
isFocused: FocusState<Bool>.Binding,
dynamicHeight: Binding<CGFloat>,
minHeight: CGFloat,
maxHeight: CGFloat)
{
_text = text
self.isFocused = isFocused
_dynamicHeight = dynamicHeight
self.minHeight = minHeight
self.maxHeight = maxHeight
}
func textViewDidBeginEditing(_ textView: UITextView) {
isFocused.wrappedValue = true
}
func textViewDidEndEditing(_ textView: UITextView) {
isFocused.wrappedValue = false
}
func textViewDidChange(_ textView: UITextView) {
self.text = textView.text ?? ""
if text.isEmpty {
dynamicHeight = minHeight
textView.isScrollEnabled = false
return
}
let newSize = textView.sizeThatFits(.init(width: textView.frame.width, height: .greatestFiniteMagnitude))
print("\n🔽최대 높이 -> \(maxHeight)")
print("❤️NEW SIZE -> \(newSize.height) / lineHeight -> \(textView.font!.lineHeight)")
print("🔼최소 높이 -> \(minHeight)")
if newSize.height < maxHeight, textView.isScrollEnabled { // 최대 높이 미만으로 줄어들면서, 스크롤이 true 라면...
textView.isScrollEnabled = false
print("📜 스크롤 뷰 꺼짐!")
} else if newSize.height > maxHeight, !textView.isScrollEnabled { // 최대 높이 초과로 커지면서, 스크롤이 false 라면...
textView.isScrollEnabled = true
textView.flashScrollIndicators()
print("🦋 스크롤 뷰 켜짐!")
}
guard newSize.height > minHeight, newSize.height < maxHeight else { return }
dynamicHeight = newSize.height // 텍스트뷰의 동적 높이 조절
}
}
}