Highlight a specific part of the text in SwiftUI - swift

Hello I'm new to Swift and am using SwiftUI for my project where I download some weather data and I display it in the ContentView().
I would like to highlight some part of the Text if it contains some specific word, but I don't have any idea how to start.
In ContentView(), I have tried to set a function receiving the string downloaded from web and return a string. I believe this is wrong, because SwiftUI does not apply the modifiers at the all for the Text.
For example, in my ContentView() I would like the word thunderstorm to have the .bold() modifier:
struct ContentView: View {
let testo : String = "There is a thunderstorm in the area"
var body: some View {
Text(highlight(str: testo))
}
func highlight(str: String) -> String {
let textToSearch = "thunderstorm"
var result = ""
if str.contains(textToSearch) {
let index = str.startIndex
result = String( str[index])
}
return result
}
}

If that requires just simple word styling then here is possible solution.
Tested with Xcode 11.4 / iOS 13.4
struct ContentView: View {
let testo : String = "There is a thunderstorm in the area. Added some testing long text to demo that wrapping works correctly!"
var body: some View {
hilightedText(str: testo, searched: "thunderstorm")
.multilineTextAlignment(.leading)
}
func hilightedText(str: String, searched: String) -> Text {
guard !str.isEmpty && !searched.isEmpty else { return Text(str) }
var result: Text!
let parts = str.components(separatedBy: searched)
for i in parts.indices {
result = (result == nil ? Text(parts[i]) : result + Text(parts[i]))
if i != parts.count - 1 {
result = result + Text(searched).bold()
}
}
return result ?? Text(str)
}
}
Note: below is previously used function, but as commented by #Lkabo it has limitations on very long strings
func hilightedText(str: String) -> Text {
let textToSearch = "thunderstorm"
var result: Text!
for word in str.split(separator: " ") {
var text = Text(word)
if word == textToSearch {
text = text.bold()
}
result = (result == nil ? text : result + Text(" ") + text)
}
return result ?? Text(str)
}

iOS 13, Swift 5. There is a generic solution described in this medium article. Using it you can highlight any text anywhere with the only catch being it cannot be more then 64 characters in length, since it using bitwise masks.
https://medium.com/#marklucking/an-interesting-challenge-with-swiftui-9ebb26e77376
This is the basic code in the article.
ForEach((0 ..< letter.count), id: \.self) { column in
Text(letter[column])
.foregroundColor(colorCode(gate: Int(self.gate), no: column) ? Color.black: Color.red)
.font(Fonts.futuraCondensedMedium(size: fontSize))
}
And this one to mask the text...
func colorCode(gate:Int, no:Int) -> Bool {
let bgr = String(gate, radix:2).pad(with: "0", toLength: 16)
let bcr = String(no, radix:2).pad(with: "0", toLength: 16)
let binaryColumn = 1 << no - 1
let value = UInt64(gate) & UInt64(binaryColumn)
let vr = String(value, radix:2).pad(with: "0", toLength: 16)
print("bg ",bgr," bc ",bcr,vr)
return value > 0 ? true:false
}

You can concatenate with multiple Text Views.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View{
let testo : String = "There is a thunderstorm in the area"
let stringArray = testo.components(separatedBy: " ")
let stringToTextView = stringArray.reduce(Text(""), {
if $1 == "thunderstorm" {
return $0 + Text($1).bold() + Text(" ")
} else {
return $0 + Text($1) + Text(" ")
}
})
return stringToTextView
}
}
PlaygroundPage.current.setLiveView(ContentView())

If you are targeting iOS15 / macOS12 and above, you can use AttributedString. For example:
private struct HighlightedText: View {
let text: String
let highlighted: String
var body: some View {
Text(attributedString)
}
private var attributedString: AttributedString {
var attributedString = AttributedString(text)
if let range = attributedString.range(of: highlighted)) {
attributedString[range].backgroundColor = .yellow
}
return attributedString
}
}
If you want your match to be case insensitive, you could replace the line
if let range = attributedString.range(of: highlighted)
with
if let range = AttributedString(text.lowercased()).range(of: highlighted.lowercased())

The answer of #Asperi works well. Here is a modified variant with a search by array of single words:
func highlightedText(str: String, searched: [String]) -> Text {
guard !str.isEmpty && !searched.isEmpty else { return Text(str) }
var result: Text!
let parts = str.components(separatedBy: " ")
for part_index in parts.indices {
result = (result == nil ? Text("") : result + Text(" "))
if searched.contains(parts[part_index].trimmingCharacters(in: .punctuationCharacters)) {
result = result + Text(parts[part_index])
.bold()
.foregroundColor(.red)
}
else {
result = result + Text(parts[part_index])
}
}
return result ?? Text(str)
}
Usage example:
let str: String = "There is a thunderstorm in the area. Added some testing long text to demo that wrapping works correctly!"
let searched: [String] = ["thunderstorm", "wrapping"]
highlightedText(str: str, searched: searched)
.padding()
.background(.yellow)

You can also make AttributedString with markdown this way
do {
return try AttributedString(markdown: foreignSentence.replacing(word.foreign, with: "**\(word.foreign)**"))
} catch {
return AttributedString(foreignSentence)
}
and just use Text
Text(foreignSentenceMarkdown)

Related

How do I replace a placeholder text with an Image in SwiftUI?

I have a string "Hello {world}" which I need to replace with "Hello 🌍". The placeholder's position is not fixed at the end. And I may have more than a single placeholder.
I am using SwiftUI and tried to make this work with
Text("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))"))
but soon found that this doesn't work and presented with this Hello Image(provider: SwiftUI.ImageProviderBox<SwiftUI.Image.(unknown context at $1ba606db0).NamedImageProvider>)
Since this worked
Text(LocalizedStringKey("Hello \(Image(systemName: "globe"))"))
I assumed I needed to pass a LocalizedStringKey into the Text I tried again with
Text(LocalizedStringKey("Hello {world}".replacingOccurrences(of: "{world}", with: "\(Image(systemName: "globe"))")))
Text(LocalizedStringKey("Hello" + "\(Image(systemName: "globe"))")) //this doesn't work either
but presented with a similar issue SwiftUI.Text.Storage.anyTextStorage(SwiftUI.(unknown context at $1ba668448).LocalizedTextStorage
I looked at the API for LocalizedStringKey and LocalizedStringKey.StringInterpolation but could not find a solution this problem. Is there a way to make replacement of placeholder string work?
After looking at #bewithyou's answer, I got the idea that I need to split this into multiple substrings and recombine the texts individually. This is the best solution I could come up with:
public extension String {
func componentsKeepingSeparator(separatedBy separator: Self) -> Array<String> {
self.components(separatedBy: separator)
.flatMap { [$0, separator] }
.dropLast()
.filter { $0 != "" }
}
}
And on playground, if I were to run this, it works perfectly.
PlaygroundPage.current.setLiveView(
"Hello {world}!"
.componentsKeepingSeparator(separatedBy: "{world}")
.reduce(Text("")) { text, str in
if str == "{world}" { return text + Text("\(Image(systemName: "globe"))") }
return text + Text(str)
}
)
I'm sure there is a more optimal solution, but this will do for now.
EDIT:
Since I needed support for multiple placeholders, I've added some more extensions that does the job more comprehensively.
func componentsKeepingSeparators(separatedBy separators: [Self]) -> [String] {
var finalResult = [self]
separators.forEach { separator in
finalResult = finalResult.flatMap { strElement in
strElement.componentsKeepingSeparator(separatedBy: separator)
}
}
return finalResult
}
and on the playground
PlaygroundPage.current.setLiveView(
"Hello {world}{world}{world}! {wave}"
.componentsKeepingSeparators(separatedBy: ["{world}", "{wave}"])
.reduce(Text("")) { text, str in
if str == "{world}" { return text + Text("\(Image(systemName: "globe"))") }
if str == "{wave}" { return text + Text("\(Image(systemName: "hand.wave"))") }
return text + Text(str)
}
)
This extension has a double loop and might not be very efficient, so again, if someone can think of a better solution, please do post.
I came to this question via answering this one and it piqued my interest. As I say in my answer there, the secret sauce is that LocalizedStringKey, when initialised with an interpolated string literal, is capable of building in references to SwiftUI Image types which can be rendered in Text.
Because you're not using an interpolated string literal, you can either build things up by multiple Texts, as in the other answers here, or do something smart with LocalizedStringKey.StringInterpolation. The advantage of this approach is that you can also use the image-holding text in any other view that uses LocalizedStringKey (which is, well, pretty much any of them that display text).
This extension on LocalizedStringKey will manually build an interpolated string:
extension LocalizedStringKey {
private static let imageMap: [String: String] = [
"world": "globe",
"moon": "moon"
]
init(imageText: String) {
var components = [Any]()
var length = 0
let scanner = Scanner(string: imageText)
scanner.charactersToBeSkipped = nil
while scanner.isAtEnd == false {
let up = scanner.scanUpToString("{")
let start = scanner.scanString("{")
let name = scanner.scanUpToString("}")
let end = scanner.scanString("}")
if let up = up {
components.append(up)
length += up.count
}
if let name = name {
if start != nil, end != nil, let imageName = Self.imageMap[name] {
components.append(Image(systemName: imageName))
length += 1
} else {
components.append(name)
}
}
}
var interp = LocalizedStringKey.StringInterpolation(literalCapacity: length, interpolationCount: components.count)
for component in components {
if let string = component as? String {
interp.appendInterpolation(string)
}
if let image = component as? Image {
interp.appendInterpolation(image)
}
}
self.init(stringInterpolation: interp)
}
}
You may want to cache these values if they are coming from an API, I haven't checked the performance of this code in a rendering loop.
You add an extension on Text, or any other view:
extension Text {
init(imageText: String) {
self.init(LocalizedStringKey(imageText: imageText))
}
}
So you can do this:
Text(imageText: "Hello {world}! or {moon} or {unmapped}")
Which gives you:
For your question the key here is not LocalizedStringKey but the key here is \() methods means string interpolation.
According to Swift document, string interpolation is a way to construct a new String value from a mix of constants, variables, literals, and expressions by including their values inside a string literal. You can use string interpolation in both single-line and multiline string literals.
In here it combines two things which is Text("hello") and Image(systemName: "globe") into a new String. Your code is wrong because of you append the string of value.
Without LocalizedStringKey, Text will appear as same as your Hello 🌍!.
Text("Hello \(Image(systemName: "globe"))!")
Or you can use as combination for easier understanding
Text("hello") + Text(Image(systemName: "globe")) + Text("!")
And for you question about mapping value you can make a dictionary for mapping image or name image do that
var dict : [String:String] = ["world" : "globe"]
// Add default name image value if key is nil
Text("Hello \(Image(systemName: dict["world", default:"globe"]))!")
Text("hello") + Text(Image(systemName: dict["world", default: "globe"])) + Text("!")
var dict : [String:Image] = ["world" : Image(systemName: "globe")]
// Add default image value if key is nil
Text("hello\(dict["world", default: Image(systemName: "globe")])!")
Text("hello") + Text(dict["world", default: Image(systemName: "globe")]) + Text("!")
All of them works the same an print out Hello 🌍!
Using #Aswath's answer, here's a custom container:
struct CText: View {
var text: String
var placeholders: [String: String]
var imagePlaceholders: [String: Image]
public init(_ text: String) {
self.text = text
self.placeholders = [:]
self.imagePlaceholders = [:]
}
private init(_ text: String, placeholders: [String: String], imagePlaceholders: [String: Image]) {
self.text = text
self.placeholders = placeholders
self.imagePlaceholders = imagePlaceholders
}
private var array: [String] {
let strings = Array(placeholders.keys)
let images = Array(imagePlaceholders.keys)
return strings + images
}
var body: Text {
text
.componentsKeepingSeparators(separatedBy: array)
.reduce(Text("")) { text, str in
if let place = placeholders[str] {
return text + Text(place)
}else if let place = imagePlaceholders[str] {
return text + Text("\(place)")
} else {
return text + Text(str)
}
}
}
func replacing(_ holder: String, with replacement: String) -> CText {
var oldPlaceholders = placeholders
oldPlaceholders[holder] = replacement
return CText(text, placeholders: placeholders, imagePlaceholders: imagePlaceholders)
}
func replacing(_ holder: String, with replacement: Image) -> CText {
var oldPlaceholders = imagePlaceholders
oldPlaceholders[holder] = replacement
return CText(text, placeholders: placeholders, imagePlaceholders: oldPlaceholders)
}
}
Usage:
struct Test: View {
var body: some View {
CText("Hello {world}")
.replacing("{world}", with: Image(systemName: "globe"))
}
}
Edit: If you need to access Text instead of View, add .body at the end:
struct Test: View {
var body: some View {
CText("Hello {world}")
.replacing("{world}", with: Image(systemName: "globe"))
.body
}
}
This is a small improvement to #jrturton's answer tweaked to my needs. Perhaps this might benefit others. However, this is very different to my original answer, and so it made sense to me to add this as a new answer. The deletingPrefix is from hackingwithswift
import PlaygroundSupport
import Foundation
import SwiftUI
extension String {
func deletingPrefix(_ prefix: String) -> String {
guard self.hasPrefix(prefix) else { return self }
return String(self.dropFirst(prefix.count))
}
}
extension LocalizedStringKey {
#available(iOS 15, *)
init(imageText: String, replacementClosure: (String) -> Any) {
var components = [Any]()
var length = 0
let scanner = Scanner(string: imageText)
scanner.charactersToBeSkipped = nil
while scanner.isAtEnd == false {
let up = scanner.scanUpToString("{")
let start = scanner.scanString("{")
let name = scanner.scanUpToString("}")
let end = scanner.scanString("}")
if let up = up {
components.append(up)
length += up.count
}
if let name = name {
if start == nil || end == nil { self.init(stringLiteral: imageText) }
let replacement = replacementClosure(name)
switch replacement {
case let image as Image:
components.append(image)
case let attributedString as AttributedString:
components.append(attributedString)
case let plainString as String:
components.append(plainString)
default:
print("No action.")
}
}
}
var interp = LocalizedStringKey.StringInterpolation(literalCapacity: length, interpolationCount: components.count)
for component in components {
if let string = component as? String {
interp.appendInterpolation(string)
}
if let attrString = component as? AttributedString {
interp.appendInterpolation(attrString)
}
if let image = component as? Image {
interp.appendInterpolation(image)
}
}
self.init(stringInterpolation: interp)
}
}
extension Text {
init(imageText: String) {
self.init(LocalizedStringKey(imageText: imageText, replacementClosure: { string in
switch string {
case "world":
return Image(systemName: "globe")
case "moon":
return Image(systemName: "moon")
case let stylisedString where stylisedString.hasPrefix("style1__"):
return AttributedString(stylisedString.deletingPrefix("style1__"), attributes: AttributeContainer().foregroundColor(.blue))
default: return string
}
}))
}
}
PlaygroundPage.current.setLiveView(Text(imageText: "Hello {world}! or {moon} or {style1__unmapped}")
)

Swift || do we possible to detect how many spaces of empty string have?

lets say we have an extension of String:
extension String {
var textBeCleared: Bool {
// how we write here
}
}
then:
var demo1 = "" // no space over here
demo.textBeCleared // true
var demo2 = " " // type one space over here
demo.textBeCleared // false
because I got a sticky issue is: I have a save button at bottom of a text field, save button only displays when removing all text contents in the text field, like demo1. but if type more space over here, like demo2, save button will be hidden.
User filter for this purpose. It will filter each char in the string and you will get your expected result.
let inputString1 = " "
let inputString2 = ""
let inputString3 = "a"
let spacesCount1 = inputString1.filter { $0 == " " }
print(spacesCount1.count)
let spacesCount2 = inputString2.filter { $0 == " " }
print(spacesCount2.count)
let spacesCount3 = inputString3.filter { $0 == " " }
print(spacesCount3.count)
Output
3
0
0
thanks guys, I managed solved this by following code
let inputString1 = " "
let inputString2 = ""
let inputString3 = "a"
extension String {
var textBeCleared: Bool {
!self.contains(" ") && allSatisfy { $0.isWhitespace }
}
}
inputString1.textBeCleared // false
inputString2.textBeCleared // true
inputString3.textBeCleared // false
you could try this:
extension String {
var textBeCleared: Bool {
self.isEmpty
}
}
var demo1 = "" // no space over here
var demo2 = " " // type one space over here
print(" demo1.textBeCleared: \(demo1.textBeCleared) ") // true
print(" demo2.textBeCleared: \(demo2.textBeCleared) ") // false

Grey Matcher to get text of multiple elements matches the same grey matcher

I am new to this framework. Could you please help me to get the text of multiple elements matches the same matcher on UI.
You can get the text of the element using the following function
open class GreyElement {
var text = ""
}
func grey_getText(_ elementCopy: GreyElement) -> GREYActionBlock {
return GREYActionBlock.action(withName: "get text",
constraints: grey_respondsToSelector(#selector(getter: UILabel.text))) { element,
errorOrNil -> Bool in
let elementObject = element as? NSObject
let text = elementObject?.perform(#selector(getter: UILabel.text),
with: nil)?.takeRetainedValue() as? String
elementCopy.text = text ?? ""
return true
}
}
And then in your test code:
var label = GreyElement()
for i in 0..<100 {
EarlGrey.selectElement(...).perform(grey_getText(text))
XCTAssert(label.count > 10)
}
The XCTest version:
for element in app.staticText[...].allElementsBoundByIndex {
XCTAssert(element.label.count > 10)
}

How to remove duplicate characters from a string in Swift

ruby has the function string.squeeze, but I can't seem to find a swift equivalent.
For example I want to turn bookkeeper -> bokepr
Is my only option to create a set of the characters and then pull the characters from the set back to a string?
Is there a better way to do this?
Edit/update: Swift 4.2 or later
You can use a set to filter your duplicated characters:
let str = "bookkeeper"
var set = Set<Character>()
let squeezed = str.filter{ set.insert($0).inserted }
print(squeezed) // "bokepr"
Or as an extension on RangeReplaceableCollection which will also extend String and Substrings as well:
extension RangeReplaceableCollection where Element: Hashable {
var squeezed: Self {
var set = Set<Element>()
return filter{ set.insert($0).inserted }
}
}
let str = "bookkeeper"
print(str.squeezed) // "bokepr"
print(str[...].squeezed) // "bokepr"
I would use this piece of code from another answer of mine, which removes all duplicates of a sequence (keeping only the first occurrence of each), while maintaining order.
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var alreadyAdded = Set<Iterator.Element>()
return self.filter { alreadyAdded.insert($0).inserted }
}
}
I would then wrap it with some logic which turns a String into a sequence (by getting its characters), unqiue's it, and then restores that result back into a string:
extension String {
func uniqueCharacters() -> String {
return String(self.characters.unique())
}
}
print("bookkeeper".uniqueCharacters()) // => "bokepr"
Here is a solution I found online, however I don't think it is optimal.
func removeDuplicateLetters(_ s: String) -> String {
if s.characters.count == 0 {
return ""
}
let aNum = Int("a".unicodeScalars.filter{$0.isASCII}.map{$0.value}.first!)
let characters = Array(s.lowercased().characters)
var counts = [Int](repeatElement(0, count: 26))
var visited = [Bool](repeatElement(false, count: 26))
var stack = [Character]()
var i = 0
for character in characters {
if let num = asciiValueOfCharacter(character) {
counts[num - aNum] += 1
}
}
for character in characters {
if let num = asciiValueOfCharacter(character) {
i = num - aNum
counts[i] -= 1
if visited[i] {
continue
}
while !stack.isEmpty, let peekNum = asciiValueOfCharacter(stack.last!), num < peekNum && counts[peekNum - aNum] != 0 {
visited[peekNum - aNum] = false
stack.removeLast()
}
stack.append(character)
visited[i] = true
}
}
return String(stack)
}
func asciiValueOfCharacter(_ character: Character) -> Int? {
let value = String(character).unicodeScalars.filter{$0.isASCII}.first?.value ?? 0
return Int(value)
}
Here is one way to do this using reduce(),
let newChar = str.characters.reduce("") { partial, char in
guard let _ = partial.range(of: String(char)) else {
return partial.appending(String(char))
}
return partial
}
As suggested by Leo, here is a bit shorter version of the same approach,
let newChar = str.characters.reduce("") { $0.range(of: String($1)) == nil ? $0.appending(String($1)) : $0 }
Just Another solution
let str = "Bookeeper"
let newChar = str.reduce("" , {
if $0.contains($1) {
return "\($0)"
} else {
return "\($0)\($1)"
}
})
print(str.replacingOccurrences(of: " ", with: ""))
Use filter and contains to remove duplicate values
let str = "bookkeeper"
let result = str.filter{!result.contains($0)}
print(result) //bokepr

Finding the first non-repeating character in a String using Swift

This finds the duplicates in the array, but i'm looking for something that finds the first non-repeating character in a string. I've been trying to figure out a way to do this and I cannot figure it out. This is the closest i've gotten.
var strArray = ["P","Q","R","S","T","P","R","A","T","B","C","P","P","P","P","P","C","P","P","J"]
println(strArray)
var filter = Dictionary<String,Int>()
var len = strArray.count
for var index = 0; index < len ;++index {
var value = strArray[index]
if (filter[value] != nil) {
strArray.removeAtIndex(index--)
len--
}else{
filter[value] = 1
}
}
println(strArray)
In order to tell if a character repeats itself, go through the entire array once, incrementing the count of occurrences in a dictionary:
let characters = ["P","Q","R","S","T","P","R","A","T","B","C","P","P","P","P","P","C","P","P","J"]
var counts: [String: Int] = [:]
for character in characters {
counts[character] = (counts[character] ?? 0) + 1
}
let nonRepeatingCharacters = characters.filter({counts[$0] == 1})
// ["Q", "S", "A", "B", "J"]
let firstNonRepeatingCharacter = nonRepeatingCharacters.first!
// "Q"
Here is a simple solution
let inputString = "PQRSTPRATBCPPPPPCPPJ"
func nonRepeat (_ input: String) -> String {
for char in input {
if input.firstIndex(of: char) == input.lastIndex(of: char) {
return String(char)
}
}
return ""
}
print (nonRepeat(inputString))
In the above example it would print "Q"
func firstNonRepeatedCharacter(input: String) -> Character?{
var characterCount : [Character : Int] = [:]
var uniqueCharacter: Character?
for character in input{
if let count = characterCount[character]{
characterCount[character] = count + 1
if(uniqueCharacter == character)
{
uniqueCharacter = nil
}
}
else{
characterCount[character] = 1
if(uniqueCharacter == nil){
uniqueCharacter = character
}
}
}
return uniqueCharacter
}
Without extra loop to find character from characterCount dictionary
Here is the way I have found to detect the first non-repeated character. It removes spaces and punctuation to find the actual letter or number that does not repeat.
extension String {
func removeNonAlphaNumChars() -> String {
let charSet = NSCharacterSet.alphanumericCharacterSet().invertedSet
return self
.componentsSeparatedByCharactersInSet(charSet)
.joinWithSeparator("")
}
var firstNonRepeatedCharacter: Character? {
let alphaNumString = self.removeNonAlphaNumChars()
let characters = alphaNumString.characters
let count = characters.count
guard count > 0 else { return nil }
// Find unique chars
var dict: [Character: Int?] = [:]
for (index, char) in characters.enumerate() {
if dict[char] != nil {
dict[char] = (nil as Int?)
}
else {
dict[char] = index
}
}
return dict.filter { $0.1 != nil }.sort { $0.1 < $1.1 }.first?.0
}
}
I totally wonder why the accepted answer was considered correct. They are using
.first
method of a dictionary and that according to documentation would return a random element in the dictionary and not the first element as a dictionary in swift is not ordered like an array.
please do find below an implementation that works
func firstNonRepeatingLetter(_ str: String) -> String{
var characterDict = [String : Int]()
for character in str{
let lower = character.lowercased()
if let count = characterDict[lower]{
characterDict[lower] = count + 1
}else{
characterDict[lower] = 1
}
}
let filtered = characterDict.filter { $0.value == 1}
for character in str{
let lower = character.lowercased()
if let _ = filtered[lower]{
return lower
}
}
return ""
}
firstNonRepeatingLetter("moonmen") would return "e".
We can iterate once and keep the letter counts inside a dictionary.
Then, iterate again and return first letter where we see it was encountered once only (or "_" if not found a non-repeating letter):
func firstNotRepeatingCharacter(s: String) -> Character {
var letterCounts: [String: Int] = [:]
var result: Character = "_"
for letter in s {
if let currentLetterCount = letterCounts[String(letter)] {
letterCounts[String(letter)] = currentLetterCount + 1
} else {
letterCounts[String(letter)] = 1
}
}
for letter in s {
if letterCounts[String(letter)] == 1 {
result = letter
break
}
}
return result
}
OrderedDictionary makes this easy for all Sequences of Hashables, not just Strings:
import struct OrderedCollections.OrderedDictionary
extension Sequence where Element: Hashable {
var firstUniqueElement: Element? {
OrderedDictionary(zip(self, true)) { _, _ in false }
.first(where: \.value)?
.key
}
}
/// `zip` a sequence with a single value, instead of another sequence.
public func zip<Sequence: Swift.Sequence, Constant>(
_ sequence: Sequence, _ constant: Constant
) -> LazyMapSequence<
LazySequence<Sequence>.Elements,
(LazySequence<Sequence>.Element, Constant)
> {
sequence.lazy.map { ($0, constant) }
}
func getFirstUniqueChar(string:String)->Character?{
var counts: [String: Int] = [:]
for character in string {
let charString = "\(character)"
counts[charString] = (counts[charString] ?? 0) + 1
}
let firstNonRepeatingCharacter = string.first {counts["\($0)"] == 1}
return firstNonRepeatingCharacter
}
print(getFirstUniqueChar(string: string))
import Foundation
import Glibc
var str:String = "aacbbcee"//your input string
var temp:String = ""
var dict:[Character:Int] = [:]
for char in str{
if let count = dict[char]{
dict[char] = count+1//storing values in dict and incrmenting counts o key
}
else{
dict[char] = 0
}
}
var arr:[Character] = []
for (key, value) in dict{
if value == 0{
arr.append(key)//filtering out, take characters which has value>0
} //int(arr)
}//print(arr.count)
if arr.count != 0{
outer:for char in str{//outer is labeling the loop
for i in arr{
if i == char{
print(i,"is first")//matching char with array elements if found break
break outer
}
else{
continue
}
}
}
}
else{
print("not found")
}
func firstNonRepeatedChar(string: String) -> Character {
var arr: [Character] = []
var dict: [Character : Int] = [:]
for character in string.description {
arr.append(character)
}
for character in arr {
dict[character] = (dict[character] ?? 0) + 1
}
let nonRepeatedArray = arr.filter { char in
if dict[char] == 1 {return true}
return false
}
let firstNonRepeatedChar = nonRepeatedArray.first
return firstNonRepeatedChar!
}
print(firstNonRepeatedChar(string: "strinstrig"))