ios 在SwiftUI中使用可点击的文本创建一个大段落

qyuhtwio  于 2023-06-25  发布在  iOS
关注(0)|答案(4)|浏览(155)

我接到一个任务,从云数据库中获取文本并将其转换为Text视图
规则如下:每个以$开头和以$结尾的单词都需要加粗:

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视图求和是可能的,但是当我尝试使用点击手势对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~"

我想要的结果是

所以这就是我所尝试的
我把它转换成一个数组:

let array1 = [$Hello$, my, dear, fellows., I, want, to, provide,
              #you#, this, %awesome, long, text. ......]

在那之后,我做了一些检查并创建了一个Texts数组:

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)
           }
       }
   }

}
但它不适用于长文本。所有的景色都互相倾轧
这是它看起来像:

hof1towb

hof1towb1#

从iOS 15开始,您可以将AttributedStringMarkdownText一起使用。这样你就得到了多行格式的文本。
使用markdown的示例:

Text("This text is **bold**, this is *italic*. This is a tappable link: [tap here](https://stackoverflow.com)")

Markdown相比,AttributedString的优势在于它为您提供了对格式的更多控制。例如,您可以设置链接颜色或下划线颜色:

var string = AttributedString("")

// you can use markdown with AttributedString
let markdownText = try! AttributedString(markdown: "This is **bold** text. This is *italic* text.")

var tappableText = AttributedString(" I am tappable! ")
tappableText.link = URL(string: "https://stackoverflow.com")
tappableText.foregroundColor = .green
    
var underlinedText = AttributedString("This is underlined text.")
underlinedText.underlineStyle = Text.LineStyle(pattern: .solid, color: .red)
    
string.append(markdownText)
string.append(tappableText)
string.append(underlinedText)

Text(string)

它看起来是这样的:

附带说明:如果您希望可点击文本具有与在浏览器中打开URL不同的行为,则可以define a custom URL scheme for your app。然后,您将能够使用onOpenURL(perform:)处理链接上的tap事件,该onOpenURL(perform:)注册了一个处理程序,以便在视图接收到视图所在的场景或窗口的url时调用。

xlpyo6sf

xlpyo6sf2#

您正在推动此版本的SwiftUI超出其当前功能!
使用UIKit中的高级文本处理,或者跳出框框,将文本转换为类似HTML的东西,这样的事情会更容易完成。
如果你必须使用SwiftUI,你最好的办法可能是首先将格式化的文本布局到可点击的段落/块上,然后在块级别使用手势识别来检测点击发生在块中的位置-间接确定点击位置是否与“可点击”文本一致。
更新#1:
示例:要使用UITextView(支持属性化文本),您可以使用UIViewRepresentable协议 Package UIKit视图,并使其可以从SwiftUI中访问。例如,使用Paul哈德逊的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视图内部处理。
注意:我之所以说你把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
    }
}

所有您需要做的是现在下载的文本转换成一个合适的属性字符串格式,你有属性格式化和点击

mrphzbgm

mrphzbgm3#

Markdown现在支持文本视图。

Text("By clicking continue you agree to our [Terms Of Service](https://example.com)")

我本来打算提供一个GIF,但“服务器上出现了问题”编辑:如果您希望它触发函数,请为您的应用创建自定义URL方案。
此Apple开发人员文档中的Learn More

6kkfgxo0

6kkfgxo04#

这里是一个可能的方法的演示(当然解析和布局是微不足道的-仅用于演示)。使用Xcode 11.4 / iOS 13.4测试

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)
            }
        }
    }
}

相关问题