列表内的多行textview /具有动态单元格高度的ForEach SWIFTUI

时间:2020-04-09 17:16:45

标签: ios swiftui ios13 swiftui-list

需要在SwiftUI的表视图中显示html格式的文本,但是Text(“ Hi”)不允许我们在其中使用属性文本。

因此,尝试使用以下代码在List中显示多行HTML格式的文本。但是没有成功。

并且单元格的高度应根据内容大小的高度动态变化。

struct ContentView: View {
     @State var text = "Hello World This is line with more than two line of code to display as multiline textview inside the List cell."
    @State var bool: Bool = false
       var body: some View {
        List(0 ..< 5) { item in
                    TextView(text: self.$text)
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            }
       }
}

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextView {
        let myTextView = UITextView()
        myTextView.delegate = context.coordinator

        myTextView.font = UIFont(name: "HelveticaNeue", size: 15)
        myTextView.isScrollEnabled = false
        myTextView.isEditable = false
        myTextView.isUserInteractionEnabled = true
        myTextView.backgroundColor = .clear  //UIColor(white: 0.0, alpha: 0.05)
        myTextView.translatesAutoresizingMaskIntoConstraints = false
        myTextView.isScrollEnabled = false
        return myTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    class Coordinator : NSObject, UITextViewDelegate {
        var parent: TextView

        init(_ uiTextView: TextView) {
            self.parent = uiTextView
        }
        // with delegate methods implemented normally
    }
}

3 个答案:

答案 0 :(得分:0)

如何执行此操作:

myTextView.isScrollEnabled = true

这项工作吗?

答案 1 :(得分:0)

尝试以下代码,希望对您有所帮助。

(如果需要任何更正,请发表评论)

struct ContentView: View {

    let text = "<p>This is<br>a paragraph<br>with line breaks. This is <sup>superscripted</sup> text.</p>"
    @State var bool: Bool = false
    var body: some View {
        List(0..<5) { item in
            Text(self.text.htmlToAttributedString.string)
                .font(Font.system(size: 17))
                .multilineTextAlignment(.leading)
        }
    }
}

扩展

extension String {
    var htmlToAttributedString: NSAttributedString {
        guard let data = data(using: .utf8) else { return NSAttributedString() }
        do {
            return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
        } catch {
            return NSAttributedString()
        }
    }
}

输出

enter image description here

答案 2 :(得分:0)

暂时无法获得主链接,我从那里获得了该链接,但它可以根据需要工作。

ScrollView {
                    VStack {
                            ForEach(/*your implementation*/) { item in
                                UITextViewWrapper(text: self.$text, calculatedHeight: self.$size, onDone: nil)
                            }
                        .frame(minWidth: 0, maxWidth: proxy.size.width, minHeight: self.size, maxHeight: .infinity)
                    }
            }

fileprivate struct UITextViewWrapper: UIViewRepresentable {
    typealias UIViewType = UITextView

    @Binding var text: String
    @Binding var calculatedHeight: CGFloat
    var onDone: (() -> Void)?

    func makeUIView(context: UIViewRepresentableContext<UITextViewWrapper>) -> UITextView {
        let textField = UITextView()
        textField.delegate = context.coordinator

        textField.isEditable = false
        textField.font = UIFont.preferredFont(forTextStyle: .body)
        textField.isSelectable = false
        textField.isUserInteractionEnabled = false
        textField.isScrollEnabled = true
        textField.backgroundColor = UIColor.clear

        if nil != onDone {
            textField.returnKeyType = .done
        }

//        textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        return textField
    }

    func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<UITextViewWrapper>) {
        if uiView.text != self.text {
            uiView.text = self.text
        }
        if uiView.window != nil, !uiView.isFirstResponder {
            uiView.becomeFirstResponder()
        }
        UITextViewWrapper.recalculateHeight(view: uiView, result: $calculatedHeight)
    }

    fileprivate static func recalculateHeight(view: UIView, result: Binding<CGFloat>)
    {
        let newSize = view.sizeThatFits(CGSize(width: view.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
        if result.wrappedValue != newSize.height
        {
            DispatchQueue.main.async
                {
                result.wrappedValue = newSize.height // !! must be called asynchronously
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator(text: $text, height: $calculatedHeight, onDone: onDone)
//        return Coordinator()
    }

   final class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var calculatedHeight: Binding<CGFloat>
        var onDone: (() -> Void)?

        init(text: Binding<String>, height: Binding<CGFloat>, onDone: (() -> Void)? = nil) {
            self.text = text
            self.calculatedHeight = height
            self.onDone = onDone
        }

        func textViewDidChange(_ uiView: UITextView) {
            text.wrappedValue = uiView.text
            UITextViewWrapper.recalculateHeight(view: uiView, result: calculatedHeight)
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if let onDone = self.onDone, text == "\n" {
                textView.resignFirstResponder()
                onDone()
                return false
            }
            return true
        }
    }
}