You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 07:57:05