0
votes

Need to display html formatted text inside the tableview in SwiftUI but Text("Hi") is not allowing us to use Attributed text inside it.

So trying following code to display multiline HTML formatted text inside List. But with no success.

And cell height should be dynamic according to the content size height.

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

3 Answers

0
votes

how about if you do this:

myTextView.isScrollEnabled = true

does this work?

0
votes

Try below code hope it will help you.

(for any correction you want please put comment)

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

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()
        }
    }
}

Output

enter image description here

0
votes

Couldn't get for now the main link, from where I got this but it works as I needed.

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
        }
    }
}