TextView 在文本为一行时添加底部填充/空格

TextView adds bottom padding/space when text is one line

提问人:benedom 提问时间:11/1/2022 更新时间:11/3/2022 访问量:639

问:

问题

我遇到了一个奇怪的行为,它位于 a 内部,定义间距为 。用于在聊天气泡内显示消息。当消息只有一行时,将展开并添加 about 的底部间距/填充。查看视图层次结构,文本本身是一个位于 .此画布视图似乎绘制得太大,导致间距过大。UITextViewUIStackView0UITextViewUITextView2.3 px_UITextLayoutFragmentView_UITextLayoutCanvasView

以下图像来自视图层次结构,其中显示问题的一行消息和包含两行消息的消息,其中问题未显示:https://i.stack.imgur.com/azhsq.jpg(链接,因为我还不能在 Stackoverflow 上发布图像)。

我试过了什么

我已经设置了插图:UITextView

messageTextView.textContainerInset = .zero
messageTextView.textContainer.lineFragmentPadding = .zero
messageTextView.contentInset = .zero

它删除了正常的填充(使用此答案/线程)。UITextView

我最初的想法是,在某处指定了最小高度,当里面的文本(一行)没有填充最小高度时,这会导致视图扩展。但是,我不知道这是否正确,或者是否有可能更改最小高度。缩小字体大小会产生更大的间距。messageTextView

聊天气泡用于根据内容自动调整其大小。在更改为 a 之前,我们使用了 一个工作正常且没有创建任何间距的 a。UIStackViewsUITextViewUILabel

有没有人遇到过类似的问题?

iOS Swift Cocoa-Touch UIKIT UITtextView

评论

0赞 DonMag 11/1/2022
“缩小字体大小会产生更大的间距”......听起来好像发生了其他事情。您是通过代码还是 Storyboard / IB 创建它?
0赞 benedom 11/1/2022
@DonMag,在 .xib 文件中创建包含 UIStackView 和 UITextView 的整个聊天气泡
0赞 DonMag 11/1/2022
添加 XIB 布局的屏幕截图 - 在文档大纲窗格中展开所有约束。最好的办法是创建一个最小的可重复示例——不需要任何交互性或数据检索......只需几个字符串即可重现该问题。
0赞 benedom 11/2/2022
@DonMag我在这里上传了 XIB 布局的屏幕截图。同时,我将尝试创建一个可重复的例子。
0赞 DonMag 11/2/2022
提示:在尝试调试布局时,请为元素提供对比鲜明的背景颜色。几乎不可能看出那张截图里有什么。

答:

-1赞 keyur kathrotiya 11/1/2022 #1
  • 使用此 SDK 并应用最小高度,然后它会自动管理高度

https://github.com/KennethTsang/GrowingTextView

评论

0赞 kumaresh saran 11/1/2022
@benedom 你能为你的UI提供任何截图吗?
0赞 benedom 11/1/2022
@keyur kathrotiya不幸的是,我无法在此项目中使用任何第三方 SDK,@kumareshsaran我在此处上传了视图层次结构中的屏幕截图: imgur.com/a/nvrNYZM
0赞 keyur kathrotiya 11/1/2022
@benedom 为什么不使用 UILabel 而不是 UITextView ?
0赞 benedom 11/1/2022
@keyurkathrotiya,我们需要检测聊天消息中的链接、电子邮件、电话号码和地址,以便它们可以点击。Apple 的 UITextView 支持自动检测该数据。
0赞 DonMag 11/3/2022 #2

您显示的布局看起来正确无误...但有些事情并不完全正确。

我把它放在一起作为一个快速测试 - 它应该非常接近你所拥有的......

细胞 xib 在 IB 中的外观:

enter image description here

运行时的外观 - 5 条“消息”相关两次。第一组隐藏了“documentsStack 和 imagesStack”,第二组显示(没有子视图,但有 10 磅高度限制):

enter image description here

enter image description here

下面是 Debug View Hierarchy 外观:

enter image description here

enter image description here

所以。。。XIB的来源:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
    <device id="retina6_1" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <objects>
        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
        <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="315" id="efZ-1S-bN1" customClass="RightStandardMessageCell" customModule="qtest" customModuleProvider="target">
            <rect key="frame" x="0.0" y="0.0" width="442" height="315"/>
            <autoresizingMask key="autoresizingMask"/>
            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="efZ-1S-bN1" id="mpK-CA-Lnn">
                <rect key="frame" x="0.0" y="0.0" width="442" height="315"/>
                <autoresizingMask key="autoresizingMask"/>
                <subviews>
                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="2vd-KS-ll8">
                        <rect key="frame" x="106" y="8" width="328" height="299"/>
                        <color key="backgroundColor" red="0.71935945749999997" green="0.8054707646" blue="0.8795467615" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                    </imageView>
                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="7Bs-WC-QCo">
                        <rect key="frame" x="118" y="20" width="304" height="275"/>
                        <subviews>
                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="Ykv-rE-DFT">
                                <rect key="frame" x="0.0" y="0.0" width="304" height="227"/>
                                <subviews>
                                    <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Text View" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="xKc-db-bx1" userLabel="Message Text View" customClass="MyTextViewLabel" customModule="qtest" customModuleProvider="target">
                                        <rect key="frame" x="220.5" y="0.0" width="83.5" height="227"/>
                                        <color key="backgroundColor" red="0.41512373645565315" green="0.80431303791610775" blue="0.90702960527304444" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                                        <color key="textColor" systemColor="labelColor"/>
                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                        <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                                    </textView>
                                </subviews>
                                <color key="backgroundColor" red="0.79694263352245631" green="0.91265043646398214" blue="0.50891842927836861" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                            </stackView>
                            <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jKv-au-EZL">
                                <rect key="frame" x="0.0" y="231" width="304" height="10"/>
                                <color key="backgroundColor" systemColor="systemOrangeColor"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="10" id="AoS-qw-PIV"/>
                                </constraints>
                            </stackView>
                            <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9wb-aV-6Ah">
                                <rect key="frame" x="0.0" y="245" width="304" height="10"/>
                                <color key="backgroundColor" systemColor="systemPurpleColor"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="10" id="ZGO-O7-jpC"/>
                                </constraints>
                            </stackView>
                            <stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wQ2-iG-u2V">
                                <rect key="frame" x="0.0" y="259" width="304" height="16"/>
                                <subviews>
                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="83z-yh-Mfn" userLabel="Timestamp">
                                        <rect key="frame" x="0.0" y="0.0" width="278" height="16"/>
                                        <constraints>
                                            <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="16" id="zeh-oM-fP6"/>
                                        </constraints>
                                        <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                        <nil key="textColor"/>
                                        <nil key="highlightedColor"/>
                                    </label>
                                    <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="checkmark.rectangle" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="fqI-0d-Wy4" userLabel="Checkmark Image View">
                                        <rect key="frame" x="286" y="1" width="18" height="13.5"/>
                                        <constraints>
                                            <constraint firstAttribute="width" constant="18" id="9NX-a1-Fzz"/>
                                            <constraint firstAttribute="height" constant="16" id="Pdb-F0-5qp"/>
                                        </constraints>
                                    </imageView>
                                </subviews>
                                <color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                            </stackView>
                        </subviews>
                    </stackView>
                </subviews>
                <color key="backgroundColor" red="0.93024982769507736" green="0.76566540916270232" blue="0.97523039579391479" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                <constraints>
                    <constraint firstItem="2vd-KS-ll8" firstAttribute="top" secondItem="mpK-CA-Lnn" secondAttribute="top" constant="8" id="95G-hj-ZRa"/>
                    <constraint firstItem="7Bs-WC-QCo" firstAttribute="top" secondItem="2vd-KS-ll8" secondAttribute="top" constant="12" id="ARj-eW-yGu"/>
                    <constraint firstItem="2vd-KS-ll8" firstAttribute="width" relation="lessThanOrEqual" secondItem="mpK-CA-Lnn" secondAttribute="width" multiplier="0.85" id="F2v-hy-pn9"/>
                    <constraint firstItem="7Bs-WC-QCo" firstAttribute="leading" secondItem="2vd-KS-ll8" secondAttribute="leading" constant="12" id="KIV-td-SQh"/>
                    <constraint firstItem="2vd-KS-ll8" firstAttribute="trailing" secondItem="mpK-CA-Lnn" secondAttribute="trailing" constant="-8" id="P1I-QI-v0Y"/>
                    <constraint firstAttribute="bottom" secondItem="2vd-KS-ll8" secondAttribute="bottom" constant="8" id="VsS-Hf-Fv6"/>
                    <constraint firstItem="7Bs-WC-QCo" firstAttribute="trailing" secondItem="2vd-KS-ll8" secondAttribute="trailing" constant="-12" id="YAZ-d9-R8n"/>
                    <constraint firstItem="7Bs-WC-QCo" firstAttribute="bottom" secondItem="2vd-KS-ll8" secondAttribute="bottom" priority="999" constant="-12" id="ke7-OJ-E00"/>
                </constraints>
            </tableViewCellContentView>
            <connections>
                <outlet property="bubbleImageView" destination="2vd-KS-ll8" id="OIs-Qk-bNu"/>
                <outlet property="documentsStack" destination="jKv-au-EZL" id="HU0-6P-C0b"/>
                <outlet property="footerStack" destination="wQ2-iG-u2V" id="zUl-YI-Gh0"/>
                <outlet property="imagesStack" destination="9wb-aV-6Ah" id="OlB-1t-tQ5"/>
                <outlet property="messageStack" destination="Ykv-rE-DFT" id="SIz-up-iY1"/>
                <outlet property="messageTextView" destination="xKc-db-bx1" id="805-2W-d4g"/>
                <outlet property="outerStack" destination="7Bs-WC-QCo" id="bM8-CF-WYS"/>
                <outlet property="timestamp" destination="83z-yh-Mfn" id="4hp-VP-93P"/>
            </connections>
            <point key="canvasLocation" x="489.85507246376818" y="264.17410714285711"/>
        </tableViewCell>
    </objects>
    <resources>
        <image name="checkmark.rectangle" catalog="system" width="128" height="93"/>
        <systemColor name="labelColor">
            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemOrangeColor">
            <color red="1" green="0.58431372549019611" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
        <systemColor name="systemPurpleColor">
            <color red="0.68627450980392157" green="0.32156862745098042" blue="0.87058823529411766" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
        </systemColor>
    </resources>
</document>

“文本视图像标签一样”类

class MyTextViewLabel: UITextView {
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        isEditable = false
        isScrollEnabled = false
        showsVerticalScrollIndicator = false
        showsHorizontalScrollIndicator = false
        
        textContainerInset = .zero
        textContainer.lineFragmentPadding = .zero
        contentInset = .zero
        
        dataDetectorTypes = .all
    }
    
}

Cell 类

class RightStandardMessageCell: UITableViewCell {
    
    @IBOutlet var bubbleImageView: UIImageView!
    @IBOutlet var messageTextView: MyTextViewLabel!
    @IBOutlet var timestamp: UILabel!

    @IBOutlet var messageStack: UIStackView!
    @IBOutlet var documentsStack: UIStackView!
    @IBOutlet var imagesStack: UIStackView!
    @IBOutlet var footerStack: UIStackView!

    @IBOutlet var outerStack: UIStackView!

    var debugColors: [UIColor] = []
    var showDebugColors: Bool = false
    var debugViews: [UIView] = []
    
    override func awakeFromNib() {
        super.awakeFromNib()
        debugViews = [
            contentView,
            bubbleImageView, messageTextView, timestamp,
            messageStack, documentsStack, imagesStack, footerStack,
            outerStack,
        ]
        debugViews.forEach { v in
            debugColors.append(v.backgroundColor ?? .clear)
        }
        // because this is the "Right Standard" call
        messageTextView.textAlignment = .right
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        bubbleImageView.layer.cornerRadius = 12.0
        bubbleImageView.layer.masksToBounds = true
        
        for (v, c) in zip(debugViews, debugColors) {
            v.backgroundColor = showDebugColors ? c : .clear
        }
        // always use the bubble image view background
        bubbleImageView.backgroundColor = UIColor(red: 0.72, green: 0.80, blue: 0.90, alpha: 1.0)
        contentView.backgroundColor = showDebugColors ? debugColors.first : .gray
    }
}

和样品控制器

class MsgTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    let tableView = UITableView()
    
    var myData: [String] = [
        "Hallo",
        "This is a longer message.",
        "Hier ist mal eine Nachricht welche sich über zwei Zeilen erstreckt.",
        "Message with\nembedded\nnewline\ncharacters.",
        "Message with data detection...\nhttps://apple.com\n770-555-1212\[email protected]"
    ]
    
    var showDebugColors: Bool = true
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        // a button to toggle the cell's "debug" colors
        let btn: UIButton = {
            var cfg = UIButton.Configuration.filled()
            cfg.title = "Toggle Debug Colors"
            let b = UIButton(configuration: cfg)
            b.addTarget(self, action: #selector(toggleDebugColors(_:)), for: .touchUpInside)
            return b
        }()
        
        btn.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btn)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)

        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([

            btn.topAnchor.constraint(equalTo: g.topAnchor, constant: 12.0),
            btn.centerXAnchor.constraint(equalTo: g.centerXAnchor),

            tableView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 20.0),
            tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
            
        ])
        
        tableView.register(UINib(nibName: "RightStandardMessageCell", bundle: nil), forCellReuseIdentifier: "c")
        tableView.dataSource = self
        tableView.delegate = self
    }
    
    // MARK: - Table view data source
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // we'll show the same data twice
        //  - first set with documentsStack and imagesStack hidden
        //  - second set with documentsStack and imagesStack showing
        return myData.count * 2
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! RightStandardMessageCell
        
        cell.timestamp.text = "Row: \(indexPath.row)"
        cell.messageTextView.text = myData[indexPath.row % myData.count]
        
        // show the documentsStack and imagesStack for the 2nd set
        cell.documentsStack.isHidden = indexPath.row < myData.count
        cell.imagesStack.isHidden = indexPath.row < myData.count

        cell.showDebugColors = self.showDebugColors
        
        return cell
    }
    
    @objc func toggleDebugColors(_ sender: Any?) -> Void {
        showDebugColors.toggle()
        tableView.reloadData()
    }
}

评论

0赞 benedom 11/4/2022
似乎示例中的间距是在文档和图像堆栈未隐藏时创建的。堆栈视图隐藏在我的项目中,并且没有添加,因为它可以在调试视图中看到。间距仅来自 ,特别是大于 的 。除此之外,您的示例似乎具有与我的项目相同的属性集。也许间距是由布局引起的?UITextViewCanvasViewFragmentViewUITableView
0赞 DonMag 11/4/2022
@benedom - 我假设当您没有文档和图像堆栈的内容时,它们将被隐藏。这就是我展示这两个例子的原因。但是,在这两种情况下,textView 的行为都符合预期 - 底部没有“额外空间”。所以,是的,听起来您正在对布局执行的其他操作导致了问题。如果你能创建一个最小的可重复的例子,我很乐意看它。
0赞 benedom 11/4/2022
我设法重现了这个问题,并找到了导致它的原因。正在由一个 pdf 资产填充(为什么)。似乎这个pdf有一个最小高度,并且在它是一行时才导致问题。将资源裁剪为较小的高度会删除间距。您应该能够重现示例中的问题,方法是使用此资源填充 .bubbleImageViewUITextViewbubbleImageView
0赞 DonMag 11/4/2022
@benedom - 是的......以为会是这样的。您可能希望转储并使用子类。那里有很多例子 - 这是我刚刚通过快速搜索找到的一个,可以很容易地“调整”以获得您想要的结果: talsharon.github.io/Custom-Speech-BubbleassetUIView