ios - SwiftUI 中自定义 UIViewRepresentable UITextView 的框架高度问题
问题描述
我正在通过 UIViewRepresentable 为 SwiftUI 构建自定义 UITextView。它旨在显示NSAttributedString
和处理链接按下。NavigationView
一切正常,但是当我在带有内联标题的 a 内显示此视图时,框架高度完全混乱。
import SwiftUI
struct AttributedText: UIViewRepresentable {
class Coordinator: NSObject, UITextViewDelegate {
var parent: AttributedText
init(_ view: AttributedText) {
parent = view
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
parent.linkPressed(URL)
return false
}
}
let content: NSAttributedString
@Binding var height: CGFloat
var linkPressed: (URL) -> Void
public func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.backgroundColor = .clear
textView.isEditable = false
textView.isUserInteractionEnabled = true
textView.delegate = context.coordinator
textView.isScrollEnabled = false
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textView.dataDetectorTypes = .link
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
return textView
}
public func updateUIView(_ view: UITextView, context: Context) {
view.attributedText = content
// Compute the desired height for the content
let fixedWidth = view.frame.size.width
let newSize = view.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
DispatchQueue.main.async {
self.height = newSize.height
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
struct ContentView: View {
private var text: NSAttributedString {
NSAttributedString(string: "Eartheart is the principal settlement for the Gold Dwarves in East Rift and it is still the cultural and spiritual center for its people. Dwarves take on pilgrimages to behold the great holy city and take their trips from other countries and the deeps to reach their goal, it use to house great temples and shrines to all the Dwarven pantheon and dwarf heroes but after the great collapse much was lost.\n\nThe lords of their old homes relocated here as well the Deep Lords. The old ways of the Deep Lords are still the same as they use intermediaries and masking themselves to undermine the attempts of assassins or drow infiltrators. The Gold Dwarves outnumber every other race in the city and therefor have full control of the city and it's communities.")
}
@State private var height: CGFloat = .zero
var body: some View {
NavigationView {
List {
AttributedText(content: text, height: $height, linkPressed: { url in print(url) })
.frame(height: height)
Text("Hello world")
}
.listStyle(GroupedListStyle())
.navigationBarTitle(Text("Content"), displayMode: .inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
运行此代码时,您会看到AttributedText
单元格太小而无法容纳其内容。
当您从 中删除displayMode: .inline
参数时navigationBarTitle
,它显示得很好。
但是,如果我添加另一行来显示高度值 ( Text("\(height)")
),它会再次中断。
也许这是某种通过状态更改的视图更新触发的竞争条件?值本身是正确的height
,只是框架实际上并没有那么高。有解决方法吗?
使用ScrollView
with a确实可以解决问题,但由于内容在真实应用程序中的显示方式VStack
,我真的更喜欢使用 a 。List
解决方案
所以我有这个确切的问题。
解决方案并不漂亮,但我找到了一个有效的解决方案:
首先,您需要继承 UITextView 以便您可以将其内容大小传递回 SwiftIU:
public class UITextViewWithSize: UITextView {
@Binding var size: CGSize
public init(size: Binding<CGSize>) {
self._size = size
super.init(frame: .zero, textContainer: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func layoutSubviews() {
super.layoutSubviews()
self.size = sizeThatFits(.init(width: frame.width, height: 0))
}
}
完成此操作后,您需要为您的自定义 UITextView 创建一个 UIViewRepresentable:
public struct HyperlinkTextView: UIViewRepresentable {
public typealias UIViewType = UITextViewWithSize
private var text: String
private var font: UIFont?
private var foreground: UIColor?
@Binding private var size: CGSize
public init(_ text: String, font: UIFont? = nil, foreground: UIColor? = nil, size: Binding<CGSize>) {
self.text = text
self.font = font
self.foreground = foreground
self._size = size
}
public func makeUIView(context: Context) -> UIViewType {
let view = UITextViewWithSize(size: $size)
view.isEditable = false
view.dataDetectorTypes = .all
view.isScrollEnabled = false
view.text = text
view.textContainer.lineBreakMode = .byTruncatingTail
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.setContentCompressionResistancePriority(.required, for: .vertical)
view.textContainerInset = .zero
if let font = font {
view.font = font
} else {
view.font = UIFont.preferredFont(forTextStyle: .body)
}
if let foreground = foreground {
view.textColor = foreground
}
view.sizeToFit()
return view
}
public func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.text = text
uiView.layoutSubviews()
}
}
现在我们可以轻松访问视图的内容大小,我们可以使用它来强制视图适合该大小的容器。出于某种原因,仅在视图上使用 .frame 是行不通的。视图只是忽略了它给定的框架。但是当将它放入几何阅读器时,它似乎按预期增长。
GeometryReader { proxy in
HyperlinkTextView(bio, size: $bioSize)
.frame(maxWidth: proxy.frame(in: .local).width, maxHeight: .infinity)
}
.frame(height: bioSize.height)
推荐阅读
- rust - 如何使用打印!编译发布时使用 Rocket?
- vue.js - Vue - 如何自定义到 vue.config.js 中定义的页面的路由?
- javascript - 如何在一个模块 React Js 中显示多个数据
- html - 如何使用 understrap 主题在我的子菜单中创建嵌套子菜单?
- php - 在 Laravel 中创建 Api,错误:未捕获(承诺中) SyntaxError: Unexpected token < in JSON at position 0
- python - 如何使用 apply 遍历 panda 数据框并访问下一个值?
- itext - iText - 重复字段出现在最后一页
- vulkan - 在 Vulkan 的 GPU 上执行命令缓冲区时可以重置吗?
- sql - 基于 Rails 中的另一个表更新一个表
- apache - Apache 反向代理 - 没有 / 的 URL 被拒绝