I have a string that contains a url. I am trying to check if the url has a space which is invalid.
let url = "http://www.example.com/images/pretty pic.png"
As you can see in this example, there is a space between pretty and pic. Thanks.
Another alternative: check if the set of characters to the string contain a whitespace
let url = "http://www.example.com/images/pretty pic.png"
url.characters.contains(" ") // true
let url = "http://www.example.com/images/prettypic.png"
url.characters.contains(" ") // false
let url = "http://www.example.com/images/pretty pic.png"
let whiteSpace = " "
if let hasWhiteSpace = url.rangeOfString(whiteSpace) {
print ("has whitespace")
} else {
print("no whitespace")
}
Use simple indexOf to find the space.
function hasWhiteSpace(s) {
return s.indexOf(' ') >= 0;
}
extension on String which returns bool would be more elegant solution here
extension String {
public var hasWhiteSpace: Bool {
return self.contains(" ")
}
}
For Swift 5
var string = "Hi "
if string.contains(" "){
print("Has space")
}else{
print("Does not have space")
}
Related
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}")
)
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
It is necessary to remove the quotes at the beginning and end of the line, if they are in the line
Could it be more beautiful?
var str = "\"Hello, playground\""
let quotes = "\""
if str.hasPrefix(quotes) && str.hasSuffix(quotes) {
let v = str.dropFirst()
str = String(v.dropLast())
}
print(str)
If you like a one liner:
let str = "\"\"\"Hello, playground\"\""
let unquoted = String(str.drop(while: { $0 == "\""}).reversed().drop(while: { $0 == "\""}).reversed())
print(unquoted) //Hello, playground
You could define these extensions to make it look a tad prettier:
extension String {
private func removeQuotesAndReverse() -> String {
return String(self.drop(while: { $0 == "\""}).reversed())
}
func unquote() -> String {
return self.removeQuotesAndReverse().removeQuotesAndReverse()
}
}
And use it like so:
let unquoted = "\"\"\"Hello, playground\"\"".unquote()
If you only need to remove the first and last quotes, if they are both present, then I would only add a check that the count is at least 2 characters, since a string like "\"" has quotes in both the prefix and suffix, but it's not between quotes:
extension String {
func withoutDoubleQuotes() -> String {
if self.hasPrefix("\""), self.hasSuffix("\""), self.count > 1 {
return String(self.dropFirst().dropLast())
}
return self
}
}
and use it like so:
"\"Hello, playground\"".withoutDoubleQuotes() //Hello, playground
"\"\"\"Hello, playground\"\"".withoutDoubleQuotes() //""Hello, playground"
"\"".withoutDoubleQuotes() //"
"\"\"".withoutDoubleQuotes() //
You can use Collection removeFirst and removeLast mutating methods:
var str = "\"Hello, playground\""
let quotes = "\""
if str.hasPrefix(quotes) && str.hasSuffix(quotes) && str != quotes {
str.removeFirst()
str.removeLast()
}
print(str) // "Hello, playground\n"
you can do so:
let str = "\"Hello, playground\""
let new = str.filter{$0 != "\""}
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
I have the following function which reverses a string value's display order.
I'm new to Swift and I'm trying to understand it's logic. What is going on with the '!pleh' value that it turns into 'Help!' ?
Thanks
func reverse(_ s: String) -> String {
var str = ""
for character in s.characters {
str = "\(character)" + str
}
return str
}
print (reverse("!pleH"))
In swift 4.0, directly call reversed on a string will get the job done
let str = "abc"
String(str.reversed()) // This will give you cba
Commented inline,
func reverse(_ s: String) -> String {
var str = ""
//.characters gives the character view of the string passed. You can think of it as array of characters.
for character in s.characters {
str = "\(character)" + str
//This will help you understand the logic.
//!+""
//p+!
//l+p! ... goes this way
print ( str)
}
return str
}
print (reverse("!pleH"))
Note: If you don't want to use the reversed() in-built function, then you can use the following code.
One-liner using Higher-order function "Reduce" on the string.
extension String {
func reverse() -> String { return self.reduce("") { "\($1)" + $0 } }
}
The function reversed(_:) iterates over each Character in the supplied string s, and simply concenates them in the reverse order.
// 1st pass in the 'for' loop:
// # start of iteration: 'str' equals ""
// update value of 'str' according to
// str = aCharacterAsString + str = "!" + ""
// 2nd pass in the 'for' loop:
// # start of iteration: str equals "!"
// update value of 'str' according to
// str = anotherCharacterAsString + str = "p" + "!"
// 3rd pass in the 'for' loop:
// # start of iteration: str equals "p!"
// update value of 'str' according to
// str = yetAnotherCharacterAsString + str = "l" + "p!"
// and so on ...
// after end of 'for' loop: str == "help!"
// this value of 'str' is then return to callee (which prints it)
A much simpler approach would be using reversed() on the CharacterView of the String instance:
let str = "!pleH"
print(String(str.characters.reversed())) // Help!
Swift 4 You can use it directly in your string
let str = "!pleH"
print(String(str.reversed())) // Help!
var string = "My,playground and my Swift"
var reverseString = ""
for str in string {
reverseString.insert(str, at: reverseString.startIndex)
}
print(reverseString)
In Swift 4 - To reverse string ::
func reverse(string:String) -> String {
var reverse = ""
for char in string {
reverse = char.description + reverse
}
return reverse
}
Input :: reverse(string: "Ashish Chhabra")
Output :: arbahhC hsihsA
var strnew = "hello world new"
var reverseStr = ""
for each in strnew
{
reverseStr = String(each) + reverseStr
}
print(reverseStr)
Different variation of answer using Character and Index.
//Reverse String
func reverse(str:String)->String{
var chars = [Character]()
for i in (0...str.count).reversed() {
let index = str.index(str.startIndex, offsetBy: i)
chars.append(str[index])
}
return String(chars)
}
You can use below code-
let str = "My name is Kplin Cours"
var reverseString = ""
for i in 0..<str.count {
let index = str.index(str.startIndex, offsetBy: (str.count - 1) - i)
// print(String(str[index]))
reverseString = reverseString + String(str[index])
}
print(reverseString) //sruoC nilpK si eman yM
let string = readLine()!
var resultString = ""
for i in 1...string.count {
let index = string.index(string.endIndex, offsetBy: -i)
resultString.append(string[index])
}
print(resultString)
let inputstr = "ABCDEFGHIGKLMNPO"
var resultstr = "";
for strchar in inputstr {
resultstr = String(strchar) + resultstr
}
print("Result = ",resultstr)
Swift 5
extension String {
func invertedEntropy() -> String {
var word = [Character]()
for char in self {
word.insert(char, at: 0)
}
return String(word)
}
}
var palindrome = "TENET"
palindrome.invertedEntropy()
// "TENET"
Simple as that!
let str = "Hello, world!"
let reversed = String(str.reversed())
print(reversed)