안녕하세요. 후르륵짭짭입니다.
최근에 여러 일이 있어서 글을 일주일 동안 못 올렸네요 ㅎㅎㅎ
이번에는 그냥 다양한 기능을 복합적으로 적어 올리려고 합니다.
그냥 다양한 기능들이 있으니,,, 읽고 넘어가시면 될 것 같습니다.
그래서 위에 기능들이 만들어 질 수 있도록 하겠습니다.
** TextField를 담는 View 생성 **
//TextField를 담는 ContainerView
let messageInputContainerView : UIView = {
let view = UIView()
view.backgroundColor = .lightText
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
// ContainerView의 AutoLayout
var bottomLayoutConstrain : NSLayoutConstraint?
private func setContainerView(){
view.addSubview(messageInputContainerView)
bottomLayoutConstrain = messageInputContainerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0)
NSLayoutConstraint.activate([
messageInputContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
messageInputContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
messageInputContainerView.heightAnchor.constraint(equalToConstant: 48),
bottomLayoutConstrain!
])
}
위의 코드를 보시면 MessageInputContainerView라는 이름으로 TextField를 담는 View를 하나 생성 해줍니다.
그리고 AutoLayout을 전체 뷰의 상하좌우로 제약조건을 걸어줍니다.
근데 여기서 bottomLayoutConstrain은 safe 영역의 bottom이랑 동일하게 처음 생성을 해주지만 변수로 빼줍니다.
왜냐하면 나중에 이 제약 조건을 변경 하기 위해서 입니다.
// CollectionView
let chatcollectionView : UICollectionView = {
let flowlayOut = UICollectionViewFlowLayout()
flowlayOut.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowlayOut)
collectionView.backgroundColor = .white
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
// CollectionView AutoLayout
private func collectionViewAuto(){
view.addSubview(chatcollectionView)
NSLayoutConstraint.activate([
chatcollectionView.topAnchor.constraint(equalTo: view.topAnchor),
chatcollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
chatcollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// chatcollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
chatcollectionView.bottomAnchor.constraint(equalTo: messageInputContainerView.topAnchor)
])
setRegister()
}
여기서 CollectionView는 messageInputContainerView의 top 부분을 기준으로 Bottom을 설정해주었습니다.
** ContainerView에 Send 버튼과 TextField 넣기 **
// TextField 생성
let inputTextField : UITextField = {
let textField = UITextField()
textField.placeholder = "Enter Here"
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
// TextField AutoLayout 설정
private func setTextField(){
messageInputContainerView.addSubview(inputTextField)
NSLayoutConstraint.activate([
inputTextField.leadingAnchor.constraint(equalTo: messageInputContainerView.leadingAnchor, constant: 10),
inputTextField.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor),
inputTextField.topAnchor.constraint(equalTo: messageInputContainerView.topAnchor),
inputTextField.bottomAnchor.constraint(equalTo: messageInputContainerView.bottomAnchor)
])
}
위에 코드 처럼 TextField를 Container View 내부에 넣습니다.
// Button을 설정
lazy var sendButton : UIButton = {
let button = UIButton()
button.setTitle("Send", for: .normal)
button.setTitleColor(.systemBlue, for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 18)
button.addTarget(self, action: #selector(selectBtn), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// Button AutoLayout
private func setButton(){
messageInputContainerView.addSubview(sendButton)
NSLayoutConstraint.activate([
sendButton.topAnchor.constraint(equalTo: messageInputContainerView.topAnchor),
sendButton.trailingAnchor.constraint(equalTo: messageInputContainerView.trailingAnchor, constant: -10),
sendButton.bottomAnchor.constraint(equalTo: messageInputContainerView.bottomAnchor),
sendButton.widthAnchor.constraint(equalToConstant: 50)
])
}
그리고 버튼도 위에 처럼 생성을 해줍니다.
그런데 여기서 예전에도 다룬 적이 있지만, Button에 addTarget을 할 때는 현재 클래스에서 작동을 한다는 말을 넣기 위해 self를 사용하는데, 이 때문에 closure 밖으로 addTarget 메소드를 빼던가, 아니면 처음 사용 할때 설정해준다는 lazy var로 해주셔야합니다.
이렇게 해주시면 필요한 View 구성은 끝 마쳤습니다.
** KeyBoard의 높이 구하기 **
일단 KeyBoard를 나오게 하는 방법에 대해 먼저 알아보도록 하겠습니다.
예전에도 다룬 적이 있는데, 다시 다루겠습니다.
일단 키보드의 사용을 알려줘야하기 때문에 KeyBoard Notification을 사용해야 합니다.
private func addKeyBoardNotification(){
1) 키보드가 올라올 때
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
2) 키보드가 내려갈 때
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
이렇게 두개를 생성 해줍니다.
(1)은 키보드가 올라 갈 때 (2)은 키보드가 내려갈 때 사용하는 겁니다.
둘다 Notification으로 되어 있기 때문에 View에 알려줄 수 있는 겁니다.
그럼 KeyBoard가 올라올 때를 먼저 다뤄보도록 하겠습니다.
@objc private func keyBoardWillShow(_ noti : Notification){
guard let userInfo = noti.userInfo else {return}
if let keyBoardFrame : NSValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
print( "cgRectVale : " ,keyBoardFrame.cgRectValue)
print("widht : " ,keyBoardFrame.cgRectValue.width)
print("height : " ,keyBoardFrame.cgRectValue.height)
bottomLayoutConstrain?.isActive = false
UIView.animate(withDuration: 0) {
self.bottomLayoutConstrain = self.messageInputContainerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -keyBoardFrame.cgRectValue.height)
self.bottomLayoutConstrain?.isActive = true
self.view.layoutIfNeeded()
} completion: { _ in
let indexPathItem = IndexPath(item: self.messages!.count - 1, section: 0)
self.chatcollectionView.scrollToItem(at: indexPathItem, at: .bottom, animated: true)
}
}
}
위와 같이 코드를 작성해 본다면,
일단 KeyBoard는 Notification이기 때문에 OBJ-C 함수에 notification을 변수로 보내줍니다.
그러면 Notification에 userInfo라는 변수가 있는데, 이 userInfo에는 아래와 같이 정리 할 수 있습니다.
SummaryStorage for values or objects related to this notification. (Notification과 관련된 값이나 객체를 저장한다.) |
DiscussionThe user information dictionary stores any additional objects that objects receiving the notification might use. |
즉 UserInfo는 Dictionary 타입이고 이 딕셔너리에는 값이나 객체가 저장 되어 있어서 사용하는 것이라고 알 수 있습니다.
그럼 우리가 필요한 것은 Keyboard의 높이와 가로의 정보인 Frame이기 때문에 아래와 같이 가져 옵니다.
if let keyBoardFrame : NSValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
,,,
}
이렇게 UIResponder.keyBoardFrameEndUserInfoKey 를 가져오면 되는데, 이것은 아래 처럼 정의 되어 있다.
SummaryThe key for an NSValue object containing a CGRect that identifies the ending frame rectangle of the keyboard in screen coordinates. The frame rectangle reflects the current orientation of the device. |
즉 NSValue 타입이기 때문에 NSValue로 타입 캐스팅 해준다면 다음과 같이 Keyboard의 크기를 구할 수 있습니다.
print( "cgRectVale : " ,keyBoardFrame.cgRectValue)
print("widht : " ,keyBoardFrame.cgRectValue.width)
print("height : " ,keyBoardFrame.cgRectValue.height)
//결과
cgRectVale : (0.0, 476.0, 375.0, 336.0)
widht : 375.0
height : 336.0
** 키보드의 높이에 따라 ContainerView 위치 변경하기 **
CotinaerView의 Bottom 제약 조건을 아래와 같이 변수로 지정 해줬습니다.
var bottomLayoutConstrain : NSLayoutConstraint?
왜냐하면 제약 조건 변경을 위해서 입니다.
그런데 제약 조건을 변경 해주기 위해서는 그냥 새롭게 변수를 재정의 한다고 되지 않습니다.
해주기 위한 방법은 제약조건을 먼저 종료시킨 다음에 재정의하고 활성화 시켜줘야합니다.
//제약조건 종료
bottomLayoutConstrain?.isActive = false
UIView.animate(withDuration: 0) {
//제약조건 재정의
self.bottomLayoutConstrain = self.messageInputContainerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -keyBoardFrame.cgRectValue.height)
//제약조건 활성화
self.bottomLayoutConstrain?.isActive = true
//View 재구성
self.view.layoutIfNeeded()
} completion: { _ in
let indexPathItem = IndexPath(item: self.messages!.count - 1, section: 0)
self.chatcollectionView.scrollToItem(at: indexPathItem, at: .bottom, animated: true)
}
위에서 키보드의 높이를 구했으니, 새로운 제약조건에 constant를 -키보드높이로 해줍니다.
(왜냐하면 bottom 양수라면 아래로 더 내려가고 -면 위로 올라가기 때문 입니다.)
그리고 Constraint를 변경 한다면 UIView.animate()로 자연스럽게 변경 될 수 있도록 합니다.
그리고 반드시 layoutIfNeeded()를 적어줘서 View를 강제적으로 재구성해주도록 합니다.
// Keyboard 숨기기
@objc private func keyBoardWillHide(_ noti : Notification){
bottomLayoutConstrain?.isActive = false
self.bottomLayoutConstrain = messageInputContainerView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: 0)
UIView.animate(withDuration: 0) {
self.bottomLayoutConstrain?.isActive = true
self.view.layoutIfNeeded()
}
}
방법은 위와 거의 동일합니다.
** CollectionView의 특정 셀 위치로 이동하기 **
CollectionView에서 자동으로 스크롤 할 수 있도록 해주는 방법이 있습니다.
일단 저는 맨 밑으로 스크롤을 강제적으로 이동 시킬 것이기 때문에,
let indexPathItem = IndexPath(item: self.messages!.count - 1, section: 0)
self.chatcollectionView.scrollToItem(at: indexPathItem, at: .bottom, animated: true)
collectionView의 scrollToItem(at : IndexPath , at : 위치 , animate : Bool)
이렇게 되어 있기 때문에 IndexPath()함수를 사용해서 만들어 줍니다.
우리는 section이 하나 밖에 없기 때문에 위와 같이 해줍니다.
** CollectionView의 특정 셀에 새로운 Cell 추가하기 **
위에서 TextField와 전송하기 버튼을 생성 했습니다.
//버튼 Target 함수 내용
@objc private func selectBtn(){
guard let text = inputTextField.text else {return}
print(text)
let tempMess = cdControl.createMessage(friend: friend!, pluse: 0, detail: text, isSender: true)
messages?.append(tempMess)
let itemLocation = messages!.count - 1
let insertionIndexPath = IndexPath(item: itemLocation, section: 0)
//특정한 라인에 아이템 넣는 방법
chatcollectionView.insertItems(at: [insertionIndexPath])
self.chatcollectionView.scrollToItem(at: insertionIndexPath, at: .bottom, animated: true)
inputTextField.text = ""
}
일단 CoreData에 데이터를 저장해서
message변수의 마지막에 내용을 넣고
IndexPath()를 만들어 준 다음
collectionView.insertItem을 사용해서 넣어줍니다.
- 만약 Return 값이 있지만 꼭 받을 필요가 없을 때는 @discardableResult 사용하면 됩니다. -
예를 들어 아래 처럼 사용해주면 됩니다.
@discardableResult
func createMessage(friend : Friend , pluse : Double , detail : String, isSender : Bool = false) -> Message{
let newMessage = Message(context : context)
newMessage.date = Date().addingTimeInterval(pluse)
newMessage.detail = detail
newMessage.chat_friend = friend
newMessage.isSender = isSender
friend.addToChat_message(newMessage)
do{
try context.save()
}
catch let err{
context.rollback()
print(err.localizedDescription)
}
return newMessage
}
그리고 CollectionView의 insertItem을 한다면 reloadData()를 해줄 필요가 없고 여러개의 값을 추가해줄 수 있습니다.
하지만 값이 온전히 추가 된 것이 아니기 때문에 어느 공간에 저장해주는 것이 좋습니다.
chatcollectionView.insertItems(at: [insertionIndexPath])
이제 메인 디쉬의 내용을 모두 정리 했습니다.
추가적으로 알면 좋은 것들에 대해 적어 보도록 하겠습니다.
** Navigation에 오른쪽 버튼 생성 하기 **
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Simulate", style: .plain, target: self, action: #selector(simuateBtn))
Navigation의 오른쪽에 버튼을 추가하는 방법은 위에 처럼 해주면 됩니다.
** TabBar 숨기는 방법 **
tabBarController?.tabBar.isHidden = true
TabBar를 숨기는 방법도 위에 처럼 간단하게 할 수 있습니다.
참고 사이트 :
Return 값을 사용하지 않을 때, 오류 없애는 방법 :
stackoverflow.com/questions/38192751/swift-3-0-result-of-call-is-unused
NSLayout에 대한 설명 :
developer.apple.com/documentation/uikit/nslayoutconstraint
LayoutIfNeed()에 대한 설명 :
Keyboard 올리는 방법
'Xcode > IOS' 카테고리의 다른 글
IOS) Literal에 대해서 알아보자! (0) | 2021.01.31 |
---|---|
IOS) NSFetchedResultsController을 이용하자! (0) | 2021.01.12 |
IOS) 동적인 Collection Cell 크기 만들기 - (부정확) (0) | 2020.12.31 |
IOS) Custom TabBar 만들기 in Code (0) | 2020.12.29 |
IOS) Dynamic UIScrollView in Code (0) | 2020.12.25 |
댓글