0

So I'm making a messaging app, and I have 4 class properties:

  1. typingView: UIView!
  2. messageTextView: UITextView!
  3. sendButton: UIButton!
  4. typingViewHeight: NSLayoutConstraint?

The typingView is the inputAccessoryView of the main view, which works! It contains the messageTextView and sendButton. But my problem is...when the number of lines of text in the messageTextView increases, I want the height to increase with an animation (I'm ignoring decreasing for now). And by doing that, I decided to change the height of the typingView.

Detecting a change in contentSize works perfectly, I added an observer by adding this line

messageTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

and listened to it with this override

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)

Inside of this function, I have (this is an over simplification and pseudocode, just assume it works)

UIView.animate(withDuration: keyboardAnimationDuration) {
    self.typingViewHeight?.constaint += (messageTextView.font?.lineHeight)!
}

Here is my code for the customizing the inputAccessoryView:

lazy var typingViewContainer: UIView = {
    // Set up typing view
    typingView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 55))
    typingView.backgroundColor = UIColor.darkGray
    typingView.translatesAutoresizingMaskIntoConstraints = false

    // Set up animated constraints
    typingViewHeight = typingView.heightAnchor.constraint(equalToConstant: typingView.frame.height)
    typingViewHeight?.isActive = true

    // Set up text view
    messageTextView = UITextView()
    messageTextView.translatesAutoresizingMaskIntoConstraints = false
    messageTextView.font = fakeMessageTextView.font
    messageTextView.keyboardType = fakeMessageTextView.keyboardType
    messageTextView.isScrollEnabled = fakeMessageTextView.isScrollEnabled
    messageTextView.alwaysBounceVertical = fakeMessageTextView.alwaysBounceVertical
    messageTextView.text = fakeMessageTextView.text
    messageTextView.textColor = fakeMessageTextView.textColor
    messageTextView.delegate = self
    typingView.addSubview(messageTextView)

    // Constraints
    messageTextView.bottomAnchor.constraint(equalTo: typingView.bottomAnchor, constant: -fakeMessageTextViewBottom.constant).isActive = true
    messageTextView.topAnchor.constraint(equalTo: typingView.topAnchor, constant: fakeMessageTextViewTop.constant).isActive = true
    messageTextView.leftAnchor.constraint(equalTo: typingView.leftAnchor, constant: fakeMessageTextViewBottom.constant).isActive = true
    messageTextView.widthAnchor.constraint(equalToConstant: fakeMessageTextViewWidth.constant).isActive = true

    // Observers
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)  
    messageTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)

    // Set up send button
    sendButton = UIButton(type: fakeSendButton.buttonType)
    sendButton.translatesAutoresizingMaskIntoConstraints = false
    sendButton.setTitle(fakeSendButton.titleLabel?.text!, for: .normal)
    sendButton.titleLabel?.font = fakeSendButton.titleLabel?.font
    sendButton.titleLabel?.shadowColor = fakeSendButton.titleLabel?.shadowColor
    sendButton.addTarget(self, action: #selector(sendPressed(_:)), for: .touchUpInside)
    typingView.addSubview(sendButton)

    // Constraints
    sendButton.heightAnchor.constraint(equalToConstant: fakeSendButtonHeight.constant).isActive = true
    sendButton.widthAnchor.constraint(equalToConstant: fakeSendButtonWidth.constant).isActive = true
    sendButton.bottomAnchor.constraint(equalTo: typingView.bottomAnchor, constant: -fakeSendButtonBottom.constant).isActive = true
    sendButton.rightAnchor.constraint(equalTo: typingView.rightAnchor, constant: -fakeSendButtonRight.constant).isActive = true

    return typingView
}()

override var inputAccessoryView: UIView? {
    get {
        return typingViewContainer
    }
}

override var canBecomeFirstResponder: Bool {
    get {
        return true
    }
}

But when I test this and typing in multiple lines, it prints out this in the console:

2018-04-25 08:52:57.614383-0500 ProxiChat[3351:168967] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
"<NSLayoutConstraint:0x608000280a50 UIView:0x7f8528a2cda0.height == 73   (active)>",
"<NSLayoutConstraint:0x6040000993c0 UIView:0x7f8528a2cda0.height == 55   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x608000280a50 UIView:0x7f8528a2cda0.height == 73   
(active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Why are there two constraints? I only added one. Please help, thanks!

EDIT

Assume that my code for changing the height of the typingView works, since it worked before I changed my view to be a custom inputAccessoryView. I just want to know why it's printing out that error, and how I can fix it from setting two constraints, even though I only added one?

Michael Hsu
  • 950
  • 1
  • 9
  • 25
  • Did you find any solution for this problem? I am in exact situation and couldn't find an answer. – Pravalika May 29 '20 at 17:29
  • Unfortunately it does not appear to be possible. Sorry, it's an iOS limitation! –  Mar 18 '22 at 17:55

2 Answers2

0

When you are creating the view you are setting the height as 55:

typingView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 55))

And then you are setting a height anchor:

typingViewHeight = typingView.heightAnchor.constraint(equalToConstant: typingView.frame.height)
typingViewHeight?.isActive = true

That would be why you are seeing a conflict. Another observation is when setting a height anchor you can activate it in the same line it is created:

typingViewHeight = typingView.heightAnchor.constraint(equalToConstant: typingView.frame.height).isActive = true

This keeps you from writing an extra line of code every time

Zachary Bell
  • 559
  • 4
  • 18
  • You cannot do that to get a constraint `typingViewHeight = typingView.heightAnchor.constraint(equalToConstant: typingView.frame.height).isActive = true` – Aleksandr Honcharov Apr 25 '18 at 14:49
  • That doesn't work for me. Also if I try `typingView = UIView()`, instead of `""` it says `""`. Even when I created a UIView() with no init, it sets a default height to zero? – Michael Hsu Apr 25 '18 at 18:08
0

First things first

Instead of observation I recommend that you use UITextView's delegate method to adjust the height of the container and your text view. That code does the same trick, but I think it's more concise and clean.

func textViewDidChange(_ textView: UITextView) {
        let sizeToFitIn = CGSize(width: textView.bounds.size.width, height: .greatestFiniteMagnitude)
        let newSize = textView.sizeThatFits(sizeToFitIn)
        var newHeight = newSize.height

        ....

        // That part depends on your approach to placing constraints, it's just my example
        textInputHeightConstraint?.constant = newHeight
        inputContainerHeightConstraint?.constant = newHeight + (interfaceFactory.inputContainerHeight - interfaceFactory.textInputMinimumHeight)
    }

Try to update your code like that:

typingViewHeight = typingView.heightAnchor.constraint(equalToConstant: typingView.frame.height)
typingViewHeight?.priority = UILayoutPriority(rawValue: 999)
typingViewHeight?.isActive = true

If it doesn't help, take a look at that answer: https://stackoverflow.com/a/27522511/5147552

Aleksandr Honcharov
  • 2,343
  • 17
  • 30
  • 1
    The code for handling the UITextView's contentSize changing is based off of a lot of other code I have written, so it'll be kind of hard to post that there. I know it works, though, because it worked with the UITextView **before** I added a custom `inputAccessoryView`. My question is, assuming that I'm adjusting the height of the `typingView` (**not** `messageTextView`) correctly, why is it giving me that error and not letting me change the height constraint? – Michael Hsu Apr 25 '18 at 18:07