3

I'm playing around with the new UICollectionViewListCell and UIContentConfigurations and they are awesome, but I can't figure out how to change the default cell height of 44 that UIKit automatically sets for these cells. Here's an example cell:


class TextFieldCell: UICollectionViewListCell {

    struct ContentConfiguration: UIContentConfiguration, Hashable {

        struct Handlers: ContentConfigurationHandlers {
            var onTextChanged: ((_ newText: String) -> Void)?
        }

        var handlers: Handlers?

        var text: String?
        var placeholder: String?
        var keyboardType: UIKeyboardType?

        func makeContentView() -> UIView & UIContentView {
            ContentView(configuration: self)
        }

        func updated(for state: UIConfigurationState) -> TextFieldCell.ContentConfiguration {
            guard let state = state as? UICellConfigurationState else { return self }
            var updatedConfiguration = self
            return updatedConfiguration
        }

    }

    class ContentView: UIView, UIContentView, UITextFieldDelegate {
        private var appliedConfiguration: TextFieldCell.ContentConfiguration!
        var configuration: UIContentConfiguration {
            get {
                appliedConfiguration
            }
            set {
                guard let newConfiguration = newValue as? TextFieldCell.ContentConfiguration else {
                    return
                }

                apply(configuration: newConfiguration)
            }
        }

        let nameTextField: UITextField = UITextField()

        init(configuration: TextFieldCell.ContentConfiguration) {
            super.init(frame: .zero)
            setupInternalViews()
            apply(configuration: configuration)
        }

        required init?(coder: NSCoder) {
            fatalError()
        }

        private func setupInternalViews() {
            addSubview(nameTextField)

            nameTextField.delegate = self
            nameTextField.clearButtonMode = .always
            nameTextField.autocapitalizationType = .sentences
            
            nameTextField.addAction(UIAction(handler: { [unowned self] action in
                textChanged()
            }), for: .editingChanged)

            NSLayoutConstraint.activate([
                nameTextField.leadingAnchor.constraint(equalTo: leadingAnchor),
                nameTextField.topAnchor.constraint(equalTo: topAnchor),
                nameTextField.trailingAnchor.constraint(equalTo: trailingAnchor),
                nameTextField.bottomAnchor.constraint(equalTo: bottomAnchor),
                nameTextField.heightAnchor.constraint(equalToConstant: 200) // Example height
            ])
        }

        /// Apply a new configuration.
        /// - Parameter configuration: The new configuration
        private func apply(configuration: TextFieldCell.ContentConfiguration) {
            guard appliedConfiguration != configuration else { return }
            appliedConfiguration = configuration
            
            nameTextField.text = appliedConfiguration.text
            nameTextField.placeholder = appliedConfiguration.placeholder
            nameTextField.keyboardType = appliedConfiguration.keyboardType ?? .default
            nameTextField.autocapitalizationType = .sentences
        }
        
        private func textChanged() {
            appliedConfiguration.handlers?.onTextChanged?(nameTextField.text ?? "")
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            textField.resignFirstResponder()
            return true
        }
    }
}

The important part is setting the layout constraints for the text field:

NSLayoutConstraint.activate([
    nameTextField.leadingAnchor.constraint(equalTo: leadingAnchor),
    nameTextField.topAnchor.constraint(equalTo: topAnchor),
    nameTextField.trailingAnchor.constraint(equalTo: trailingAnchor),
    nameTextField.bottomAnchor.constraint(equalTo: bottomAnchor),
    nameTextField.heightAnchor.constraint(equalToConstant: 200) // Example height
])

I set the height to 200 (as an example). When I run this, I get this:

2020-11-08 11:12:01.805610+0100 Sophia[11924:720407] [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. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x600001111f40 h=--& v=--& _TtCC6Sophia13TextFieldCell11ContentView:0x7fc0ed27a1c0.height == 44   (active)>",
    "<NSLayoutConstraint:0x6000011198b0 UITextField:0x7fc0ed27a370.height == 200   (active)>",
    "<NSLayoutConstraint:0x600001119810 V:|-(0)-[UITextField:0x7fc0ed27a370]   (active, names: '|':_TtCC6Sophia13TextFieldCell11ContentView:0x7fc0ed27a1c0 )>",
    "<NSLayoutConstraint:0x600001119b80 UITextField:0x7fc0ed27a370.bottom == _TtCC6Sophia13TextFieldCell11ContentView:0x7fc0ed27a1c0.bottom   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000011198b0 UITextField:0x7fc0ed27a370.height == 200   (active)>

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

As you see, there's an auto-generated height constraint setting the height to 44, which creates a conflict. Why is that there? And how can I change it?

Quantm
  • 2,444
  • 3
  • 19
  • 42
  • it seems to me you pinned the nameTextField to the ContentView so Autlayout dont use the height constraint and automatically give the 44. For example try to ContentView height constraint equals to 60 ; nameTextField height will be 60. – zeytin Nov 08 '20 at 10:35
  • Thanks but it doesn't seem to change anything, unfortunately – Quantm Nov 08 '20 at 10:39
  • Take a look to this https://stackoverflow.com/a/42948877/5575955 – Fabio Nov 08 '20 at 11:03
  • I don't think this applies here. I'm using a list layout for the collectionView – Quantm Nov 08 '20 at 11:23
  • Try this, connect a outlet of height, heightConstraint.constant = 100; and self.layoutIfNeeded() – Kathir Nov 08 '20 at 12:08
  • I'm sorry, I don't really know what you mean – Quantm Nov 08 '20 at 12:09
  • @Quantm I think you are missing `nameTextField.translatesAutoresizingMaskIntoConstraints = false` this line for `nameTextField` to apply your constraints for `nameTextField` – Kishan Bhatiya Nov 09 '20 at 05:34
  • Added it but the result is still the same, unfortunately – Quantm Nov 09 '20 at 19:01
  • @Quantm also you have to remove `bottomAnchor` to apply your `height` constraint – Kishan Bhatiya Nov 10 '20 at 04:32
  • It crashes when I do that: `Rounding frame ({{16, -8.9884656743115785e+307}, {1162, 1.7976931348623157e+308}}) from preferred layout attributes resulted in a frame with one or more invalid members ({{16, -8.9884656743115785e+307}, {1162, inf}}).`. I think it needs top and bottom anchors to determine the cell height automatically. – Quantm Nov 11 '20 at 05:58
  • " I think it needs top and bottom anchors to determine the cell height automatically. " - Yes, try to remove height constraint and add top and bottom constraint. – Kishan Bhatiya Nov 11 '20 at 06:43
  • But then it just uses the Textfields intrinsic content size, and not the one I want it to have – Quantm Nov 11 '20 at 07:29

1 Answers1

3

I ran into the same issue. The best way I can describe this for myself right now is, that the constraints involving the UIContentView need to have "more than 1 satisfiable solution", where the solution you want it to have is only the "best" out of all possible ones.

So in a case pretty much the same as yours, I solved this by lowering the priority of the bottom constraint to .defaultHigh. Initially I thought maybe setting the height's priority to a lower value would be enough, but it then used that view's intrinsicContentSize instead (in my case it was a UIImageView). Which is not what I wanted.

So in summary:

let bottomConstraint = nameTextField.bottomAnchor.constraint(equalTo: bottomAnchor)
bottomConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
    nameTextField.leadingAnchor.constraint(equalTo: leadingAnchor),
    nameTextField.topAnchor.constraint(equalTo: topAnchor),
    nameTextField.trailingAnchor.constraint(equalTo: trailingAnchor),
    bottomConstraint,
    nameTextField.heightAnchor.constraint(equalToConstant: 200) // Example height
])

should fix it.

Dennis
  • 2,164
  • 3
  • 25
  • 32