我在快速用户界面中有一个复杂的 struct ,可以检测字符串中的链接和标签.该 struct 修改它们的 colored颜色 ,并检测对特定标签和链接的点击.我想添加一个背景 colored颜色 的标签,特别是一些填充.目前,标签是紫色和粗体的.我希望最后的外观是一个紫色背景的白色标签.当我try 将填充和背景修饰符应用于文本元素时,我得到以下错误.
无法将类型为‘Some View’的返回表达式转换为返回类型‘Text’
我将提供完整的代码,以使这个问题可重现.但请注意,相关代码在LinkColoredText以内.这是第一个 struct ,因此您不必滚动所有内容.
问题在于,Body中的这些视图是Components数组中的元素,添加填充或背景 colored颜色 将导致类型不匹配.
import SwiftUI
private let linkDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
struct LinkColoredText: View {
@Environment(\.colorScheme) var colorScheme
enum Component {
case text(String)
case link(String, URL)
case hashtag(String, String)
}
let text: String
let components: [Component]
init(text: String, links: [NSTextCheckingResult], hashtags: [String]) {
self.text = text
let nsText = text as NSString
var components: [Component] = []
var index = 0
for result in links {
if result.range.location > index {
let sub_str = nsText.substring(with: NSRange(location: index, length: result.range.location - index))
if findHashtags(in: sub_str).isEmpty {
components.append(.text(nsText.substring(with: NSRange(location: index, length: result.range.location - index))))
} else {
let words = extractWordsAndSpaces(from: sub_str)
var offset = 0
for word in words {
if !findHashtags(in: String(word)).isEmpty && containsWhiteSpace(at: offset - 1, in: sub_str) {
components.append(.hashtag(nsText.substring(with: NSRange(location: index + offset, length: word.count)), String(word)))
} else {
components.append(.text(nsText.substring(with: NSRange(location: index + offset, length: word.count))))
}
offset += word.count
}
}
}
components.append(.link(nsText.substring(with: result.range), result.url!))
index = result.range.location + result.range.length
}
if index < nsText.length {
let sub_str = nsText.substring(from: index)
var offset = 0
if findHashtags(in: sub_str).isEmpty {
components.append(.text(sub_str))
} else {
let words = extractWordsAndSpaces(from: sub_str)
for word in words {
if !findHashtags(in: String(word)).isEmpty && containsWhiteSpace(at: offset - 1, in: sub_str){
components.append(.hashtag(nsText.substring(with: NSRange(location: index + offset, length: word.count)), String(word)))
} else {
components.append(.text(nsText.substring(with: NSRange(location: index + offset, length: word.count))))
}
offset += word.count
}
}
}
self.components = components
}
var body: some View {
components.map { component in
switch component {
case .text(let text):
return Text(verbatim: text)
case .link(let text, _):
return Text(verbatim: text)
.foregroundColor(.blue)
case .hashtag(let text, _):
return Text(verbatim: text)
.foregroundColor(.indigo).bold()
}
}.reduce(Text(""), +)
}
}
struct LinkedText: View {
let text: String
let istip: Bool
let isMessage: Bool?
let links: [NSTextCheckingResult]
let hashtags: [String]
init (_ text: String, tip: Bool, isMess: Bool?) {
self.text = text
self.istip = tip
self.isMessage = isMess
let nsText = text as NSString
let wholeString = NSRange(location: 0, length: nsText.length)
links = linkDetector.matches(in: text, options: [], range: wholeString)
hashtags = findHashtags(in: text)
}
var body: some View {
LinkColoredText(text: text, links: links, hashtags: hashtags)
.overlay(LinkTapOverlay(text: text, isTip: istip, isMessage: isMessage, links: links, hashtags: hashtags))
}
}
private struct LinkTapOverlay: UIViewRepresentable {
let text: String
let isTip: Bool
let isMessage: Bool?
let links: [NSTextCheckingResult]
let hashtags: [String]
func makeUIView(context: Context) -> LinkTapOverlayView {
let view = LinkTapOverlayView(frame: .zero, text: text, overlay: self)
view.textContainer = context.coordinator.textContainer
view.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.didTapLabel(_:)))
tapGesture.delegate = context.coordinator
view.addGestureRecognizer(tapGesture)
let longPressGesture = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.didLongPressLabel(_:)))
longPressGesture.delegate = context.coordinator
view.addGestureRecognizer(longPressGesture)
return view
}
func updateUIView(_ uiView: LinkTapOverlayView, context: Context) {
let attributedString = NSAttributedString(string: text, attributes: [.font: UIFont.preferredFont(forTextStyle: .body)])
context.coordinator.textStorage = NSTextStorage(attributedString: attributedString)
context.coordinator.textStorage!.addLayoutManager(context.coordinator.layoutManager)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIGestureRecognizerDelegate {
let overlay: LinkTapOverlay
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
var textStorage: NSTextStorage?
init(_ overlay: LinkTapOverlay) {
self.overlay = overlay
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = .byWordWrapping
textContainer.maximumNumberOfLines = 0
layoutManager.addTextContainer(textContainer)
}
@objc func didTapLabel(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: gesture.view!)
if let result = link(at: location) {
guard let url = result.url else {
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else if let result = hashtag(at: location) {
}
}
@objc func didLongPressLabel(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
}
}
private func link(at point: CGPoint) -> NSTextCheckingResult? {
guard !overlay.links.isEmpty else {
return nil
}
let indexOfCharacter = layoutManager.characterIndex(
for: point,
in: textContainer,
fractionOfDistanceBetweenInsertionPoints: nil
)
for i in 0..<overlay.links.count {
if indexOfCharacter >= overlay.links[i].range.location && indexOfCharacter <= (overlay.links[i].range.location + overlay.links[i].range.length) {
return overlay.links[i]
}
}
return nil
}
private func hashtag(at point: CGPoint) -> String? {
guard !overlay.hashtags.isEmpty else {
return nil
}
let indexOfCharacter = layoutManager.characterIndex(
for: point,
in: textContainer,
fractionOfDistanceBetweenInsertionPoints: nil
)
let words = extractWordsAndSpaces(from: overlay.text)
var offset = 0
for i in 0..<words.count {
if indexOfCharacter >= offset && indexOfCharacter <= (offset + words[i].count){
if !findHashtags(in: words[i]).isEmpty {
if i == 0 {
return words[i]
} else if words[i - 1].hasSuffix(" "){
return words[i]
}
}
return nil
}
offset += words[i].count
}
return nil
}
}
}
private class LinkTapOverlayView: UIView {
private var overlay: LinkTapOverlay?
var textContainer: NSTextContainer!
var text: String?
override init(frame: CGRect) {
super.init(frame: frame)
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPressLabel(_:)))
addGestureRecognizer(longPressGesture)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
convenience init(frame: CGRect, text: String, overlay: LinkTapOverlay) {
self.init(frame: frame)
self.text = text
self.overlay = overlay
}
override func layoutSubviews() {
super.layoutSubviews()
var newSize = bounds.size
newSize.height += 20
textContainer.size = newSize
}
@objc func didLongPressLabel(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
}
}
}
func findHashtags(in text: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: "#\\w+", options: [])
let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
return matches.map {
String(text[Range($0.range, in: text)!])
}
} catch {
return []
}
}
func extractWordsAndSpaces(from sentence: String) -> [String] {
let pattern = try! NSRegularExpression(pattern: "\\S+|\\s+", options: [])
let matches = pattern.matches(in: sentence, options: [], range: NSRange(location: 0, length: sentence.utf16.count))
let resultArray = matches.map { (match) -> String in
let range = Range(match.range, in: sentence)!
return String(sentence[range])
}
return resultArray
}
func containsWhiteSpace(at index: Int, in text: String) -> Bool {
if index == -1 {
return true
}
guard index >= 0 && index < text.count else {
return false
}
let charAtIndex = text[text.index(text.startIndex, offsetBy: index)]
return charAtIndex.isWhitespace
}