【问题标题】:SwiftUI Text - how can I create a hyperlink and underline a weblink in a stringSwiftUI Text - 如何在字符串中创建超链接并在 Web 链接下划线
【发布时间】:2022-06-16 20:09:42
【问题描述】:

在 SwiftUI 中,如果我在字符串之间有一个网络链接,我如何创建一个超链接并在我的视图中将其加下划线。

注意,“messageContent”字符串并不总是相同的。

例如

struct MessageModel {
    var messageContent: String = "Test of hyperlink www.google.co.uk within a text message"
}


struct Content: View {
    
    var message: MessageModel
        
    var body: some View {
        
        VStack {
            Text(message.messageContent)
        }
        
    }
}

我想要实现的图像显示在此图像中。 “www.google.co.uk”是一个可点击的超链接,并带有下划线

Example of what I want to achieve



更新我想要达到的目标

我创建了下面的测试代码来展示我想要实现的目标,因为如上所述,“messageContent”并不总是相同的字符串。

虽然以下内容对于处理所有情况和处理错误等并不完美,但希望这能更好地了解我想要实现的目标。唯一的问题是这似乎不起作用。

它会为超链接生成下划线,但文本不会以 Markdown 格式显示 - 请参阅附图。

Result

import SwiftUI

struct HyperlinkAndUnderlineText: View {
    
    var message: MessagesModel = MessagesModel(messageContent: "Test of hyperlink www.google.co.uk within a text message")
    
    @State var messageContentAfterSplitting: [SplitMessage] = []
    
    var body: some View {
            
            CustomText(inputText: messageContentAfterSplitting)
        
        .onAppear() {
            messageContentAfterSplitting = splitMessage(message: message)
        }
    }
}





struct MessagesModel {
    var messageContent: String = ""
}


struct SplitMessage {
    var content: String = ""
    var type: contentType = .text
}

enum contentType {
    case text
    case url
}





func splitMessage(message: MessagesModel) -> [SplitMessage] {
    
    func detectIfMessageContainsUrl(message: String) -> [String]? {
        
        let urlDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
        let matches = urlDetector.matches(in: message, options: [], range: NSRange(location: 0, length: message.utf16.count))
        
        var urls: [String] = []
        
        for (index, match) in matches.enumerated() {
            guard let range = Range(match.range, in: message) else { continue }
            let url = message[range]
            
            urls.append(String(url))
            
            if index == matches.count - 1 {
                return urls
            }
        }
        return []
        
    }
    
    let urlsFoundInMessage = detectIfMessageContainsUrl(message: message.messageContent)
    
    
    
    
    func getComponents(urlsFoundInMessage: [String]) -> [String] {
        
        var componentsEitherSideOfUrl: [String] = []
        
        for (index,url) in urlsFoundInMessage.enumerated() {
            componentsEitherSideOfUrl = message.messageContent.components(separatedBy: url)
            
            if index == urlsFoundInMessage.count - 1 {
                return componentsEitherSideOfUrl
            }
        }
        
        return []
    }
    
    let componentsEitherSideOfUrl = getComponents(urlsFoundInMessage: urlsFoundInMessage!)
    
    
    
    
    func markdown(urlsFoundInMessage: [String]) -> [String] {
        
        var markdownUrlsArray: [String] = []
        
        for (index, url) in urlsFoundInMessage.enumerated() {
            
            let placeholderText = "[\(url)]"
            
            var url2: String
            if url.hasPrefix("https://www.") {
                url2 = "(\(url.replacingOccurrences(of: "https://www.", with: "https://")))"
            } else if url.hasPrefix("www.") {
                url2 = "(\(url.replacingOccurrences(of: "www.", with: "https://")))"
            } else {
                url2 = "(\(url))"
            }
            
            let markdownUrl = placeholderText + url2
            
            markdownUrlsArray.append(markdownUrl)
            
            if index == urlsFoundInMessage.count - 1 {
                return markdownUrlsArray
            }
        }
        
        return []
        
    }
    
    let markdownUrls = markdown(urlsFoundInMessage: urlsFoundInMessage!)
    
    
    
    
    func recombineStrings(componentsEitherSideOfUrl: [String], markdownUrls: [String]) -> [SplitMessage] {
        
        var text = SplitMessage()
        var textAsArray: [SplitMessage] = []
        
        
        for i in 0...2 {
            if i.isMultiple(of: 2) {
                if i == 0 {
                    text.content = componentsEitherSideOfUrl[i]
                    text.type = .text
                    textAsArray.append(text)
                } else {
                    text.content = componentsEitherSideOfUrl[i-1]
                    text.type = .text
                    textAsArray.append(text)
                }
            } else {
                text.content = markdownUrls[i-1]
                text.type = .url
                textAsArray.append(text)
            }
        }
        
        return textAsArray
    }
    
    
    let recombinedStringArray = recombineStrings(componentsEitherSideOfUrl: componentsEitherSideOfUrl, markdownUrls: markdownUrls)
    
    return recombinedStringArray
    
}




func CustomText(inputText: [SplitMessage]) -> Text {
    
    var output = Text("")
    
    for input in inputText {
                
        let text: Text
        
        text = Text(input.content)
            .underline(input.type == .url ? true : false, color: .blue)
        
        output = output + text
        
    }
    
    return output
    
    
}

【问题讨论】:

    标签: swift text swiftui hyperlink underline


    【解决方案1】:

    在 Swift 5.5 (iOS 15+) 中

    你可以使用markdown:

     Text("This is a link [Google](https://google.com.com)")
    

    要仅为链接添加下划线,您可以这样做:

      Text("This is a link ") + Text("[google.com](https://google.com.com)").underline()
    

    【讨论】:

    • 感谢@Guillermo Jiménez。这在一定程度上有所帮助 - 请参阅上面对我的问题的更新。如前所述,messageContent 不是固定的,它会发生变化,所以我需要一些更动态的东西。
    • 很高兴我可以帮助解决您的最后一个问题,只需像这样初始化字符串 Text(.init(input.content))
    • 感谢您的小而有力的意见!已将代码发布到我所追求的解决方案中。
    【解决方案2】:

    这是我使用的最终解决方案。应该适用于各种字符串输入。

    import SwiftUI
    
    
    struct HyperlinkAndUnderlineTextView: View {
        
        var body: some View {
            
            ScrollView {
                
                VStack (alignment: .leading, spacing: 30) {
                    
                    Group {
                        CustomTextWithHyperlinkAndUnderline("Test of a hyperlink www.google.co.uk within a text message", .blue)
                        CustomTextWithHyperlinkAndUnderline("www.google.co.uk hyperlink at the start of a text message", .blue)
                        CustomTextWithHyperlinkAndUnderline("Test of hyperlink at the end of a text message www.google.co.uk", .blue)
                        CustomTextWithHyperlinkAndUnderline("www.google.co.uk", .blue)
                        CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink www.apple.com", .blue)
                        CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink www.apple.com.  This is text after it.", .blue)
                        CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink www.apple.com.  This is a 3rd hyperlink www.microsoft.com",  .blue)
                        CustomTextWithHyperlinkAndUnderline("This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink www.apple.com.  This is a 3rd hyperlink www.microsoft.com.  This is text after it.", .blue)
                        CustomTextWithHyperlinkAndUnderline("www.google.co.uk is a hyperlink at the start of a text message.  www.apple.com is the 2nd hyperlink within the same text message.", .blue)
                        CustomTextWithHyperlinkAndUnderline("This is a test of another type of url which will get processed google.co.uk", .blue)
                    }
                    
                    Group {
                        CustomTextWithHyperlinkAndUnderline("google.co.uk", .blue)
                        CustomTextWithHyperlinkAndUnderline("Pure text with no hyperlink", .blue)
                        CustomTextWithHyperlinkAndUnderline("Emoji test ?", .blue)
                    }
                }
            }
        }
    }
    
    
    
    struct SplitMessageContentWithType {
        var content: String = ""
        var type: contentType = .text
    }
    
    enum contentType {
        case text
        case url
    }
    
    
    
    //Function to produce a text view where all urls and clickable and underlined
    func CustomTextWithHyperlinkAndUnderline(_ inputString: String, _ underlineColor: Color) -> Text {
        
        let inputText: [SplitMessageContentWithType] = splitMessage(inputString)
        
    
        var output = Text("")
        
        for input in inputText {
            let text: Text
            
            text = Text(.init(input.content))
                .underline(input.type == .url ? true : false, color: underlineColor)
            
            output = output + text
        }
        
        return output
        
    }
    
    
    
    func splitMessage(_ inputString: String) -> [SplitMessageContentWithType] {
        
        
        //1) Function to detect if the input string contains any urls and returns the ones found as an array of strings
        func detectIfInputStringContainsUrl(inputString: String) -> [String] {
            
            let urlDetector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
            let matches = urlDetector.matches(in: inputString, options: [], range: NSRange(location: 0, length: inputString.utf16.count))
            
            var urls: [String] = []
            
            for match in matches {
                guard let range = Range(match.range, in: inputString) else { continue }
                let url = inputString[range]
                
                urls.append(String(url))
            }
            return urls
            
        }
        let urlsFoundInInputString = detectIfInputStringContainsUrl(inputString: inputString)
        print("\n \nurlsFoundInInputString are: \(urlsFoundInInputString)")
        
        
        
        //2) Function to get the string components either side of a url from the inputString.  Returns these components as an array of strings
        func getStringComponentsSurroundingUrls(urlsFoundInInputString: [String]) -> [String] {
            
            var stringComponentsSurroundingUrls: [String] = []
            
            for (index, url) in urlsFoundInInputString.enumerated() {
                
                let splitInputString = inputString.components(separatedBy: url)
                
                //This code handles the case of an input string with 2 hyperlinks inside it (e.g. This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink www.apple.com.  This is text after it.)
                //In the 1st pass of the for loop, this will return splitInputString = ["This is 1 hyperlink ", ".  This is a 2nd hyperlink www.apple.com.  This is text after it."]
                //Because the last element in the array contains either "www" or "http", we only append the contents of the first (prefix(1)) to stringComponentsSurroundingUrls (i.e "This is 1 hyperlink ")
                //In the 2nd pass of the for loop, this will return splitInputString = ["This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink ", ".  This is text after it."]
                //Beacuse the last element in the array does not contain a hyperlink, we append both elements to stringComponentsSurroundingUrls
                if splitInputString.last!.contains("www") || splitInputString.last!.contains("http") {
                    stringComponentsSurroundingUrls.append(contentsOf: inputString.components(separatedBy: url).prefix(1))
                } else {
                    stringComponentsSurroundingUrls.append(contentsOf: inputString.components(separatedBy: url))
                }
                
                
                //At this point in the code, in the above example, stringComponentsSurroundingUrls = ["This is 1 hyperlink ",
                //                                                                                    "This is 1 hyperlink www.google.co.uk.  This is a 2nd hyperlink ",
                //                                                                                    ".  This is text after it."]
                //We now iterate through this array of string, to complete another split.  This time we separate out by any elements by urlsFoundInInputString[index-1]
                //At the end of this for loop, stringComponentsSurroundingUrls = ["This is 1 hyperlink ",
                //                                                                ".  This is a 2nd hyperlink ",
                //                                                                ".  This is text after it."]
                if index == urlsFoundInInputString.count - 1 {
                    for (index, stringComponent) in stringComponentsSurroundingUrls.enumerated() {
                        if index != 0 {
                            let stringComponentFurtherSeparated = stringComponent.components(separatedBy: urlsFoundInInputString[index-1])
                            stringComponentsSurroundingUrls.remove(at: index)
                            stringComponentsSurroundingUrls.insert(stringComponentFurtherSeparated.last!, at: index)
                        }
                    }
                }
            }
            
            return stringComponentsSurroundingUrls
        }
        
        var stringComponentsSurroundingUrls: [String]
        //If there no no urls found in the inputString, simply set stringComponentsSurroundingUrls equal to the input string as an array, else call the function to find the string comoponents surrounding the Urls found
        if urlsFoundInInputString == [] {
            stringComponentsSurroundingUrls = [inputString]
        } else {
            stringComponentsSurroundingUrls = getStringComponentsSurroundingUrls(urlsFoundInInputString: urlsFoundInInputString)
        }
        print("\n \nstringComponentsSurroundingUrls are: \(stringComponentsSurroundingUrls)")
        
        
        
        
        //3)Function to markdown the urls found to follow a format of [placeholderText](hyperlink) such as [Google](https://google.com) so SwiftUI markdown can render it as a hyperlink
        func markdown(urlsFoundInInputString: [String]) -> [String] {
            
            var markdownUrlsArray: [String] = []
            
            for url in urlsFoundInInputString {
                let placeholderText = "[\(url)]"
                
                var hyperlink: String
                if url.hasPrefix("https://www.") {
                    hyperlink = "(\(url.replacingOccurrences(of: "https://www.", with: "https://")))"
                } else if url.hasPrefix("www.") {
                    hyperlink = "(\(url.replacingOccurrences(of: "www.", with: "https://")))"
                } else {
                    hyperlink = "(http://\(url))"
                }
                
                let markdownUrl = placeholderText + hyperlink
                
                markdownUrlsArray.append(markdownUrl)
            }
            
            return markdownUrlsArray
            
        }
        
        let markdownUrls = markdown(urlsFoundInInputString: urlsFoundInInputString)
        print("\n \nmarkdownUrls is: \(markdownUrls)")
        
        
        
        //4) Function to combine stringComponentsSurroundingUrls and markdownUrls back together
        func recombineStringComponentsAndMarkdownUrls(stringComponentsSurroundingUrls: [String], markdownUrls: [String]) -> [SplitMessageContentWithType] {
            
            var text = SplitMessageContentWithType()
            var text2 = SplitMessageContentWithType()
            var splitMessageContentWithTypeAsArray: [SplitMessageContentWithType] = []
            
            //Saves each string component and url as either .text or .url type so in the CustomTextWithHyperlinkAndUnderline() function, we can underline all .url types
            for (index, stringComponents) in stringComponentsSurroundingUrls.enumerated() {
                text.content = stringComponents
                text.type = .text
                splitMessageContentWithTypeAsArray.append(text)
                
                if index <= (markdownUrls.count - 1) {
                    text2.content = markdownUrls[index]
                    text2.type = .url
                    splitMessageContentWithTypeAsArray.append(text2)
                }
            }
            
            return splitMessageContentWithTypeAsArray
        }
        
        
        let recombineStringComponentsAndMarkdownUrls = recombineStringComponentsAndMarkdownUrls(stringComponentsSurroundingUrls: stringComponentsSurroundingUrls, markdownUrls: markdownUrls)
        print("\n \nrecombineStringComponentsAndMarkdownUrls is: \(recombineStringComponentsAndMarkdownUrls)")
        
        return recombineStringComponentsAndMarkdownUrls
        
    }
    

    【讨论】:

      【解决方案3】:

      我为这个问题做了非常简单的解决方案。 你可以用这个referenceenter link description here创建任何View,你需要提供你想要显示的文本,文本可以包含任何URL,电子邮件地址。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-07
        • 2013-02-12
        • 1970-01-01
        • 2012-03-06
        • 2014-06-04
        • 2016-06-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多