2

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)

screen 1

On the first screen I have space between last message and messageInputContainerView. How to fix it?

screen 2

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) ?

Community
  • 1
  • 1
Bogdan Bogdanov
  • 882
  • 11
  • 36
  • 79
  • Does your log console show the constraints conflict warning message? Well, I confuse that shouldn't your collection view is above your text input view? But your first constraint is opposite. – Terence Feb 10 '17 at 17:07
  • No conflicts. I edited my question and I added all constraints of the view. Yes now my textInputView is above collection view. I want to be attached to collectionView. – Bogdan Bogdanov Feb 10 '17 at 17:22
  • Did you set collection view's translatesAutoresizingMaskIntoConstraints false? Your top four constraints are there... and maybe you should try "V:[v1][v0(48)]" instead – Terence Feb 10 '17 at 17:30
  • when i set collectionView?.translatesAutoresizingMaskIntoConstraints = false the screen becomes black , when i set [v1] to be before v0 the height constraint of messageInputContainerView that I modify isn't working. – Bogdan Bogdanov Feb 10 '17 at 19:17
  • I guess your view constraints are not completed. Apparently, you should go to check how does auto constraints work properly. – Terence Feb 10 '17 at 19:20
  • maybe `` is the problem, but I don't know how to change it ? – Bogdan Bogdanov Feb 10 '17 at 19:25
  • that is because your keyboard is visible, it is completely normal. – Terence Feb 10 '17 at 19:30
  • if you put this controller in a demo repo and share, i can fix the issue. i mean i know where is the problem but if i will explain, this conversation will continue too long. still i will try. – Mahesh Agrawal Feb 15 '17 at 12:04
  • @BogdanBogdanov - wonder if you looked at my answer? – DonMag Feb 20 '17 at 13:59

2 Answers2

2

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.

Terence
  • 443
  • 3
  • 13
  • 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)]-0-|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0":messageInputContainerView, "v1": collectionView!]) collectionView?.translatesAutoresizingMaskIntoConstraints = false inputTextView.translatesAutoresizingMaskIntoConstraints = false` not working like this i am receiving white view – Bogdan Bogdanov Feb 10 '17 at 19:45
  • check the frame of these two views after viewdidappear as well as the container's to see if they have been layout properly – Terence Feb 10 '17 at 19:48
  • (0.0, 0.0, 375.0, 0.0) collection (46.0, 6.0, 269.0, 100.0) input and I cant see the collection view the inputtextView is stuck in the bottom of the view – Bogdan Bogdanov Feb 10 '17 at 19:58
  • that is totally incorrect, do you have anything else in the container?, their width are not even the same, that is impossible if you set it right, I don't know what had happened to you. Maybe you have add other constraints that affect the final size. – Terence Feb 10 '17 at 20:06
  • in viewdidload after setting the constraints I receive frames (0.0, 0.0, 375.0, 667.0) (0.0, 0.0, 0.0, 0.0) – Bogdan Bogdanov Feb 10 '17 at 20:16
  • auto constraint will adjust view's frame after `viewDidLoad`, but it would be stable after `viewDidAppear`, what you need to look at is why the two views' frame is changed even with the constraints above, probably you have some other constraints affect them. – Terence Feb 10 '17 at 20:33
  • is your `messageInputContainerView` and `inputTextView` the same thing? – Terence Feb 10 '17 at 20:34
  • yes yes I found the mistake from comment 1 and I fixed it , but still is white screen and I am seeing only messageInputContainerView. I've put print(view.constraint) in viewdidload and viewdidappear and the only difference is the last constraints which I will add to the question check it again – Bogdan Bogdanov Feb 10 '17 at 20:50
  • Apparently, messageInputContainerView take as much height as possible, something in it is pretty large so that it makes messageInputContainerView to take all height, changes it t0 "V:|-0-[v1]-0-[v0(48)]-0-|" would show the collection view, but since your messageInputContainerView has flexible height according to text input, so you should look at subviews of messageInputContainerView to see which one get all height – Terence Feb 10 '17 at 21:08
  • it must be one or more of these constraints force it large `messageInputContainerView.addConstraintsWithFormat(format: "V:|-6-[v0]|", views: inputTextView) messageInputContainerView.addConstraintsWithFormat(format: "V:[v0]-6-|", views: sendTextButton) messageInputContainerView.addConstraintsWithFormat(format: "V:[v0]-14-|", views: sendPicButton)` put a constant height on each of them one by one, to test which one it causes the issue. By the way, you can use this library if you want, it is more powerful https://github.com/jessesquires/JSQMessagesViewController – Terence Feb 16 '17 at 14:54
1

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)
    }

}
DonMag
  • 69,424
  • 5
  • 50
  • 86