基于更改子视图高度以编程方式增加 UIView 高度

问题描述

我一直无法弄清楚如何解决我的这个问题。我试图遵循这篇文章的答案。 How to change UIView height based on elements inside it

就像帖子回答说的那样,我有

  • 在 UIContainerView 顶部到 UITextView 顶部和 UIContainerView 底部到 UITextView 底部之间设置自动布局约束 (#1)
  • 在文本视图上设置高度约束 (#2) 并在调整文本视图大小时更改其常量 (#3)

我必须以编程方式完成这一切。我首先为容器视图设置框架并给它一个指定的高度。我不确定这是否也可以。我还在 viewDidLoad 中添加了 (#1),但不确定这是否正确。

文本视图无法在当前约束下增加高度(如果我删除 topAnchor 约束但容器视图仍然不会改变大小,它可以增加)。

class ChatController: UICollectionViewController,UICollectionViewDelegateFlowLayout,UIImagePickerControllerDelegate,UINavigationControllerDelegate {

lazy var containerView: UIView = {
    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    containerView.frame = CGRect(x: 0,y: 0,width: self.view.frame.width,height: self.view.frame.height * 0.075)
    return containerView
}()

lazy var textView: UITextView = {
    let textView = UITextView()
    textView.text = "Enter message..."
    textView.isScrollEnabled = false
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.delegate = self
    return textView
}()

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    textViewDidChange(self.textView)
    addContainerSubViews()
    (#1)
    containerView.topAnchor.constraint(equalTo: self.textView.topAnchor,constant: -UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
    containerView.bottomAnchor.constraint(equalTo: self.textView.bottomAnchor,constant: UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true

}

func addContainerSubViews() {
    
    let height = UIScreen.main.bounds.size.height
    let width = UIScreen.main.bounds.size.width
    let containerHeight = height * 0.075
    
    ...//constraints for imageView and sendButton...

    containerView.addSubview(self.textView)
    self.textView.leftAnchor.constraint(equalTo: imageView.rightAnchor,constant: width/20).isActive = true
    self.textView.rightAnchor.constraint(equalTo: sendButton.leftAnchor,constant: -width/20).isActive = true
    (#2)
    self.textView.heightAnchor.constraint(equalToConstant: containerHeight * 0.6).isActive = true

}

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

(#3)
func textViewDidChange(_ textView: UITextView) {
    let size = CGSize(width: view.frame.width,height: .infinity)
    let estimatedSize = textView.sizeThatFits(size)
    textView.constraints.forEach { (constraint) in
        if constraint.firstAttribute == .height {
            constraint.constant = estimatedSize.height
        }
    }
}

enter image description here

解决方法

您可以使用自动布局/约束来完成这一切。由于禁用滚动的 UITextView 将根据文本“自动调整”其高度,因此无需计算高度并更改约束常量。

这是一个例子——它来自上一个答案,修改为包含您的图像视图和发送按钮:

class ViewController: UIViewController {
    
    let testLabel: InputLabel = InputLabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let instructionLabel = UILabel()
        instructionLabel.textAlignment = .center
        instructionLabel.text = "Tap yellow label to edit..."
        
        let centeringFrameView = UIView()
        
        // label properties
        let fnt: UIFont = .systemFont(ofSize: 32.0)
        testLabel.isUserInteractionEnabled = true
        testLabel.font = fnt
        testLabel.adjustsFontSizeToFitWidth = true
        testLabel.minimumScaleFactor = 0.25
        testLabel.numberOfLines = 2
        testLabel.setContentHuggingPriority(.required,for: .vertical)
        let minLabelHeight = ceil(fnt.lineHeight)
        
        // so we can see the frames
        centeringFrameView.backgroundColor = .red
        testLabel.backgroundColor = .yellow
        
        [centeringFrameView,instructionLabel,testLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        
        view.addSubview(instructionLabel)
        view.addSubview(centeringFrameView)
        centeringFrameView.addSubview(testLabel)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            // instruction label centered at top
            instructionLabel.topAnchor.constraint(equalTo: g.topAnchor,constant: 20.0),instructionLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),// centeringFrameView 20-pts from instructionLabel bottom
            centeringFrameView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor,// Leading / Trailing with 20-pts "padding"
            centeringFrameView.leadingAnchor.constraint(equalTo: g.leadingAnchor,centeringFrameView.trailingAnchor.constraint(equalTo: g.trailingAnchor,constant: -20.0),// test label centered vertically in centeringFrameView
            testLabel.centerYAnchor.constraint(equalTo: centeringFrameView.centerYAnchor,constant: 0.0),// Leading / Trailing with 20-pts "padding"
            testLabel.leadingAnchor.constraint(equalTo: centeringFrameView.leadingAnchor,testLabel.trailingAnchor.constraint(equalTo: centeringFrameView.trailingAnchor,// height will be zero if label has no text,//  so give it a min height of one line
            testLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: minLabelHeight),// centeringFrameView height = 3 * minLabelHeight
            centeringFrameView.heightAnchor.constraint(equalToConstant: minLabelHeight * 3.0)
        ])
        
        // to handle user input
        testLabel.editCallBack = { [weak self] str in
            guard let self = self else { return }
            self.testLabel.text = str
        }
        testLabel.doneCallBack = { [weak self] in
            guard let self = self else { return }
            // do something when user taps done / enter
        }
        
        let t = UITapGestureRecognizer(target: self,action: #selector(self.labelTapped(_:)))
        testLabel.addGestureRecognizer(t)
        
    }
    
    @objc func labelTapped(_ g: UITapGestureRecognizer) -> Void {
        testLabel.becomeFirstResponder()
        testLabel.inputContainerView.theTextView.text = testLabel.text
        testLabel.inputContainerView.theTextView.becomeFirstResponder()
    }
    
}

class InputLabel: UILabel {
    
    var editCallBack: ((String) -> ())?
    var doneCallBack: (() -> ())?
    
    override var canBecomeFirstResponder: Bool {
        return true
    }
    override var canResignFirstResponder: Bool {
        return true
    }
    override var inputAccessoryView: UIView? {
        get { return inputContainerView }
    }
    
    lazy var inputContainerView: CustomInputAccessoryView = {
        let v = CustomInputAccessoryView()
        v.editCallBack = { [weak self] str in
            guard let self = self else { return }
            self.editCallBack?(str)
        }
        v.doneCallBack = { [weak self] in
            guard let self = self else { return }
            self.resignFirstResponder()
        }
        return v
    }()

}

class CustomInputAccessoryView: UIView,UITextViewDelegate {
    
    var editCallBack: ((String) -> ())?
    var doneCallBack: (() -> ())?
    
    let theTextView: UITextView = {
        let tv = UITextView()
        tv.isScrollEnabled = false
        tv.font = .systemFont(ofSize: 16)
        tv.autocorrectionType = .no
        tv.returnKeyType = .done
        return tv
    }()
    
    let imgView: UIImageView = {
        let v = UIImageView()
        v.contentMode = .scaleAspectFit
        v.clipsToBounds = true
        return v
    }()
    
    let sendButton: UIButton = {
        let v = UIButton()
        return v
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .lightGray
        autoresizingMask = [.flexibleHeight,.flexibleWidth]

        if let img = UIImage(named: "testImage") {
            imgView.image = img
        } else {
            imgView.backgroundColor = .systemBlue
        }
        
        let largeConfig = UIImage.SymbolConfiguration(pointSize: 22,weight: .regular,scale: .large)
        let buttonImg = UIImage(systemName: "paperplane.fill",withConfiguration: largeConfig)
        sendButton.setImage(buttonImg,for: .normal)
        
        [theTextView,imgView,sendButton].forEach { v in
            addSubview(v)
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        // if we want to see the image view and button frames
        //[imgView,sendButton].forEach { v in
        //  v.backgroundColor = .systemYellow
        //}
        
        NSLayoutConstraint.activate([
            
            // constrain image view 40x40 with 8-pts leading
            imgView.widthAnchor.constraint(equalToConstant: 40.0),imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),imgView.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 8.0),// constrain image view 40x40 with 8-pts trailing
            sendButton.widthAnchor.constraint(equalToConstant: 40.0),sendButton.heightAnchor.constraint(equalTo: sendButton.widthAnchor),sendButton.trailingAnchor.constraint(equalTo: trailingAnchor,constant: -8.0),// constrain text view with 10-pts from
            //  image view trailing
            //  send button leading
            theTextView.leadingAnchor.constraint(equalTo: imgView.trailingAnchor,constant: 10),theTextView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor,constant: -10),// constrain image view and button
            //  centered vertically
            //  at least 8-pts top and bottom
            imgView.centerYAnchor.constraint(equalTo: centerYAnchor),sendButton.centerYAnchor.constraint(equalTo: centerYAnchor),imgView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor,sendButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor,imgView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor,sendButton.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor,// constrain text view 8-pts top/bottom
            theTextView.topAnchor.constraint(equalTo: topAnchor,theTextView.bottomAnchor.constraint(equalTo: bottomAnchor,])
        
        theTextView.delegate = self
    }
    
    func textView(_ textView: UITextView,shouldChangeTextIn range: NSRange,replacementText text: String) -> Bool {
        if (text == "\n") {
            textView.resignFirstResponder()
            doneCallBack?()
        }
        return true
    }
    func textViewDidChange(_ textView: UITextView) {
        editCallBack?(textView.text ?? "")
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override var intrinsicContentSize: CGSize {
        return .zero
    }
    
}

输出:

enter image description here

enter image description here

enter image description here

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...