web-dev-qa-db-fra.com

Comment rendre un texte multiligne dans SwiftUI List avec une hauteur correcte?

J'aimerais avoir une vue SwiftUI qui montre de nombreuses lignes de texte, avec les exigences suivantes:

  • Fonctionne à la fois sur macOS et iOS.
  • Affiche un grand nombre de chaînes (chaque chaîne est soutenue par un objet de modèle distinct).
  • Je peux appliquer un style arbitraire au texte multiligne.
  • Chaque chaîne de texte peut être de longueur arbitraire, s'étendant éventuellement sur plusieurs lignes et paragraphes.
  • La largeur maximale de chaque chaîne de texte est fixée à la largeur du conteneur. La hauteur est variable en fonction de la longueur réelle du texte.
  • Il n'y a pas de défilement pour chaque texte individuel, seulement la liste.
  • Les liens dans le texte doivent être tapables/cliquables.
  • Le texte est en lecture seule et n'a pas besoin d'être modifiable.

On dirait que la solution la plus appropriée serait d'avoir une vue Liste, enveloppant UITextView/NSTextView natif.

Voici ce que j'ai jusqu'à présent. Il met en œuvre la plupart des exigences SAUF avoir la bonne hauteur pour les lignes.

//
//  ListWithNativeTexts.Swift
//  SUIToy
//
//  Created by Jaanus Kase on 03.05.2020.
//  Copyright © 2020 Jaanus Kase. All rights reserved.
//

import SwiftUI

let number = 20

struct ListWithNativeTexts: View {
    var body: some View {
        List(texts(count: number), id: \.self) { text in
            NativeTextView(string: text)
        }
    }
}

struct ListWithNativeTexts_Previews: PreviewProvider {
    static var previews: some View {
        ListWithNativeTexts()
    }
}

func texts(count: Int) -> [String] {
    return (1...count).map {
        (1...$0).reduce("Hello https://example.com:", { $0 + " " + String($1) })
    }
}

#if os(iOS)
typealias NativeFont = UIFont
typealias NativeColor = UIColor

struct NativeTextView: UIViewRepresentable {

    var string: String

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()

        textView.isEditable = false
        textView.isScrollEnabled = false
        textView.dataDetectorTypes = .link
        textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        textView.textContainer.lineFragmentPadding = 0

        let attributed = attributedString(for: string)
        textView.attributedText = attributed

        return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
    }

}
#else
typealias NativeFont = NSFont
typealias NativeColor = NSColor

struct NativeTextView: NSViewRepresentable {

    var string: String

    func makeNSView(context: Context) -> NSTextView {
        let textView = NSTextView()
        textView.isEditable = false
        textView.isAutomaticLinkDetectionEnabled = true
        textView.isAutomaticDataDetectionEnabled = true
        textView.textContainer?.lineFragmentPadding = 0
        textView.backgroundColor = NSColor.clear

        textView.textStorage?.append(attributedString(for: string))
        textView.isEditable = true
        textView.checkTextInDocument(nil) // make links clickable
        textView.isEditable = false

        return textView
    }

    func updateNSView(_ textView: NSTextView, context: Context) {

    }

}
#endif

func attributedString(for string: String) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 4
    let range = NSMakeRange(0, (string as NSString).length)

    attributedString.addAttribute(.font, value: NativeFont.systemFont(ofSize: 24, weight: .regular), range: range)
    attributedString.addAttribute(.foregroundColor, value: NativeColor.red, range: range)
    attributedString.addAttribute(.backgroundColor, value: NativeColor.yellow, range: range)
    attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
    return attributedString
}

Voici ce qu'il produit sur iOS. La sortie macOS est similaire.

iOS output

Comment obtenir cette solution pour dimensionner les vues de texte avec des hauteurs correctes?

Une approche que j'ai essayée, mais non montrée ici, est de donner la hauteur "de l'extérieur vers l'intérieur" - pour spécifier la hauteur sur la ligne de liste elle-même avec cadre. Je peux calculer la hauteur d'un NSAttributedString lorsque je connais la largeur, que je peux obtenir avec geoReader. Cela fonctionne presque, mais est bogué et ne me semble pas bien, donc je ne le montre pas ici.

6
Jaanus

"Je voudrais avoir une vue SwiftUI qui montre plusieurs lignes de texte, ..."

Utilisation de SwiftUI uniquement:

Je crois que cela coche tous vos articles en ce qui concerne SwiftUI. MacOS, je vous laisse vous adapter. Votre texte peut être défini par programmation, de même que tous les styles différents que vous choisirez d'adopter. J'ai montré l'exemple aussi simplement que possible en utilisant des propriétés statiques. Vous pouvez rendre la plupart des cellules exploitables avec un NavigationLink

Voici l'aperçu dans Xcode pour IOS:

enter image description here

C'est MacOS: enter image description here

Voici le code lui-même, aucune autre pièce n'était requise.

//
//  ListWithNativeTextz.Swift
//  CRecipes
//
//  Created by Zahirudeen Premji on 5/7/20.
//  With credit to Paul Hudson post

import SwiftUI
import SafariServices

struct ListWithNativeTextz: View {

    let number = 20
    let introText = "Hello "
    let urlText = "https://hackingwithswift.com: "

    var body: some View {

        List(texts(count: number), id: \.self) { text in
            NavigationLink(destination: NewDetail()) {

                Text(self.introText)
                    .font(.headline)
                    .foregroundColor(.blue)
                + Text(self.urlText)
                    .font(.subheadline)
                    .foregroundColor(.green)
                + Text(text)
                    .font(.caption)
                    .foregroundColor(.red)
            }
        }
    }

    func texts(count: Int) -> [String] {
        return (1...count).map {
            (1...$0).reduce("", {$0 + " " + String($1)})
        }
    }
}

struct NewDetail: View {

    // add a SafariView here to display the link in Safari
    // You must import SafariServices and then follow instruction here :
    // https://stackoverflow.com/questions/56518029/how-do-i-use-sfsafariviewcontroller-with-swiftui
    // https://www.hackingwithswift.com/read/32/3/how-to-use-sfsafariviewcontroller-to-browse-a-web-page

    var body: some View {
        Text("Hello There")
    }
}

struct ListWithNativeTextz_Previews: PreviewProvider {
    static var newDetail = NewDetail()
    static var previews: some View {
        ListWithNativeTextz()
    }
}

Branchez pour Paul Hudson dans le texte à: https://www.hackingwithswift.com/quick-start/swiftui/how-to-combine-text-views-together

0
ZUser