UITextView+图片自适应的UICollectionReusableView动态高度问题
嘿,我太懂你这种纠结了——做测验类App,Collection View Header要适配带图+不定长文本的题目,还得动态调高度,TextView计算高度总是偏多的坑我之前也踩过好几次!来给你分享几个实战里验证过的解决办法:
一、先搞定TextView高度计算偏高的核心问题
TextView默认藏了两个“隐形空间”:textContainerInset(文本容器的内边距)和lineFragmentPadding(每行文本的左右边距,默认是8pt),这俩就是导致计算高度比实际需要高的罪魁祸首。你可以用这个精准计算的方法:
func calculateTextViewHeight(text: String, font: UIFont, maxWidth: CGFloat, customInsets: UIEdgeInsets = .zero) -> CGFloat { let tempTextView = UITextView() tempTextView.text = text tempTextView.font = font // 必须和你实际Header里的TextView设置完全一致 tempTextView.textContainerInset = customInsets tempTextView.textContainer.lineFragmentPadding = 0 // 干掉默认的左右边距 let fittingSize = tempTextView.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)) return fittingSize.height }
划重点:一定要和你实际Header中的TextView的字体、内边距、行高(如果自定义了)完全匹配,不然计算结果还是会不准。
二、综合计算Header的总高度
Header的高度是「文本高度 + 图片高度(如果有) + 元素间距 + Header内边距」的总和,你可以在Collection View的布局代理方法里完成这个计算:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let currentQuestion = yourQuestionsArray[section] let headerAvailableWidth = collectionView.bounds.width - 20 // 假设Header左右各留10pt内边距 var totalHeight: CGFloat = 20 // Header上下内边距总和 // 计算文本高度 if let questionText = currentQuestion.text { let textHeight: CGFloat // 用TextView的话用上面的计算方法,用Label的话用Label的sizeThatFits textHeight = calculateTextViewHeight(text: questionText, font: UIFont.systemFont(ofSize: 17), maxWidth: headerAvailableWidth) totalHeight += textHeight + 10 // 文本和图片之间的间距(如果有图) } // 计算图片高度(如果有图片) if let questionImage = currentQuestion.image { // 按图片的宽高比计算适配后的高度 let imageAspectRatio = questionImage.size.height / questionImage.size.width let imageDisplayWidth = headerAvailableWidth let imageDisplayHeight = imageDisplayWidth * imageAspectRatio totalHeight += imageDisplayHeight + 10 // 图片和Header底部的间距 } return CGSize(width: collectionView.bounds.width, height: totalHeight) }
这里要注意:所有的间距、内边距数值,必须和你Header实际布局里的数值完全对应,比如你布局时文本和图片之间是12pt,这里就要把+10改成+12。
三、更省心的Auto Layout方案
如果你用Auto Layout来搭建Header的内部布局(比如文本View顶部接Header内边距,图片接文本View下方,底部接Header内边距,形成完整的约束链),那可以直接让系统帮你计算高度,不用手动算每个元素:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { // 先拿到Header实例并配置数据 let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "QuestionHeaderID", for: IndexPath(item: 0, section: section)) as! QuestionHeader header.configure(with: yourQuestionsArray[section]) // 让系统根据约束计算最合适的高度 let fittingSize = header.systemLayoutSizeFitting( CGSize(width: collectionView.bounds.width, height: UIView.layoutFittingCompressedSize.height), withHorizontalFittingPriority: .required, // 宽度固定 verticalFittingPriority: .fittingSizeLevel // 高度自适应 ) return fittingSize }
这种方法的前提是:Header内部的所有元素约束必须完整,从顶部到底部没有约束断层。另外,TextView的textContainerInset和lineFragmentPadding还是要按之前的方法设置好,不然还是会有高度偏差。
额外小技巧
如果你的图片是网络加载的,加载完成后记得调用collectionView.collectionViewLayout.invalidateLayout()刷新布局,这样Header会重新计算高度,避免图片显示不全或者留白过多的问题。
内容的提问来源于stack exchange,提问作者Silent Knight

