Sự cố chuỗi nsattribution khi chuyển đổi html thành chuỗi được gán

Công thức này cho biết cách định dạng nội dung của SwiftUI

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 bằng HTML qua
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 trên bất kỳ phiên bản SwiftUI nào

Kết quả cuối cùng trông như thế này

Sự cố chuỗi nsattribution khi chuyển đổi html thành chuỗi được gán

Có hai giải pháp trong công thức này

  1. SwiftUI 3 (iOS 15, macOS 12) mang đến trình bao bọc
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    1 mới xung quanh chế độ xem
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 và
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 hoạt động tự nhiên với nó. Có một số mã mẫu ở cuối bài viết hướng dẫn cách sử dụng
  2. SwiftUI 1 và 2 (iOS 13-14, macOS 10. 15-11) mờ nhạt một cách đáng ngạc nhiên khi làm việc với
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 trong
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 lượt xem. không có hỗ trợ mặc định cho nó và thậm chí cố gắng đưa trực tiếp chuỗi được gán vào Chế độ xem dẫn đến một số vấn đề về bộ nhớ kỳ lạ. Nó không phải là tất cả vô vọng, mặc dù. đọc tiếp để xem giải pháp hiệu quả cho tất cả các phiên bản SwiftUI

Công thức cho tất cả các phiên bản

Giải pháp chưa hoàn thiện đầy đủ vì nó không hỗ trợ tất cả các thẻ HTML - nhưng nó luôn hoàn thành công việc

Nó cũng không hỗ trợ các siêu liên kết ngay lập tức - để kiểm tra xem điều đó được thực hiện như thế nào, hãy sử dụng công thức Siêu liên kết trong SwiftUI Text. Sự khác biệt chính là công thức này chỉ hoạt động với chế độ xem Văn bản SwiftUI, đây có thể là một lợi thế lớn

Trước tiên, chúng tôi sẽ kiểm tra giải pháp hiệu quả, giải pháp nào

  1. Hỗ trợ hầu hết các tùy chọn định dạng HTML,
  2. không có vấn đề về kích thước hoặc bố cục,
  3. sử dụng
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 gốc, cho phép bạn sử dụng tất cả các công cụ sửa đổi chế độ xem dành riêng cho
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0

Sau đó, chúng ta sẽ xem xét một số lần thử khác không thành công do sự cố SwiftUI nội bộ

Giải pháp làm việc

OK, vì vậy giải pháp làm việc là hai phần

  1. Hiển thị một
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 trong một
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0, và
  2. Chuyển đổi văn bản HTML thành
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 và giải quyết một số chi tiết

Hiển thị NSAttributionString trong một văn bản

Phần đầu tiên chia

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 thành các chuỗi con được phân bổ rõ ràng, tạo và tạo kiểu cho một
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 dựa trên các thuộc tính của nó. Sau đó, nó dựa trên phép nối
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 - thực tế là bạn có thể sử dụng toán tử
extension Text {
  init(_ attributedString: NSAttributedString) {
    self.init("") // initial, empty Text

    // scan the attributed string for distinctly attributed regions
    attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), 
                                         options: []) { (attrs, range, _) in
      let string = attributedString.attributedSubstring(from: range).string
      var text = Text(string)

      // then, read applicable attributes and apply them to the Text

      if let font = attrs[.font] as? UIFont {
        // this takes care of the majority of formatting - text size, font family,
        // font weight, if it's italic, etc.
        text = text.font(.init(font))
      }

      if let color = attrs[.foregroundColor] as? UIColor {
        text = text.foregroundColor(Color(color))
      }

      if let kern = attrs[.kern] as? CGFloat {
        text = text.kerning(kern)
      }

      if #available(iOS 14.0, *) {
        if let tracking = attrs[.tracking] as? CGFloat {
          text = text.tracking(tracking)
        }
      }

      if let strikethroughStyle = attrs[.strikethroughStyle] as? NSNumber, 
         strikethroughStyle != 0 {
        if let strikethroughColor = (attrs[.strikethroughColor] as? UIColor) {
          text = text.strikethrough(true, color: Color(strikethroughColor))
        } else {
          text = text.strikethrough(true)
        }
      }

      if let underlineStyle = attrs[.underlineStyle] as? NSNumber,
         underlineStyle != 0 {
        if let underlineColor = (attrs[.underlineColor] as? UIColor) {
          text = text.underline(true, color: Color(underlineColor))
        } else {
          text = text.underline(true)
        }
      }

      if let baselineOffset = attrs[.baselineOffset] as? NSNumber {
        text = text.baselineOffset(CGFloat(baselineOffset.floatValue))
      }

      // append the newly styled subtext to the rest of the text
      self = self + text
    }
  }
}
4 trên hai phiên bản
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 và SwiftUI sẽ kết hợp chúng thành một
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 mới, trong khi vẫn đảm bảo bố cục và kích thước

________số 8

Có một nhược điểm tiềm ẩn đối với điều này - toán tử

extension Text {
  init(_ attributedString: NSAttributedString) {
    self.init("") // initial, empty Text

    // scan the attributed string for distinctly attributed regions
    attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), 
                                         options: []) { (attrs, range, _) in
      let string = attributedString.attributedSubstring(from: range).string
      var text = Text(string)

      // then, read applicable attributes and apply them to the Text

      if let font = attrs[.font] as? UIFont {
        // this takes care of the majority of formatting - text size, font family,
        // font weight, if it's italic, etc.
        text = text.font(.init(font))
      }

      if let color = attrs[.foregroundColor] as? UIColor {
        text = text.foregroundColor(Color(color))
      }

      if let kern = attrs[.kern] as? CGFloat {
        text = text.kerning(kern)
      }

      if #available(iOS 14.0, *) {
        if let tracking = attrs[.tracking] as? CGFloat {
          text = text.tracking(tracking)
        }
      }

      if let strikethroughStyle = attrs[.strikethroughStyle] as? NSNumber, 
         strikethroughStyle != 0 {
        if let strikethroughColor = (attrs[.strikethroughColor] as? UIColor) {
          text = text.strikethrough(true, color: Color(strikethroughColor))
        } else {
          text = text.strikethrough(true)
        }
      }

      if let underlineStyle = attrs[.underlineStyle] as? NSNumber,
         underlineStyle != 0 {
        if let underlineColor = (attrs[.underlineColor] as? UIColor) {
          text = text.underline(true, color: Color(underlineColor))
        } else {
          text = text.underline(true)
        }
      }

      if let baselineOffset = attrs[.baselineOffset] as? NSNumber {
        text = text.baselineOffset(CGFloat(baselineOffset.floatValue))
      }

      // append the newly styled subtext to the rest of the text
      self = self + text
    }
  }
}
4 chỉ hoạt động trên
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 và chỉ một số công cụ sửa đổi chế độ xem của nó thực sự trả về
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 chứ không phải
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
00. e. g, cái này không hoạt động

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}

Điều này có nghĩa là không phải tất cả các thuộc tính đều có thể được áp dụng cho một

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0. e. g, thuộc tính
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
02 bị bỏ qua vì không có cách nào để phát hiện một lần nhấn vào
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 và vẫn trả về một
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0. Tuy nhiên, nó bao gồm hầu như tất cả các định dạng từ một chuỗi được gán mà bạn thường cần. Đây là mã

extension Text {
  init(_ attributedString: NSAttributedString) {
    self.init("") // initial, empty Text

    // scan the attributed string for distinctly attributed regions
    attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), 
                                         options: []) { (attrs, range, _) in
      let string = attributedString.attributedSubstring(from: range).string
      var text = Text(string)

      // then, read applicable attributes and apply them to the Text

      if let font = attrs[.font] as? UIFont {
        // this takes care of the majority of formatting - text size, font family,
        // font weight, if it's italic, etc.
        text = text.font(.init(font))
      }

      if let color = attrs[.foregroundColor] as? UIColor {
        text = text.foregroundColor(Color(color))
      }

      if let kern = attrs[.kern] as? CGFloat {
        text = text.kerning(kern)
      }

      if #available(iOS 14.0, *) {
        if let tracking = attrs[.tracking] as? CGFloat {
          text = text.tracking(tracking)
        }
      }

      if let strikethroughStyle = attrs[.strikethroughStyle] as? NSNumber, 
         strikethroughStyle != 0 {
        if let strikethroughColor = (attrs[.strikethroughColor] as? UIColor) {
          text = text.strikethrough(true, color: Color(strikethroughColor))
        } else {
          text = text.strikethrough(true)
        }
      }

      if let underlineStyle = attrs[.underlineStyle] as? NSNumber,
         underlineStyle != 0 {
        if let underlineColor = (attrs[.underlineColor] as? UIColor) {
          text = text.underline(true, color: Color(underlineColor))
        } else {
          text = text.underline(true)
        }
      }

      if let baselineOffset = attrs[.baselineOffset] as? NSNumber {
        text = text.baselineOffset(CGFloat(baselineOffset.floatValue))
      }

      // append the newly styled subtext to the rest of the text
      self = self + text
    }
  }
}

Nếu bạn thử với các phiên bản

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 kết quả, bạn sẽ phát hiện ra rằng việc áp dụng công cụ sửa đổi chế độ xem
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
06 không hoạt động, tôi. e

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0

Mặt khác, các công cụ sửa đổi khác, chẳng hạn như

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
07,
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
08 hoặc
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
09 hoạt động như mong đợi. Đây có vẻ là sự không nhất quán nội bộ của SwiftUI, nhưng điều quan trọng cần lưu ý là phần tiếp theo

Hiển thị HTML trong văn bản

Swift có thể dễ dàng chuyển đổi một chuỗi HTML thành một

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 và sau đó chúng ta có thể đưa
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 đó vào trình khởi tạo từ phần trước. Điều duy nhất còn lại để sắp xếp là làm thế nào để đặt phông chữ cho toàn bộ văn bản HTML. Để làm điều này, chúng tôi sẽ sử dụng một mẹo nhỏ - chúng tôi sẽ nhúng chuỗi có định dạng HTML vào khung tài liệu HTML và chỉ định kiểu dáng của nó thông qua CSS

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
1

Và đó là nó. Bạn có thể thử giải pháp với mã này

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
2
Giải pháp không hiệu quả

Gói UILabel trong UIViewRepftimeable

Giải pháp này liên quan đến việc gói

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
12 trong
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
13 và sau đó gán chuỗi được gán cho thuộc tính
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
14 của nó. Một cái gì đó như thế này

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
6

Vấn đề với phương pháp này là văn bản không chia thành nhiều dòng. Tôi đã sửa phần đó, nhưng chế độ xem vẫn không có kích thước phù hợp và có vấn đề về bố cục, đặc biệt là khi được gói trong

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
15. Tôi đã có thể giải quyết nó cho một trường hợp sử dụng cụ thể, nhưng không thể tìm ra giải pháp hoạt động trong bất kỳ bố cục nào, giống như giải pháp dựa trên
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0

Sử dụng FlowLayoutView

Giải pháp này liên quan đến việc tạo chế độ xem tùy chỉnh cho mọi dải con chuỗi được phân bổ và sắp xếp chúng bằng Bố cục luồng. Bố cục Luồng sắp xếp thứ tự chế độ xem theo thứ tự trong một dòng và tự động xử lý ngắt dòng và ngắt dòng, vì vậy, đây có vẻ là ứng cử viên hoàn hảo. Ít nhất là cho đến khi SwiftUI quyết định can thiệp

Vì bất kỳ lý do gì,

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
17 của SwiftUI gặp sự cố khi bạn lưu trữ một
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 trong một
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
19. Hãy xem xét ví dụ này

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
1

Nếu chạy mẫu này, bạn sẽ gặp một loạt lỗi

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
17, dẫn đến sự cố

Screenshot%202021-05-26%20at%2011.51.47

Tôi đã cố gắng giải quyết vấn đề này theo nhiều cách

  • Loại bỏ
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    21 và làm việc với hằng số
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 đơn giản
  • Không lưu trữ
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 ở bất cứ đâu, chỉ chuyển nó vào trình khởi tạo
  • Thậm chí không chuyển
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 cho chế độ xem mà thay vào đó chuyển trực tiếp mảng mục
  • Lưu trữ
    var concatenatedText: Text {
      Text("My") + Text(" text").onTapGesture {
      } // nope, as onTapGesture doesn't return a Text
    }
    0 bên ngoài toàn bộ phân cấp chế độ xem ứng dụng

Bây giờ, nếu bạn chuyển một chuỗi phân bổ được mã hóa cứng cho ứng dụng, nó sẽ không gặp sự cố. Tuy nhiên, nếu bạn định tính toán nó ở đâu đó trong mã (như chúng ta làm khi chuyển đổi chuỗi HTML) hoặc thậm chí đọc nó dưới dạng

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
26, thì nó sẽ gặp sự cố. Tôi nghi ngờ có một vấn đề sâu hơn đang hoạt động, đó cũng có thể là lý do tại sao Apple không cung cấp bất kỳ hỗ trợ SwiftUI gốc nào cho
var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0

May mắn thay, giải pháp

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0 luôn hoạt động đủ tốt, ít nhất là cho đến khi phiên bản tiếp theo của SwiftUI cung cấp cho chúng tôi sự hỗ trợ mà chúng tôi cần (EDIT. Sự hỗ trợ cuối cùng đã có ở đây trong SwiftUI 3. )

Giải pháp SwiftUI 3

SwiftUI 3 hỗ trợ chuỗi được phân bổ nguyên bản, cũng như hiển thị chúng trong những năm

var concatenatedText: Text {
  Text("My") + Text(" text").onTapGesture {
  } // nope, as onTapGesture doesn't return a Text
}
0, dẫn đến đoạn mã sau

NSAttributionString là gì?

Một đối tượng NSAttributionString quản lý các chuỗi ký tự và tập hợp các thuộc tính được liên kết (ví dụ: phông chữ và kerning) áp dụng cho từng ký tự hoặc phạm vi ký tự trong chuỗi. An association of characters and their attributes is called an attributed string.

Nsmutableattributionstring là gì?

Một chuỗi có thể thay đổi với các thuộc tính được liên kết (chẳng hạn như kiểu trực quan, siêu liên kết hoặc dữ liệu trợ năng) cho các phần văn bản của nó .