UIKit代码实现水平StackView中多行可编辑自定义TextView问题求助
问题:水平UIStackView中自定义CaptionTextView无法输入多行文本
我通过UIKit代码实现了一个水平方向的UIStackView,其中包含UIImageView和自定义的CaptionTextView(内置UILabel实现占位符功能),但目前CaptionTextView无法输入多行文本,求解决。
自定义CaptionTextView代码
import UIKit class CaptionTextView: UITextView { private var placeHolderLabel: UILabel! override init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) configureUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc func textChanged() { placeHolderLabel.isHidden = !text.isEmpty } private func configureUI() { font = UIFont.systemFont(ofSize: 16) isScrollEnabled = false textContainer.maximumNumberOfLines = 0 NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil) configurePlaceHolderLabel() } private func configurePlaceHolderLabel() { let label = UILabel() placeHolderLabel = label addSubview(label) label.text = "What's Happening?" label.font = UIFont.systemFont(ofSize: 16) label.textColor = .darkGray label.setTranslatesMask() let leading = label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 4) let top = label.topAnchor.constraint(equalTo: topAnchor, constant: 8) NSLayoutConstraint.activate([leading, top]) } }
调用代码
private func configureStackView() { let stackView = UIStackView() contentView = stackView view.addSubview(stackView) stackView.isLayoutMarginsRelativeArrangement = true stackView.layoutMargins = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16) stackView.spacing = 12 stackView.setTranslatesMask() let leading = stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor) let trailing = stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor) let top = stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor) NSLayoutConstraint.activate([leading, trailing, top]) // stackView.backgroundColor = .twitterBlue configureProfileImageView() configureCaptionView() } private func configureProfileImageView() { guard let url = URL(string: user.profileImageUrl) else { return } let imageView = UIImageView() contentView.addArrangedSubview(imageView) profileImageView = imageView imageView.contentMode = .scaleAspectFill imageView.layer.masksToBounds = true imageView.layer.cornerRadius = 16 imageView.layer.borderColor = UIColor.white.cgColor imageView.layer.borderWidth = 1 imageView.kf.setImage(with: url) imageView.setTranslatesMask() let height = imageView.heightAnchor.constraint(equalToConstant: 32) let width = imageView.widthAnchor.constraint(equalToConstant: 32) NSLayoutConstraint.activate([height, width]) } private func configureCaptionView() { let captionView = CaptionTextView() contentView.addArrangedSubview(captionView) captionView.delegate = self self.captionTextView = captionView } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { view.layoutIfNeeded() } }
解决方案
问题根源在于水平StackView的默认对齐方式和CaptionTextView缺少必要的布局约束,导致文本框高度无法随内容展开。具体修改如下:
1. 修改StackView的对齐方式
水平StackView默认alignment是.fill,这会强制所有子视图高度一致(和UIImageView等高),导致CaptionTextView无法被拉长。在configureStackView中添加:
stackView.alignment = .top // 让子视图顶部对齐,CaptionTextView可自由调整高度
2. 给CaptionTextView添加布局约束
在configureCaptionView中,给CaptionTextView添加最小高度约束,并让它填充StackView的剩余宽度:
private func configureCaptionView() { let captionView = CaptionTextView() contentView.addArrangedSubview(captionView) captionView.delegate = self self.captionTextView = captionView // 添加布局约束 captionView.setTranslatesMask() // 设置最小高度,保证初始占位符完整显示 let minHeight = captionView.heightAnchor.constraint(greaterThanOrEqualToConstant: 32) // 让textView填充StackView的可用宽度 let trailingConstraint = captionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) NSLayoutConstraint.activate([minHeight, trailingConstraint]) }
3. 优化CaptionTextView的内部布局
在CaptionTextView的configureUI方法中,设置textContainerInset,让文本和占位符位置匹配,避免重叠:
private func configureUI() { font = UIFont.systemFont(ofSize: 16) isScrollEnabled = false textContainer.maximumNumberOfLines = 0 // 添加文本内边距,和占位符偏移保持一致 textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 8, right: 4) NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil) configurePlaceHolderLabel() }
同时调整占位符Label的约束,跟随内边距变化:
private func configurePlaceHolderLabel() { let label = UILabel() placeHolderLabel = label addSubview(label) label.text = "What's Happening?" label.font = UIFont.systemFont(ofSize: 16) label.textColor = .darkGray label.setTranslatesMask() // 使用textContainerInset的值设置偏移,保持位置一致 let leading = label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: textContainerInset.left) let top = label.topAnchor.constraint(equalTo: topAnchor, constant: textContainerInset.top) NSLayoutConstraint.activate([leading, top]) }
4. 优化文本变化时的布局更新
在textViewDidChange中触发父视图布局更新,确保StackView及时调整高度:
extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { contentView.layoutIfNeeded() view.layoutIfNeeded() } }
完成以上修改后,CaptionTextView就能正常输入多行文本,且高度会随内容自动调整。
内容的提问来源于stack exchange,提问作者Pranjal
相关产品推荐
相关产品推荐

