【问题标题】:How to use Attributed String in SwiftUI如何在 SwiftUI 中使用属性化字符串
【发布时间】:2020-04-19 05:12:10
【问题描述】:

如何在 SwiftUI 中使用 AttributedString。没有可用的 API 在文本中使用 AttributedString

【问题讨论】:

    标签: swift swiftui


    【解决方案1】:

    iOS 15 和 Swift 5.5

    Text现在支持markdown,你也可以创建自定义属性:

    您甚至可以远程获取已定义的属性,例如:


    iOS 13 和 14

    您可以将多个 Text 对象与一个简单的 + 运算符组合在一起,这将处理一些属性:

    每一个都可以有多个特定的修饰符


    完全支持的后备!

    由于它不直接支持Text(直到 iOS 15),您可以将UILabel 带到那里并随意修改它:

    实施:

    struct UIKLabel: UIViewRepresentable {
    
        typealias TheUIView = UILabel
        fileprivate var configuration = { (view: TheUIView) in }
    
        func makeUIView(context: UIViewRepresentableContext<Self>) -> TheUIView { TheUIView() }
        func updateUIView(_ uiView: TheUIView, context: UIViewRepresentableContext<Self>) {
            configuration(uiView)
        }
    }
    

    用法:

    var body: some View {
        UIKLabel {
            $0.attributedText = NSAttributedString(string: "HelloWorld")
        }
    }
    

    【讨论】:

    • 有没有办法让这些文本之一可点击? tapGesture 似乎不合适,因为它返回 some View 而不是 Text?
    • +1 表示更多原生但访问更少。它涵盖了格式化文本的一些长度,而不添加额外和复杂的代码。
    • 用法:“参数传递给不带参数的调用”
    • SwiftUI 2.0 有一个名为 Label 的结构。您可能忘记实现自己的 Label 并且它与原始版本冲突 @Ixx
    • @MojtabaHosseini - 如何在UIKLabel 上启用对齐?已尝试在堆栈上包装并拥有 View 似乎没有任何效果。
    【解决方案2】:

    attributed string 的思想是带有属性的字符串。在 SwiftUI 中,这个想法是通过 Text 属性修饰符和 + 运算符实现的。如下例所示:

    Group {
        Text("Bold")
            .fontWeight(.bold) +
        Text("Underlined")
            .underline() +
        Text("Color")
            .foregroundColor(Color.red)
    }
    

    【讨论】:

    • 如果您支持一种以上的语言,这肯定不好。
    • 这里如果我们像这样使用并且如果我们想添加点击颜色或任何其他我们不能的文本
    • 似乎有可能用这个概念在纯 swift UI 中创建属性字符串类型系统
    【解决方案3】:

    有很多答案都使用UILabel 或UITextView。我很好奇是否可以创建一个不依赖任何 UIKit 功能的原生 SwiftUI 实现。这代表了一个适合我当前需求的实现。这与 NSAttributedString 规范的完整实现相去甚远,但对于最基本的需求来说绝对足够好。接受 HTML 字符串的NSAttributedString 的构造函数是我制作的自定义类别,非常容易实现。如果有人想用它运行并创建一个更强大和更完整的组件,那么你就是我的英雄。可惜我没有时间做这样的项目。

    //
    //  AttributedText.swift
    //
    import SwiftUI
    
    struct AttributedTextBlock {
        let content: String
        let font: Font?
        let color: Color?
    }
    
    struct AttributedText: View {
        var attributedText: NSAttributedString?
        
        private var descriptions: [AttributedTextBlock] = []
        
        init(_ attributedText: NSAttributedString?) {
            self.attributedText = attributedText
            
            self.extractDescriptions()
        }
        
        init(stringKey: String) {
            self.init(NSAttributedString(htmlString: NSLocalizedString(stringKey, comment: "")))
        }
        
        init(htmlString: String) {
            self.init(NSAttributedString(htmlString: htmlString))
        }
        
        private mutating func extractDescriptions()  {
            if let text = attributedText {
                text.enumerateAttributes(in: NSMakeRange(0, text.length), options: [], using: { (attribute, range, stop) in
                    let substring = (text.string as NSString).substring(with: range)
                    let font =  (attribute[.font] as? UIFont).map { Font.custom($0.fontName, size: $0.pointSize) }
                    let color = (attribute[.foregroundColor] as? UIColor).map { Color($0) }
                    descriptions.append(AttributedTextBlock(content: substring,
                                                            font: font,
                                                            color: color))
                })
            }
        }
        
        var body: some View {
            descriptions.map { description in
                Text(description.content)
                    .font(description.font)
                    .foregroundColor(description.color)
            }.reduce(Text("")) { (result, text) in
                result + text
            }
        }
    }
    
    struct AttributedText_Previews: PreviewProvider {
        static var previews: some View {
            AttributedText(htmlString: "Hello! <b>World</b>")
        }
    }
    

    【讨论】:

    • 这是出色的工作!我添加了一个滚动视图并开始参加比赛!我已经持久化了 40K+ 字节 NSAttributedStrings 的数据对象,并且视图可以毫无延迟地打开它们(之前它们需要 5 秒或更长时间才能出现)。如果可以的话,我会多次投票。
    • 干得好!不幸的是获取日志: AttributeGraph:在视图中使用属性 X 时检测到循环,最终导致崩溃:/
    • 有趣,是的,我不知道为什么,可能是您的属性字符串有一个代码没有考虑的用例。如果您能够找到错误,请发布它的要点,我将通过错误修复更新答案。
    【解决方案4】:

    如果你想用 NSAttributedString 实现动态高度文本,你可以使用这个:

    实施:

    struct TextWithAttributedString: View {
    
    var attributedText: NSAttributedString
    @State private var height: CGFloat = .zero
    
    var body: some View {
        InternalTextView(attributedText: attributedText, dynamicHeight: $height)
            .frame(minHeight: height)
    }
    
    struct InternalTextView: UIViewRepresentable {
    
        var attributedText: NSAttributedString
        @Binding var dynamicHeight: CGFloat
    
        func makeUIView(context: Context) -> UITextView {
            let textView = UITextView()
            textView.textAlignment = .justified
            textView.isScrollEnabled = false
            textView.isUserInteractionEnabled = false
            textView.showsVerticalScrollIndicator = false
            textView.showsHorizontalScrollIndicator = false
            textView.allowsEditingTextAttributes = false
            textView.backgroundColor = .clear
            textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
            textView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
            return textView
        }
    
        func updateUIView(_ uiView: UITextView, context: Context) {
            uiView.attributedText = attributedText
            DispatchQueue.main.async {
                dynamicHeight = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
            }
        }
    }
    

    }

    用法:

        VStack {
           TextWithAttributedString(attributedText: viewModel.description)
             .padding([.leading, .trailing], self.horizontalPadding)
             .layoutPriority(1)
             .background(Color.clear)
        }
        .transition(.opacity)
        .animation(.linear)
    

    【讨论】:

      【解决方案5】:

      iOS 15(测试版)

      我们终于得到AttributedString!它真的很容易使用。

      struct ContentView: View {
          var body: some View {
              VStack(spacing: 40) {
                  
                  /// Note: You can replace `$0` with `string in string`
                  
                  VStack {
                      Text("Regular")
                      Text("Italics") { $0.font = Font.system(size: 17).italic() }
                      Text("Bold") { $0.font = Font.system(size: 17).bold() }
                      Text("Strikethrough") { $0.strikethroughStyle = Text.LineStyle(pattern: .solid, color: .red) }
                      Text("Code") { $0.font = Font.system(size: 17, design: .monospaced) }
                      Text("Foreground Color") { $0.foregroundColor = Color.purple }
                      Text("Background Color") { $0.backgroundColor = Color.yellow }
                      Text("Underline") { $0.underlineColor = Color.green }
                  }
                  
                  VStack {
                      Text("Kern") { $0.kern = CGFloat(10) }
                      Text("Tracking") { $0.tracking = CGFloat(10) }
                  }
                  
                  VStack {
                      Text("Baseline Offset") { $0.baselineOffset = CGFloat(10) }
                      Text("Link") { $0.link = URL(string: "https://apple.com") }
                  }
              }
          }
      }
      
      /// extension to make applying AttributedString even easier
      extension Text {
          init(_ string: String, configure: ((inout AttributedString) -> Void)) {
              var attributedString = AttributedString(string) /// create an `AttributedString`
              configure(&attributedString) /// configure using the closure
              self.init(attributedString) /// initialize a `Text`
          }
      }
      

      要将属性应用于特定范围,请使用range(of:options:locale:) 方法。

      struct ContentView: View {
          var body: some View {
              Text("Some Attributed String") { string in
                  string.foregroundColor = .blue
                  if let range = string.range(of: "Attributed") { /// here!
                      string[range].foregroundColor = .red
                  }
              }
          }
      }
      

      请参阅我的article 了解更多详情。也可以use Markdown!

      【讨论】:

        【解决方案6】:

        要为 iOS 14 添加一种不同的样式,这对我有用:

        struct ItalicTextView: View {
          let text: String
          let italicText: String
        
          var body: some View {
            let array = text.components(separatedBy: italicText)
            array.reduce(Text(""), {
              if $1 == array.last {
                return $0 + Text($1)
              }
              return $0 + Text($1) + Text(italicText).italic()
            })
          }
        }
        

        用法:

         var body: some View {
         HStack(alignment: .center, spacing: 0) {
            ItalicTextView(text: notification.description, italicText: "example")
              .multilineTextAlignment(.leading)
              .fixedSize(horizontal: false, vertical: true)
              .padding(.vertical, 16)
              .padding(.horizontal, 8)
          }
        }
        

        }

        【讨论】:

          【解决方案7】:
          1. 适用于 MacOS
          2. 工作速度比 SwiftUI 的 Text(someInstanceOf_AttributedString) 快得多
          3. 能够在单击或选择文本时选择文本而不需要重置字体属性
          import SwiftUI
          import Cocoa
          
          @available(OSX 11.0, *)
          public struct AttributedText: NSViewRepresentable {
              private let text: NSAttributedString
              
              public init(attributedString: NSAttributedString) {
                  text = attributedString
              }
              
              public func makeNSView(context: Context) -> NSTextField {
                  let textField = NSTextField(labelWithAttributedString: text)
                  textField.isSelectable = true
                  textField.allowsEditingTextAttributes = true // Fix of clear of styles on click
                  
                  textField.preferredMaxLayoutWidth = textField.frame.width
                  
                  return textField
              }
              
              public func updateNSView(_ nsView: NSTextField, context: Context) {
                  nsView.attributedStringValue = text
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2022-06-10
            • 1970-01-01
            • 1970-01-01
            • 2013-07-04
            • 2014-12-21
            • 2012-05-20
            • 1970-01-01
            • 2023-01-31
            相关资源
            最近更新 更多