@@ -19,10 +19,11 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi
1919 private var textViewHeightConstraint : NSLayoutConstraint !
2020
2121 // MARK: - UI Components
22+ private let bottomBackgrounView = UIView ( ) . then {
23+ $0. backgroundColor = . appColor( . neutral100)
24+ }
2225 private let messageInputView = UIView ( ) . then {
23- $0. backgroundColor = UIColor . systemGray6 // ✅ 회색 배경 설정
24- $0. layer. cornerRadius = 16
25- $0. layer. masksToBounds = true
26+ $0. backgroundColor = . appColor( . neutral100)
2627 }
2728
2829 private let leftButton = UIButton ( ) . then {
@@ -32,26 +33,24 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi
3233 private let textView = UITextView ( ) . then {
3334 $0. isScrollEnabled = false
3435 $0. font = UIFont . systemFont ( ofSize: 16 )
35- $0. backgroundColor = . clear // ✅ 배경 투명
36+ $0. backgroundColor = . appColor ( . neutral0 )
3637 $0. textContainerInset = UIEdgeInsets ( top: 8 , left: 8 , bottom: 8 , right: 8 )
37- $0. layer. cornerRadius = 8
38+ $0. layer. cornerRadius = 12
39+ $0. text = " 메세지 보내기 "
40+ $0. textColor = . appColor( . neutral500)
3841 }
3942
4043 private let sendButton = UIButton ( ) . then {
4144 $0. setImage ( UIImage . appImage ( asset: . send) , for: . normal)
4245 }
4346
44- private let blockModalViewController = ModalViewController ( width: 301 , height: 179 , paddingBetweenLabels: 12 , title: " 이 사용자를 차단하시겠습니까? " , subTitle: " 쪽지 수신 및 발신이 모두 차단됩니다. " , titleColor: UIColor . appColor ( . neutral700) , subTitleColor: UIColor . appColor ( . gray) , rightButtonText: " 차단하기 " ) . then {
45- $0. modalPresentationStyle = . overFullScreen
46- $0. modalTransitionStyle = . crossDissolve
47- }
48-
4947 private let blockCheckModalViewController = BlockCheckModalViewController ( ) . then {
5048 $0. modalPresentationStyle = . overFullScreen
5149 $0. modalTransitionStyle = . crossDissolve
5250 }
5351
54- private let chatHistoryTableView = ChatHistoryTableView ( ) . then { _ in
52+ private let chatHistoryTableView = ChatHistoryTableView ( ) . then {
53+ $0. contentInset = UIEdgeInsets ( top: 0 , left: 0 , bottom: 16 , right: 0 )
5554 }
5655
5756 init ( viewModel: ChatViewModel ) {
@@ -78,7 +77,6 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi
7877 configureView ( )
7978 bind ( )
8079 NotificationCenter . default. addObserver ( self , selector: #selector( keyboardWillShow ( _: ) ) , name: UIResponder . keyboardWillShowNotification, object: nil )
81- NotificationCenter . default. addObserver ( self , selector: #selector( keyboardWillHide ( _: ) ) , name: UIResponder . keyboardWillHideNotification, object: nil )
8280 let tapGesture = UITapGestureRecognizer ( target: self , action: #selector( dismissKeyboard) )
8381 view. addGestureRecognizer ( tapGesture)
8482 leftButton. addTarget ( self , action: #selector( leftButtonTapped) , for: . touchUpInside)
@@ -102,18 +100,14 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi
102100 private func bind( ) {
103101 let outputSubject = viewModel. transform ( with: inputSubject. eraseToAnyPublisher ( ) )
104102 outputSubject. receive ( on: DispatchQueue . main) . sink { [ weak self] output in
105- guard let strongSelf = self else { return }
103+ guard let self else { return }
106104 switch output {
107- case . showChatHistory( let chatHistory) : self ? . chatHistoryTableView. setChatHistory ( item: chatHistory)
105+ case . showChatHistory( let chatHistory) : chatHistoryTableView. setChatHistory ( item: chatHistory)
108106 case . showToast( let message, let success) :
109- self ? . showToast ( message: message)
110- if success { self ? . navigationController? . popViewController ( animated: true ) }
107+ showToast ( message: message)
108+ if success { navigationController? . popViewController ( animated: true ) }
111109 }
112110 } . store ( in: & subscriptions)
113- blockModalViewController. rightButtonPublisher. sink { [ weak self] _ in
114- guard let self = self else { return }
115- present ( blockCheckModalViewController, animated: true )
116- } . store ( in: & subscriptions)
117111
118112 blockCheckModalViewController. buttonPublihser. sink { [ weak self] in
119113 guard let self else { return }
@@ -130,6 +124,8 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi
130124 } . store ( in: & subscriptions)
131125
132126 chatHistoryTableView. imageTapPublisher. sink { [ weak self] imageUrl in
127+ self ? . dismissKeyboard ( )
128+
133129 let zoomedImageViewController = ZoomedImageViewControllerB ( shouldShowTitle: false )
134130 zoomedImageViewController. configure ( urls: [ imageUrl] , initialIndexPath: IndexPath ( row: 0 , section: 0 ) )
135131 self ? . present ( zoomedImageViewController, animated: true , completion: nil )
@@ -140,16 +136,17 @@ final class ChatViewController: UIViewController, UITextViewDelegate, PHPickerVi
140136extension ChatViewController {
141137
142138 @objc private func sendButtonTapped( ) {
143- if textView. text. isEmpty { return }
139+ if textView. text. isEmpty || textView . textColor == . appColor ( . neutral500 ) { return }
144140 inputSubject. send ( . sendMessage( textView. text, false ) )
145141 textView. text = " "
146- textViewHeightConstraint. constant = 40
142+ textViewHeightConstraint. constant = 36
147143 UIView . animate ( withDuration: 0.1 ) {
148144 self . view. layoutIfNeeded ( )
149145 }
150146 }
151147
152148 @objc private func leftButtonTapped( ) {
149+ dismissKeyboard ( )
153150 presentImagePicker ( )
154151 }
155152
@@ -182,16 +179,19 @@ extension ChatViewController{
182179 }
183180
184181 @objc private func rightButtonTapped( ) {
182+ dismissKeyboard ( )
183+
185184 present ( blockCheckModalViewController, animated: true )
186185 }
187186 @objc private func keyboardWillShow( _ notification: Notification ) {
188187 guard let keyboardFrame = notification. userInfo ? [ UIResponder . keyboardFrameEndUserInfoKey] as? CGRect else { return }
189188 let keyboardHeight = keyboardFrame. height
190- adjustInputViewPosition ( up : true , height : keyboardHeight )
191- }
192- @ objc private func keyboardWillHide ( _ notification : Notification ) {
193- adjustInputViewPosition ( up : false , height : 0 )
189+
190+ UIView . animate ( withDuration : 0.3 ) { [ weak self ] in
191+ self ? . chatHistoryTableView . contentOffset . y += ( keyboardHeight - UIApplication . bottomSafeAreaHeight ( ) )
192+ }
194193 }
194+
195195 private func adjustInputViewPosition( up: Bool , height: CGFloat ) {
196196 messageInputBottomConstraint. constant = up ? - height : 0
197197 UIView . animate ( withDuration: 0.3 ) {
@@ -201,12 +201,20 @@ extension ChatViewController{
201201 @objc internal override func dismissKeyboard( ) {
202202 textView. resignFirstResponder ( )
203203 }
204+
205+ func textViewDidBeginEditing( _ textView: UITextView ) {
206+ if textView. textColor == . appColor( . neutral500) {
207+ textView. text = " "
208+ textView. textColor = . appColor( . neutral800)
209+ }
210+ }
211+
204212 func textViewDidChange( _ textView: UITextView ) {
205213 let size = CGSize ( width: textView. frame. width, height: . infinity)
206214 let estimatedSize = textView. sizeThatFits ( size)
207215
208216 let maxHeight : CGFloat = 120
209- let minHeight : CGFloat = 40
217+ let minHeight : CGFloat = 36
210218 if estimatedSize. height >= maxHeight {
211219 textViewHeightConstraint. constant = maxHeight
212220 textView. isScrollEnabled = true
@@ -220,12 +228,27 @@ extension ChatViewController{
220228 self . view. layoutIfNeeded ( )
221229 }
222230 }
231+
232+ func textViewDidEndEditing( _ textView: UITextView ) {
233+ if textView. text == " " {
234+ textView. text = " 메시지 보내기 "
235+ textView. textColor = . appColor( . neutral500)
236+ }
237+ }
238+
239+ func textViewShouldEndEditing( _ textView: UITextView ) -> Bool {
240+ if textView. text == " " {
241+ textView. text = " 메시지 보내기 "
242+ textView. textColor = . appColor( . neutral500)
243+ }
244+ return true
245+ }
223246}
224247
225248extension ChatViewController {
226249
227250 private func setUpLayOuts( ) {
228- [ chatHistoryTableView, messageInputView] . forEach {
251+ [ bottomBackgrounView , chatHistoryTableView, messageInputView] . forEach {
229252 view. addSubview ( $0)
230253 }
231254 [ leftButton, textView, sendButton] . forEach {
@@ -236,29 +259,33 @@ extension ChatViewController {
236259 private func setUpConstraints( ) {
237260 chatHistoryTableView. snp. makeConstraints { make in
238261 make. top. leading. trailing. equalToSuperview ( )
239- make. bottom. equalTo ( messageInputView. snp. top) . offset ( - 16 )
262+ make. bottom. equalTo ( messageInputView. snp. top)
240263 }
241264 messageInputView. snp. makeConstraints { make in
242- make. leading. trailing. equalToSuperview ( ) . inset ( 8 )
265+ make. leading. trailing. equalToSuperview ( )
243266 make. height. greaterThanOrEqualTo ( 50 )
244- messageInputBottomConstraint = make. bottom. equalTo ( view. safeAreaLayoutGuide ) . constraint . layoutConstraints . first
267+ make. bottom. equalTo ( view. keyboardLayoutGuide . snp . top )
245268 }
246269 leftButton. snp. makeConstraints { make in
247- make. leading. equalTo ( messageInputView ) . offset ( 8 )
248- make. centerY . equalTo ( messageInputView)
270+ make. leading. equalToSuperview ( ) . offset ( 24 )
271+ make. bottom . equalTo ( messageInputView. snp . bottom ) . offset ( - 8 )
249272 make. size. equalTo ( 32 )
250273 }
251274 sendButton. snp. makeConstraints { make in
252- make. trailing. equalTo ( messageInputView ) . offset ( - 8 )
253- make. centerY . equalTo ( messageInputView)
275+ make. trailing. equalToSuperview ( ) . offset ( - 24 )
276+ make. bottom . equalTo ( messageInputView. snp . bottom ) . offset ( - 8 )
254277 make. size. equalTo ( 32 )
255278 }
256279 textView. snp. makeConstraints { make in
257280 make. leading. equalTo ( leftButton. snp. trailing) . offset ( 8 )
258281 make. trailing. equalTo ( sendButton. snp. leading) . offset ( - 8 )
259282 make. top. equalTo ( messageInputView) . offset ( 8 )
260283 make. bottom. equalTo ( messageInputView) . offset ( - 8 )
261- textViewHeightConstraint = make. height. equalTo ( 40 ) . priority ( . high) . constraint. layoutConstraints. first
284+ textViewHeightConstraint = make. height. equalTo ( 36 ) . priority ( . high) . constraint. layoutConstraints. first
285+ }
286+ bottomBackgrounView. snp. makeConstraints {
287+ $0. leading. trailing. bottom. equalToSuperview ( )
288+ $0. top. equalTo ( messageInputView. snp. bottom)
262289 }
263290 }
264291 private func configureView( ) {
0 commit comments