многострочное текстовое представление внутри списка / SWIFTUI ForEach с динамикой c высота ячейки - PullRequest
0 голосов
/ 09 апреля 2020

Необходимо отображать html отформатированный текст внутри табличного представления в SwiftUI, но Text ("Hi") не позволяет нам использовать текст Attributed внутри него.

Поэтому пробуем следующий код для отображения многострочного HTML отформатированный текст внутри списка. Но безуспешно.

И высота ячейки должна быть динамической c в соответствии с высотой размера содержимого.

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 голосов
/ 10 апреля 2020

Попробуйте приведенный ниже код, надеюсь, он вам поможет.

(для любого исправления, которое вы хотите, пожалуйста, оставьте комментарий)

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

0 голосов
/ 15 апреля 2020

Не могу пока получить основную ссылку, откуда я это получил, но она работает так, как мне нужно.

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
        }
    }
}
0 голосов
/ 10 апреля 2020

как насчет того, если вы сделаете это:

myTextView.isScrollEnabled = true

это работает?

...