I am trying to extract a href from a text and let it clickable to the website.
This is how my try looks like but it does not work correctly:
extension String {
func findHref() -> [String] {
var arr_hasStrings:[String] = []
let regex = try? NSRegularExpression(pattern: "https[a-zA-Z0-9_\\p{Arabic}\\p{N}]*\\.com)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
for match in matches {
arr_hasStrings.append(NSString(string: self).substring(with: NSRange(location:match.range.location, length: match.range.length )))
}
}
return arr_hasStrings
}
}
and then I used it like this:
struct Details: View {
var body: some View {
VStack {
ForEach( typedcomment.findHashtags(), id: \.self ){
element in
HStack{
Text(element).background(Color("bg"))
}
}
}
}
}
It worked for https://stackoverflow.com
but not for a link like this: https://www.apple.com/de/macbook-pro-13/
try this:
let input = "This is a test with the URL https://www.apple.com/de/macbook-pro-13/ to be detected."
func returnUrl(text: String) -> URL? {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
var url: URL?
for match in matches {
guard let range = Range(match.range, in: input) else { continue }
let urlFound = input[range]
url = URL(string: String(urlFound))
}
return url
}
from https://www.hackingwithswift.com/example-code/strings/how-to-detect-a-url-in-a-string-using-nsdatadetector
How do I extract hashtag strings from a text in Swift? I've seen some answers but they seem too complicated for what I need and I don't really understand how RegEx works?
E.g.
Text: "This is #something with a lot of #random #hashtags #123yay."
What I want: "something", "random", "hashtags", "123yay".
Thanks!
here is the helper method to convert your string into hash detection string
this extension find the # words from sting also including arabic words.
extension String {
func findMentionText() -> [String] {
var arr_hasStrings:[String] = []
let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
if let matches = regex?.matches(in: self, options:[], range:NSMakeRange(0, self.count)) {
for match in matches {
arr_hasStrings.append(NSString(string: self).substring(with: NSRange(location:match.range.location, length: match.range.length )))
}
}
return arr_hasStrings
}
}
And below method converts your string into Reach colorful hash string.
func convert(_ hashElements:[String], string: String) -> NSAttributedString {
let hasAttribute = [NSAttributedStringKey.foregroundColor: UIColor.orange]
let normalAttribute = [NSAttributedStringKey.foregroundColor: UIColor.black]
let mainAttributedString = NSMutableAttributedString(string: string, attributes: normalAttribute)
let txtViewReviewText = string as NSString
hashElements.forEach { if string.contains($0) {
mainAttributedString.addAttributes(hasAttribute, range: txtViewReviewText.range(of: $0))
}
}
return mainAttributedString
}
i.e
let text = "#Jaydeep #Viral you have to come for party"
let hashString = convert(text.findMentionText(), string: text)
Output:
extension String
{
func hashtags() -> [String]
{
if let regex = try? NSRegularExpression(pattern: "#[a-z0-9]+", options: .caseInsensitive)
{
let string = self as NSString
return regex.matches(in: self, options: [], range: NSRange(location: 0, length: string.length)).map {
string.substring(with: $0.range).replacingOccurrences(of: "#", with: "").lowercased()
}
}
return []
}
}
then, to get the hashtags array
yourstring.hashtags()
Here is the source
let str = "This is #something with a lot of #random #hashtags #123yay."
let words = str.components(separatedBy: " ")
var hashTags = [String]()
for word in words{
if word.hasPrefix("#"){
let hashtag = word.dropFirst()
hashTags.append(String(hashtag))
}
}
print("Hashtags :: ", hashTags)
First things first, this works best in a TextView. So set one up inside of your view however you want, but make sure that your ViewController has a UITextViewDelegate & the textView is delegated to that view controller.
I’m also doing this with some prefilled information, but the same concept applies with pulling data from your database and what not.
This is how we set up our ViewController:
class ViewController: UIViewController, UITextViewDelegate {
var string = "Hello, my name is #Jared & #Jared and I like to move it."
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.text = string
textView.delegate = self
}
The overall task we’re trying to accomplish in this part is just to split up all the words in our textView. It’s simpler than you might think:
First, let’s create our extension:
Now add this to your ViewController:
extension UITextView {
func resolveTags(){
let nsText:NSString = self.text as NSString
let words:[String] = nsText.components(separatedBy: " ")
let attrs = [
NSAttributedStringKey.font : UIFont.init(name: "HelveticaNeue", size: 13),
NSAttributedStringKey.foregroundColor : UIColor.black
]
let attrString = NSMutableAttributedString(string: nsText as String, attributes:attrs)
for word in words {
if word.hasPrefix("#") {
let matchRange:NSRange = nsText.range(of: word as String)
var stringifiedWord:String = word as String
stringifiedWord = String(stringifiedWord.dropFirst())
attrString.addAttribute(NSAttributedStringKey.link, value: "hash:\(stringifiedWord)", range: matchRange)
attrString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue , range: matchRange)
}
}
self.attributedText = attrString
}
}
Let’s use this thing!
It all comes down to this. We have this function working, now how do we use it?
Easy.
Inside of your viewDidLoad function, or wherever you set your textView text, just call:
textView.resolveTags()
Result:
Courtesy of: Jared Davidson On Twitter
You can also use third party Activelabel . this is simple to use and also support Hashtags (#), Mentions (#), URLs (http://) and custom regex patterns
https://github.com/optonaut/ActiveLabel.swift
I just changed #JayDeep 's answer to more swifty style.
extension String {
var tags: [String] {
let regex = try? NSRegularExpression(pattern: "(#[a-zA-Z0-9_\\p{Arabic}\\p{N}]*)", options: [])
let nsRange: NSRange = .init(location: 0, length: self.count)
guard let matches = regex?.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: nsRange)
else { return [] }
return matches
.map { match in
let startIndex = self.index(self.startIndex, offsetBy: match.range.location)
let endIndex = self.index(startIndex, offsetBy: match.range.length)
let range = startIndex ..< endIndex
return String(self[range])
}
}
}
My clean solution: We will return PrefixesDetected to the view. And the view will format it as he wants. (So we will execute yourString.resolvePrefixes()) in the viewModel and we will be able to test it.
struct PrefixesDetected {
let text: String
let prefix: String?
}
extension String {
func resolvePrefixes(_ prefixes: [String] = ["#", "#"]) -> [PrefixesDetected] {
let words = self.components(separatedBy: " ")
return words.map { word -> PrefixesDetected in
PrefixesDetected(text: word,
prefix: word.hasPrefix(prefixes: prefixes))
}
}
func hasPrefix(prefixes: [String]) -> String? {
for prefix in prefixes {
if hasPrefix(prefix) {
return prefix
}
}
return nil
}
}
Then in the view we can format it as for example: (In this case we want both in the same color but in this way you can give them different behaviors)
Here I do with reduce but this is just to show an example, you can format it as you want! :)
titleDetectedPrefixes.reduce(NSAttributedString(), { result, prefixDectedWord in
let wordColor: UIColor = prefixDectedWord.prefix != nil ? .highlightTextMain : .mainText
let attributedWord = NSAttributedString(string: prefixDectedWord.text)
{ Add desired attributes }
})
for those who are using swiftUI you can achieve it by using the "+" operator
so the final solution will look like this
static func tagHighlighter(description : String , previousText : Text = Text("") , tag : String = "#") -> Text {
var t : Text = Text("")
let words : [String] = description.components(separatedBy: " ")
for word in words {
if !word.isEmpty {
let tag = word[word.startIndex]
if tag == "#" {
t = t + Text("\(word) ").foregroundColor(Color("tag_color"))
} else if tag == "#" {
t = t + Text("\(word) ").foregroundColor(Color("tag_color"))
} else {
t = t + Text("\(word) ")
}
}
}
return t
}
This is how I'm doing it
private func getHashTags(from caption: String) -> [String] {
var words: [String] = []
let texts = caption.components(separatedBy: " ")
for text in texts.filter({ $0.hasPrefix("#") }) {
if text.count > 1 {
let subString = String(text.suffix(text.count - 1))
words.append(subString.lowercased())
}
}
return words
}
Copy paste this extension to your class:
extension UITextView{
func insertTextWithHashtags(text textString: String){
let nsTextString: NSString = textString as NSString
let simpleTextAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor : UIColor(named: "Black Color")!, NSAttributedString.Key.font : UIFont(name: "Inter-Regular", size: 16.0)!]
let attributedString = NSMutableAttributedString(string: textString, attributes: simpleTextAttributes)
var word = ""
for text in textString+" "{ //+" " is for loop to run one extra time to complete hashtag
if text == "#" || text == "\n" || text == " "{
if word.hasPrefix("#"){
let range = nsTextString.range(of: word)
let link = [NSAttributedString.Key.link : word]
attributedString.addAttributes(link, range: range)
if text == "#"{
word = "#"
}else{
word = ""
}
}else{
if text == "#"{
word = "#"
}
}
}else{
if word.hasPrefix("#"){
word.append(text)
}
}
}
//For for applying attributes to hashtag
let linkAttributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor : UIColor(named: "Primary Color")!]
self.linkTextAttributes = linkAttributes
self.attributedText = attributedString
}
}
and then call it like this:
postTextView.insertTextWithHashtags(text: "#Hello#Hey #Space")
From the server side, sometimes I am getting simple string, sometimes string that contains URL and sometimes URL only. How to check if that string contains URL only, no other text.
Add below two functions into to your ViewContoller
func getUrlStringFromString(text: String) - > String {
var tempStrArray = text.components(separatedBy: " ")
var urlString = ""
for i in 0 ..< tempStrArray.count {
if isValidUrl(str: "\(tempStrArray[i])") {
urlString = tempStrArray[i]
}
}
return urlString
}
func isValidUrl(str: String) -> Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
if let match = detector.firstMatch(in: str, options: [], range: NSRange(location: 0, length: str.endIndex.encodedOffset)) {
// it is a link, if the match covers the whole string
return match.range.length == str.endIndex.encodedOffset
} else {
return false
}
}
Then call getUrlStringFromString with your string
let urlString = self.getStringFromSting(text: YOUR_STRING)
if urlString != "" {
//YOUR_STRING have url. and urlString contains URL
} else {
//YOUR_STRING doesn't have url.
}
Hope this will help you
I'm accessing the HTML of a webpage after the user has submitted information.
If I have...
var htmlString = "This is the massive HTML string - it has lots of characters "FindStringDetails"<123789456.123456> This is a massive HTML string - "
var findString = "FindStringDetails\"<"
Is there a suitable way that I could extract the numbers that come after "FindStringDetails"< to give me 123789456.123456 ?
import Foundation
var htmlString = "This is the massive HTML string - it has lots of characters \"FindStringDetails\"<123789456.123456> This is a massive HTML string - "
var findString = "FindStringDetails\"<"
extension String {
func matchingStrings(regex: String) -> [[String]] {
guard let regex = try? NSRegularExpression(pattern: regex, options: []) else { return [] }
let nsString = NSString(string: self)
let results = regex.matches(in: self, options: [], range: NSMakeRange(0, nsString.length))
return results.map { result in
(0..<result.numberOfRanges).map { result.range(at: $0).location != NSNotFound
? nsString.substring(with: result.range(at: $0))
: ""
}
}
}
}
let m = htmlString.matchingStrings(regex: "\(findString)(\\d+)\\.(\\d+)")
print(m[0][1]) // 123789456
print(m[0][2]) // 123456
(Code adapted from here.)
I use a code from Swift extract regex matches
func matchesForRegexInText(regex: String!, text: String!) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matchesInString(text,
options: [], range: NSMakeRange(0, nsString.length))
return results.map { nsString.substringWithRange($0.range)}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
I try match this pattern (sample pattern) "^([a-z]+).*?(\d+)$"
And if I use it for string "abc.....123" I get as result this full string...
But I want to get array of strings ["abc", "123"]
You have to get the subranges of the captured groups – the expressions of the regex within the parentheses respectively – with rangeAtIndex() of the first match.
Since the range of the full string is at index 0, start the loop at index 1.
func matchesForRegexInText(regex: String, text: String) -> [String] {
var result = [String]()
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
if let match = regex.firstMatchInString(text, options: [], range: NSMakeRange(0, nsString.length)) {
for i in 1..<match.numberOfRanges {
result.append(nsString.substringWithRange(match.rangeAtIndex(i)))
}
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
}
return result
}
PS: Not everything in Swift needs a ? or !. The exclamation marks in the parameter strings are meaningless as none of them is actually considered as optional.