如何在不滚动 textView 的情况下使 UITableViewCell 的高度扩展以适应 UITextView 的内容并包装文本? (斯威夫特 5)

Posted

技术标签:

【中文标题】如何在不滚动 textView 的情况下使 UITableViewCell 的高度扩展以适应 UITextView 的内容并包装文本? (斯威夫特 5)【英文标题】:How can one make a UITableViewCell's height expand to fit the contents of a UITextView with wrapping text without the textView scrolling? (Swift 5) 【发布时间】:2020-09-30 18:17:31 【问题描述】:

我有一个使用自定义单元格的 UITableViewController。在自定义单元格中是一个 UITextView 和一个 UIButton。当用户键入时,我需要选择的 tableView 单元格适合用户输入的多行文本。 iPhone 预装的提醒应用程序可以完美地展示这一点。目前在我的应用中,tableView 单元格的高度保持不变,并且 textView 在必要时启用滚动以容纳多行文本。

我尝试在这篇文章 (Dynamically change cell's height while typing text, and reload the containing tableview for resize) 中实施解决方案,这似乎提出了同样的问题,但是,这篇文章已经有将近 3 年的历史了,即使答案仍然有效,我也没有足够的示例代码了解如何实施答案。我尽了最大的努力,但没有运气,并清除了我当前的任何先前尝试的代码,因此就这个所需的功能而言,它目前是一个干净的石板。看到包含 Swift 5 中的示例代码的答案将是惊人的。

如果有用的话,这里是自定义单元格类:

class newNoteTableViewCell: UITableViewCell 

    @IBOutlet weak var lyricsField: UITextView!
    @IBOutlet weak var recordButton: UIButton!
    
    override func awakeFromNib() 
        super.awakeFromNib()
        // Initialization code
    

    override func setSelected(_ selected: Bool, animated: Bool) 
        super.setSelected(selected, animated: animated)
    
    
    @IBAction func recordButtonPressed(_ sender: UIButton) 
    

还有我的 cellForRowAt,这是目前除了 numberOfRowsInSection 之外唯一实现的表视图委托方法:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "lyricsCell", for: indexPath) as! newNoteTableViewCell
        
        cell.lyricsField.delegate = self
        
        cell.lyricsField.tag = indexPath.row
        
        if let safeLyrics = lyrics 
            if indexPath.row < safeLyrics.count 
                cell.lyricsField.text = safeLyrics[indexPath.row].text
             else 
                cell.lyricsField.text = ""
            
        
        
        return cell
    

【问题讨论】:

【参考方案1】:

我们可以通过向您的单元格添加“回调”闭包来做到这一点。

当文本视图被编辑时,单元格类将“回调”到控制器,在那里我们可以告诉表格视图重新计算行高(以及保存编辑的文本)。

这是一个带有约束的简单单元格布局。确保在文本视图上禁用滚动:

请注意,在设计时单元格的高度并不重要...我们的约束将允许自动布局来处理。

结果:

示例代码如下:

class ExampleCell: UITableViewCell, UITextViewDelegate 
    
    @IBOutlet var recordButton: UIButton!
    @IBOutlet var lyricsField: UITextView!
    
    var callback: ((String) -> ())?
    
    override func didMoveToSuperview() 
        super.didMoveToSuperview()
        // make sure scroll is disabled
        lyricsField.isScrollEnabled = false
        // make sure delegate is set
        lyricsField.delegate = self
        // if these are set in Storyboard this func is not needed
    
    func textViewDidChange(_ textView: UITextView) 
        let str = textView.text ?? ""
        // tell the controller
        callback?(str)
    



class ExampleTableViewController: UITableViewController 

    var myData: [String] = []
    
    override func viewDidLoad() 
        super.viewDidLoad()

        // start with 20 sample strings for our data
        // fill data array with 30 strings
        myData = (1...30).map  "This is row \($0)" 

        // give the second row some longer sample text
        myData[1] = "Some sample text so we see that the text view height will be automatically handled by auto-layout."
        
    

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return myData.count
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "exampleCell", for: indexPath) as! ExampleCell

        cell.lyricsField.text = myData[indexPath.row]
        
        // set the closure
        weak var tv = tableView
        cell.callback =  [weak self] str in
            guard let self = self, let tv = tv else  return 
            print("called back", str)
            // update our data with the edited string
            self.myData[indexPath.row] = str
            // we don't need to do anything else here
            // this will force the table to recalculate row heights
            tv.performBatchUpdates(nil)
        

        return cell
    


这里是 Storyboard 源代码供参考:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="ios.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="UxY-Y6-LYS">
    <device id="retina3_5" orientation="portrait" appearance="light"/>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Example Table View Controller-->
        <scene sceneID="HKA-46-9zH">
            <objects>
                <tableViewController id="UxY-Y6-LYS" customClass="ExampleTableViewController" customModule="Temp" customModuleProvider="target" sceneMemberID="viewController">
                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="ZaD-4v-hEm">
                        <rect key="frame" x="0.0" y="0.0"  />
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <prototypes>
                            <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="exampleCell" rowHeight="141" id="1bW-gv-rnI" customClass="ExampleCell" customModule="Temp" customModuleProvider="target">
                                <rect key="frame" x="0.0" y="28"  />
                                <autoresizingMask key="autoresizingMask"/>
                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="1bW-gv-rnI" id="H65-Gy-hPe">
                                    <rect key="frame" x="0.0" y="0.0"  />
                                    <autoresizingMask key="autoresizingMask"/>
                                    <subviews>
                                        <button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5rZ-Jh-Wyd">
                                            <rect key="frame" x="96" y="11"  />
                                            <color key="backgroundColor" red="0.85215073819999998" green="0.88016217949999997" blue="0.94548028709999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <state key="normal" title="Record"/>
                                        </button>
                                        <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" keyboardDismissMode="onDrag" text="The Text View" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ddd-0L-1pM">
                                            <rect key="frame" x="24" y="49"  />
                                            <color key="backgroundColor" red="0.99953407049999998" green="0.98835557699999999" blue="0.47265523669999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <color key="textColor" systemColor="labelColor"/>
                                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                            <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                                        </textView>
                                    </subviews>
                                    <constraints>
                                        <constraint firstItem="ddd-0L-1pM" firstAttribute="top" secondItem="5rZ-Jh-Wyd" secondAttribute="bottom" constant="8" id="MHf-Yq-xQq"/>
                                        <constraint firstAttribute="trailingMargin" secondItem="ddd-0L-1pM" secondAttribute="trailing" constant="8" id="QMH-AZ-j7k"/>
                                        <constraint firstItem="5rZ-Jh-Wyd" firstAttribute="leading" secondItem="H65-Gy-hPe" secondAttribute="leadingMargin" constant="80" id="TYR-1f-iit"/>
                                        <constraint firstAttribute="trailingMargin" secondItem="5rZ-Jh-Wyd" secondAttribute="trailing" constant="80" id="Yf1-2g-dlf"/>
                                        <constraint firstItem="5rZ-Jh-Wyd" firstAttribute="top" secondItem="H65-Gy-hPe" secondAttribute="topMargin" id="gaW-td-egM"/>
                                        <constraint firstItem="ddd-0L-1pM" firstAttribute="bottom" secondItem="H65-Gy-hPe" secondAttribute="bottomMargin" id="lKj-ML-H4Q"/>
                                        <constraint firstItem="ddd-0L-1pM" firstAttribute="leading" secondItem="H65-Gy-hPe" secondAttribute="leadingMargin" constant="8" id="uxs-rD-Pdj"/>
                                    </constraints>
                                </tableViewCellContentView>
                                <connections>
                                    <outlet property="lyricsField" destination="ddd-0L-1pM" id="9Nz-Ru-psp"/>
                                    <outlet property="recordButton" destination="5rZ-Jh-Wyd" id="qNA-Up-zLK"/>
                                </connections>
                            </tableViewCell>
                        </prototypes>
                        <connections>
                            <outlet property="dataSource" destination="UxY-Y6-LYS" id="cmh-tD-hLg"/>
                            <outlet property="delegate" destination="UxY-Y6-LYS" id="xk4-oC-WNJ"/>
                        </connections>
                    </tableView>
                </tableViewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="7mc-zX-bKw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="330" y="127.5"/>
        </scene>
    </scenes>
    <resources>
        <systemColor name="labelColor">
            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
    </resources>
</document>

编辑 回应评论....

你在你的单元格中的约束都是错误的......

这是它们在原始 xib 中的外观:

这是他们应该的样子:

重要提示:您的堆栈视图设置需要:

【讨论】:

哇!感谢您提供非常详细的答案。我非常感谢示例代码和线框图。我输入了一个详细的回复,结果太长了,无法在此处留下评论,但长话短说,我尝试实施这些更改,但单元格仍保持固定高度。唯一改变的是滚动被禁用。我知道要问的可能很多,但你愿意看我的回购吗?我在 newNoteTableViewCell 文件中留下了关于我如何尝试进行更改的详细回复作为评论。 link @michaelthedeveloper - 请参阅我的答案底部的 Edit。如果您愿意,可以将我作为“协作者”添加到您的 GitHub 存储库中……我也会更新您的代码,然后在新分支上提交更改,以便您可以清楚地看到差异。 (我的 GitHub id 也是DonMag 我修复了限制并重新实现了最初的解决方案,现在它运行良好!如果您有兴趣,我还需要其他一些帮助。我为录制按钮设置了录制功能,以便当前在模拟器中,按下录制按钮可以录制一些音频,然后在完成后播放,但是,我尝试了几种不同的方法来保存这些录制到领域,以便他们也用相应的歌词重新填充他们的单元格,但似乎无法弄清楚如何做到这一点。立即将您添加为协作者 @michaelthedeveloper - 如何管理保存/重新加载用户输入的数据和音频是一个完全不同的问题。我建议您从具有一个文本视图和一个按钮的 simple 应用程序中保存/加载文本字符串开始。然后处理保存/加载音频......也可以从一个简单的应用程序。一旦您对这些任务有了扎实的把握,然后将其添加到您的完整应用中。 我已经能够将字符串保存到 Realm,然后用字符串重新填充单元格,但是只有在尝试访问与保存的字符串关联的 URL 时才会出现问题。我知道这是另一个问题,但因为这是同一个项目,所以我想看看你是否能提供帮助。感谢您帮助解决表格单元格动态高度问题!我将继续在音频存储上寻找答案,并尝试首先在一个简单的应用程序上实现它。

以上是关于如何在不滚动 textView 的情况下使 UITableViewCell 的高度扩展以适应 UITextView 的内容并包装文本? (斯威夫特 5)的主要内容,如果未能解决你的问题,请参考以下文章

如何在不给定高度的情况下使内容在弹性框中可滚动?

如何在不使用滤镜的情况下使图像变暗? [复制]

如何在不需要额外点击的情况下使 DataGridCheckBoxColumn 可编辑?

如何在不使用 div 的情况下使 iframe 响应?

如何在不重复自己的情况下使该算法更懒惰?

如何在不打开应用程序的情况下使 ContentObserver 工作?