본문 바로가기
Xcode/IOS

IOS) CollectionView에서 특정 Cell에 내용 넣기 In Code

by 후르륵짭짭 2021. 1. 9.
728x90
반응형

안녕하세요. 후르륵짭짭입니다.

최근에 여러 일이 있어서 글을 일주일 동안 못 올렸네요 ㅎㅎㅎ

이번에는 그냥 다양한 기능을 복합적으로 적어 올리려고 합니다.

그냥 다양한 기능들이 있으니,,, 읽고 넘어가시면 될 것 같습니다.

그래서 위에 기능들이 만들어 질 수 있도록 하겠습니다.

 

** 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에는 아래와 같이 정리 할 수 있습니다.

Summary

Storage for values or objects related to this notification. (Notification과 관련된 값이나 객체를 저장한다.)

Discussion

The user information dictionary stores any additional objects that objects receiving the notification might use.
(User info는 딕셔너리 타입이고 이 딕셔너리는 Notification에서 사용에 받아지는 객체를 저장한다.)

즉 UserInfo는 Dictionary 타입이고 이 딕셔너리에는 값이나 객체가 저장 되어 있어서 사용하는 것이라고 알 수 있습니다.

 

그럼 우리가 필요한 것은 Keyboard의 높이와 가로의 정보인 Frame이기 때문에 아래와 같이 가져 옵니다.

if let keyBoardFrame : NSValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
  ,,, 
}

이렇게 UIResponder.keyBoardFrameEndUserInfoKey 를 가져오면 되는데, 이것은 아래 처럼 정의 되어 있다.

Summary

The 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.
(스크린 좌표의 키보드 사각형의 끝 부분의 정의인 CGRect를 포함하는 NSValue 입니다. - 그냥 CGRect 타입은 키보드의 Frame을 가지고 있다.)
(프레임 사격형은 현재 디바이스를 반영한다.)

즉 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

 

Swift 3.0 Result of call is unused

I am writing in swift 3.0 i have this code which gives me the warning result of call is unused public override init(){ super.init() } public init(annotations: [

stackoverflow.com

 

NSLayout에 대한 설명 :

developer.apple.com/documentation/uikit/nslayoutconstraint

 

Apple Developer Documentation

 

developer.apple.com

 

LayoutIfNeed()에 대한 설명 : 

jongchanpark.tistory.com/42

 

오토 레이아웃과 함께 UIView의 애니메이션 기능 사용하기

UIView에는 animate(withDuration:animations:completion:) 라는 애니메이션 메서드가 있습니다. 프레임(frame) 베이스의 앱에서는 아주 직관적이고 편리한 메서드입니다. 하지만 오토 레이아웃을 쓰게 되면 좀

jongchanpark.tistory.com

 

Keyboard 올리는 방법

developer-fury.tistory.com/22

 

[Swift] TextField 등이 키보드에 가려지는 현상

TextField 키보드에 화면이 가려지는 현상 해결하기 Record  작성일 2019. 08. 28 (수) Swift 버전 Swift 5 Xcode 버전 10.3 안녕하세요. Fury입니다 :] 오늘은 Keyboard 때문에 고생하시는 분들이 많을 것으로..

developer-fury.tistory.com

 

 

728x90
반응형

댓글