ios - 在 SwiftUI 中创建带有可点击文本的大段落

标签 ios swiftui

我收到了从云数据库中获取文本并将其转换为 TextView 的任务

规则如下:
每个以 $ 开头并以 $ 结尾的单词都需要加粗:

let str = "$random$"
let extractStr = "random"
Text(extractStr).bold()

每个以〜开头并以〜结尾的单词都必须是可点击的
let str = "~random~"
let extractStr = "random"
Text(extractStr).onTapGesture { print("tapping")}

每个以 % 开头并以 % 结尾的单词都需要是红色的
let str = "%random%"
let extractStr = "random"
Text(extractStr).foregroundColor(Color.red)

等等。不同种类的规则。

总体目标是将所有文本合并为一个段落。

在没有任何手势的情况下对 Texts View 求和时是可能的,但是当我尝试用点击手势对 Textview 求和时,一切都崩溃了

例如,我创建了一个长文本
let text1 = "$Hello$ my dear fellows. I want to provide #you# this
             %awesome% long Text. I shall $talk$ a bit more to show
             @you@ that when the text is $very very long$ , $it
             doesn’t behave as I wanted to$. \n\n Lets #investigate a
             bit more# and see how this text can behave. \n\n ~you can
             click me~ and ~you can click me also~ and if you need
             to, you have ~another clickable text~"

我想要的结果是这样的:

enter image description here

所以这就是我尝试过的

我将其转换为数组:
let array1 = [$Hello$, my, dear, fellows., I, want, to, provide,
              #you#, this, %awesome, long, text. ......]

之后,我做了一些检查并创建了一个文本数组:
var arrayOfText: [Text] = []

我做了一些逻辑,最终得到了这个:
arrayOfText = [Text("Hello").bold(), Text("my"),
       Text("dear").underline(),
       Text("fellows."), Text("I"), Text("want"), Text("to")
       Text("provide"), Text("you").foregroundColor(.blue),
       Text("this"), Text("awesome").foregroundColor(.red),
       Text("long"), Text("text"), ... ... ...
       Text("another clickable text").onTapGesture{print("tap")}] // <-- Text with tap gesture, this is not valid...

现在我可以遍历文本并将它们全部汇总:
var newText: Text = Text("")
for text in arrayOfText {
    newText = newText + text
}

但它失败了......我无法将带有点击手势的文本分配到文本数组中

我收到了这个错误:
Could not cast value of type 'SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI.AddGestureModifier<SwiftUI._EndedGesture<SwiftUI.TapGesture>>>' (0x7fc8c08e9758) to 'SwiftUI.Text' (0x7fff8166cd30).

所以我尝试做一些解决方法并将数组设置为 AnyView 数组
var arrayOfText: [AnyView] = []

但是当我将 Text 转换为 AnyView

我收到此错误:
Cast from 'AnyView' to unrelated type 'Text' always fails

有什么想法我怎么能做到这一点?

更新

我尝试使用 Asperi 方法:
struct StringPresenter: View {
   let text1 = "hello $world$ I am %a% &new&\n\n~robot~"
   var body: some View {
       ForEach(text1.split(separator: "\n"), id: \.self) { line in
           HStack(spacing: 4) {
               ForEach(line.split(separator: " "), id: \.self) { part in
                  self.generateBlock(for: part)
               }
           }
       }
   }

   private func generateBlock(for str: Substring) -> some View {
       Group {
           if str.starts(with: "$") {
               Text(str.dropFirst().dropLast(1))
                   .bold()
           } else
           if str.starts(with: "&") {
               Text(str.dropFirst().dropLast(1))
                   .font(Font.body.smallCaps())
           } else
           if str.starts(with: "%") {
               Text(str.dropFirst().dropLast(1))
                   .italic().foregroundColor(.red)
           } else
           if str.starts(with: "~") {
               Text(str.dropFirst().dropLast(1))
                   .underline().foregroundColor(.blue).onTapGesture { print("tapping ")}
           }
           else {
               Text(str)
           }
       }
   }

}

但它不适用于长文本。整个观点相互崩溃

这是它的样子:

enter image description here

最佳答案

您正在插入此版本的 SwiftUI 超越其当前功能!
使用 UIKit 中的高级文本处理,或者通过跳出框框思考并将文本转换为 HTML 之类的东西,这样的事情会更容易完成。
如果您必须使用 SwiftUI,您最好的选择可能是首先将格式化文本布局到可点击的段落/ block 上,然后在 block 级别使用手势识别来检测 block 中发生点击的位置 - 间接确定是否点击位置与“可点击”文本一致。
更新#1:
示例:要使用 UITextView(支持属性文本),您可以使用 UIViewRepresentable 协议(protocol)来包装 UIKit View 并使其可从 SwiftUI 中访问。例如对代码使用 Paul Hudson 的 UIViewRepresentable 示例...

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UITextView {
        return UITextView()
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }
}
然后可以直接在 SwiftUI 中使用 TextView。
现在,虽然 Textview 为您提供格式设置,但它不会为您提供所需的可点击性,而无需大量额外工作,但用于呈现 HTML 版本文本的 WKWebView 将允许您将可点击文本转换为 HTML 链接,这些链接可能是在您的新 SwiftUI View 内部处理。
注意:我说您将 SwiftUI 推向极限的原因是,当前版本的 SwiftUI 隐藏了 UIKit 中公开的许多可配置性,并迫使您进行侧手翻来获得 UIKit 中通常已经存在的解决方案.
更新#2:
这是一个使用 UITextField 和 NSAttributedString 的可点击版本:
class MyTextView: UITextView, UITextViewDelegate {
    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        print(URL)
    
        return false
    }
}

struct SwiftUIView: UIViewRepresentable {
    @Binding var text: NSAttributedString

    func makeUIView(context: Context) -> MyTextView {
        let view = MyTextView()
        
        view.dataDetectorTypes = .link
        view.isEditable        = false
        view.isSelectable      = true
        view.delegate          = view
        
        return view
    }

    func updateUIView(_ uiView: MyTextView, context: Context) {       
        uiView.attributedText = text
    }
}
您现在需要做的就是将下载的文本转换为合适的属性字符串格式,并且您已属性格式和可点击性

关于ios - 在 SwiftUI 中创建带有可点击文本的大段落,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62036563/

相关文章:

ios - Xcode 7 - 无法调用 AudioFileStreamOpen

ios - 我想用一个字符串来标识数组,这个字符串是我之前使用 Segue 从 View 中获得的

javascript - 如何制作目标宽度和高度大于 640px 的高质量 PhoneGap 相机图像?

ios - .newPassword for SecureField 选择强密码在使用 SwiftUI 和 iOS14 时不起作用

swiftui - ScrollView 未更新

ios - UIAlertView 上的按钮

ios - 通过应用程序中的链接使 pdf 文件可访问

ios - LazyVGrid 中的 SwiftUI NavigationLink 选择随机索引

swift - SwiftUI 中文本的自定义字体大小

swiftui - .previewLayout(PreviewLayout.fixed(width :_, height:_)) 在 Xcode 14 中不起作用