Reputation: 992
I am using this code Swift 3 - CollectionView data source did not return a valid cell UPDATED FROM TERENCE ANSWER: In viewDidLoad I put
collectionView?.translatesAutoresizingMaskIntoConstraints = false
messageInputContainerView.translatesAutoresizingMaskIntoConstraints = false
view.addConstraintsWithFormat(format: "H:|-0-[v0]-0-|", views: messageInputContainerView)
view.addConstraintsWithFormat(format: "H:|-0-[v0]-0-|", views: collectionView!)
let constraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[v1]-0-[v0(48)]", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":messageInputContainerView, "v1": collectionView!])
constraints[2].identifier = "heightConstraint"
view.addConstraints(constraints)
bottomConstraint = NSLayoutConstraint(item: messageInputContainerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
with bottomconstraint I modify the messageInputContainerView to goes up when the keyboard appear
messageInputContainerView.addConstraintsWithFormat(format: "H:|-8-[v0(30)]-8-[v1][v2(60)]|", views: sendPicButton, inputTextView, sendTextButton)
messageInputContainerView.addConstraintsWithFormat(format: "V:|-6-[v0]|", views: inputTextView)
messageInputContainerView.addConstraintsWithFormat(format: "V:[v0]-6-|", views: sendTextButton)
messageInputContainerView.addConstraintsWithFormat(format: "V:[v0]-14-|", views: sendPicButton)
messageInputContainerView.addConstraintsWithFormat(format: "H:|[v0]|", views: topBorderView)
messageInputContainerView.addConstraintsWithFormat(format: "V:|[v0(0.5)]", views: topBorderView)
On the first screen I have space between last message and messageInputContainerView. How to fix it?
On the second screen messageInputContainerView is already over the collection view
I am modifying constraints[2].identifier = "heightConstraint"
in textViewDidChange method to change the position of the messageInputContainerView when keyboard appear
How to fix it to be attached, because now its over the mesagess(collectionView) ?
Upvotes: 2
Views: 1628
Reputation: 77647
One approach would be to change the ChatLogController from a subclass of UICollectionViewController to a plain UIViewController, and then add the CollectionView as a subview, add the MessageInputContainerView as a subview, and then pin the bottom of the Collection view to the top of the Input view.
Here is a modified version of the ChatLogViewController.swift class... it's from the code at Step 7 (https://www.letsbuildthatapp.com/course_video?id=152) of that sample app. You should be able to drop it into your project pretty much as-is... just change the loading line from:
let controller = ChatLogController(collectionViewLayout: layout)
to
let controller = MyChatLogController()
Also note: this does not have the variable-height textfield... but if you implement it in the same way as you did in your version, it should work just fine (remember, the bottom of the Collection view will now be "pinned" to the top of the Input container view).
Edit: I made a few changes since my original post - this now supports the auto-height-adjusting input field.
import UIKit
class MyChatLogController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITextViewDelegate {
fileprivate var collectionView: UICollectionView?
fileprivate let cellId = "cellId"
var friend: Friend? {
didSet {
navigationItem.title = friend?.name
messages = friend?.messages?.allObjects as? [Message]
messages = messages?.sorted(by: {$0.date!.compare($1.date! as Date) == .orderedAscending})
}
}
var messages: [Message]?
let messageInputContainerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}()
let inputTextView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 18)
return textView
}()
let sendButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Send", for: UIControlState())
let titleColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 1)
button.setTitleColor(titleColor, for: UIControlState())
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
return button
}()
var bottomConstraint: NSLayoutConstraint?
var heightConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
tabBarController?.tabBar.isHidden = true
let layout = UICollectionViewFlowLayout()
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
// make sure collectionView creation was successful
guard let cv = collectionView else {
return
}
cv.delegate = self
cv.dataSource = self
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = UIColor.white
view.addSubview(cv)
cv.register(MyChatLogMessageCell.self, forCellWithReuseIdentifier: cellId)
view.addSubview(messageInputContainerView)
view.addConstraintsWithFormat("H:|[v0]|", views: messageInputContainerView)
view.addConstraintsWithFormat("H:|[v0]|", views: cv)
view.addConstraintsWithFormat("V:|[v0]-(-32)-[v1]", views: cv, messageInputContainerView)
bottomConstraint = NSLayoutConstraint(item: messageInputContainerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
heightConstraint = NSLayoutConstraint(item: messageInputContainerView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 60)
view.addConstraint(heightConstraint!)
setupInputComponents()
inputTextView.delegate = self
inputTextView.isScrollEnabled = false
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let indexPath = IndexPath(item: self.messages!.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
}
func handleKeyboardNotification(_ notification: Notification) {
if let userInfo = notification.userInfo {
let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
bottomConstraint?.constant = isKeyboardShowing ? -keyboardFrame!.height : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed) in
if isKeyboardShowing {
let indexPath = IndexPath(item: self.messages!.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
}
})
}
}
func textViewDidChange(_ textView: UITextView) { //Handle the text changes here
let sz = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
heightConstraint?.constant = max(60, sz.height)
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed) in
let indexPath = IndexPath(item: self.messages!.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
inputTextView.endEditing(true)
}
fileprivate func setupInputComponents() {
let topBorderView = UIView()
topBorderView.backgroundColor = UIColor(white: 0.75, alpha: 1.0)
messageInputContainerView.addSubview(inputTextView)
messageInputContainerView.addSubview(sendButton)
messageInputContainerView.addSubview(topBorderView)
messageInputContainerView.addConstraintsWithFormat("H:|-8-[v0][v1(60)]|", views: inputTextView, sendButton)
messageInputContainerView.addConstraintsWithFormat("V:|[v0]|", views: inputTextView)
messageInputContainerView.addConstraintsWithFormat("V:|[v0]|", views: sendButton)
messageInputContainerView.addConstraintsWithFormat("H:|[v0]|", views: topBorderView)
messageInputContainerView.addConstraintsWithFormat("V:|[v0(0.5)]", views: topBorderView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = messages?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyChatLogMessageCell
cell.messageTextView.text = messages?[indexPath.item].text
if let message = messages?[indexPath.item], let messageText = message.text, let profileImageName = message.friend?.profileImageName {
cell.profileImageView.image = UIImage(named: profileImageName)
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
if message.isSender == nil || !message.isSender!.boolValue {
cell.messageTextView.frame = CGRect(x: 48 + 8, y: 0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x: 48 - 10, y: -4, width: estimatedFrame.width + 16 + 8 + 16, height: estimatedFrame.height + 20 + 6)
cell.profileImageView.isHidden = false
// cell.textBubbleView.backgroundColor = UIColor(white: 0.95, alpha: 1)
cell.bubbleImageView.image = MyChatLogMessageCell.grayBubbleImage
cell.bubbleImageView.tintColor = UIColor(white: 0.95, alpha: 1)
cell.messageTextView.textColor = UIColor.black
} else {
//outgoing sending message
cell.messageTextView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 16 - 8, y: 0, width: estimatedFrame.width + 16, height: estimatedFrame.height + 20)
cell.textBubbleView.frame = CGRect(x: view.frame.width - estimatedFrame.width - 16 - 8 - 16 - 10, y: -4, width: estimatedFrame.width + 16 + 8 + 10, height: estimatedFrame.height + 20 + 6)
cell.profileImageView.isHidden = true
// cell.textBubbleView.backgroundColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 1)
cell.bubbleImageView.image = MyChatLogMessageCell.blueBubbleImage
cell.bubbleImageView.tintColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 1)
cell.messageTextView.textColor = UIColor.white
}
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let messageText = messages?[indexPath.item].text {
let size = CGSize(width: 250, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let estimatedFrame = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 18)], context: nil)
return CGSize(width: view.frame.width, height: estimatedFrame.height + 20)
}
return CGSize(width: view.frame.width, height: 100)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(8, 0, 0, 0)
}
}
class MyChatLogMessageCell: BaseCell {
let messageTextView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 18)
textView.text = "Sample message"
textView.backgroundColor = UIColor.clear
return textView
}()
let textBubbleView: UIView = {
let view = UIView()
view.layer.cornerRadius = 15
view.layer.masksToBounds = true
return view
}()
let profileImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = 15
imageView.layer.masksToBounds = true
return imageView
}()
static let grayBubbleImage = UIImage(named: "bubble_gray")!.resizableImage(withCapInsets: UIEdgeInsetsMake(22, 26, 22, 26)).withRenderingMode(.alwaysTemplate)
static let blueBubbleImage = UIImage(named: "bubble_blue")!.resizableImage(withCapInsets: UIEdgeInsetsMake(22, 26, 22, 26)).withRenderingMode(.alwaysTemplate)
let bubbleImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = MyChatLogMessageCell.grayBubbleImage
imageView.tintColor = UIColor(white: 0.90, alpha: 1)
return imageView
}()
override func setupViews() {
super.setupViews()
addSubview(textBubbleView)
addSubview(messageTextView)
addSubview(profileImageView)
addConstraintsWithFormat("H:|-8-[v0(30)]", views: profileImageView)
addConstraintsWithFormat("V:[v0(30)]|", views: profileImageView)
profileImageView.backgroundColor = UIColor.red
textBubbleView.addSubview(bubbleImageView)
textBubbleView.addConstraintsWithFormat("H:|[v0]|", views: bubbleImageView)
textBubbleView.addConstraintsWithFormat("V:|[v0]|", views: bubbleImageView)
}
}
Upvotes: 1
Reputation: 443
Maybe you can try this: container add these "V:|-0-[collectionView]-0-[inputview(>=48)]-0-|"
and "H:|-0-[collectionView]-0-|"
with "H:|-0-[inputview]-0-|"
where container does not necessary set auto resizing mask false unless your container also need it. But both collection view and input view need to set it false to make auto constraints work.
Upvotes: 2