如何在不滚动 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)的主要内容,如果未能解决你的问题,请参考以下文章