String formatting with characters is crashing - ios

var modifiedString: String {
var outString = String()
for thisChar in self.characters {
if (thisChar == " "){
outString.appendingFormat("%%02X",thisChar ) // This line is asking me unwrap as CVarArg
outString.appendingFormat("%%02X",thisChar as! CVarArg) // This line crashes
}
}
}
}
I am trying to use outString.appendingFormat("%%02X",thisChar )
But this line is forcing me to unwrap as CVarArg but it is crashing when I am doing it. what is wrong in this

I assume this is placed in a String extension.
The error occurs because Character does not conform to CVarArg. Instead of converting the character directly to CVarArg, you need to convert it to a type that conforms to CVarArg. Here, the best choice would be String:
outString.appendingFormat("%%02X", "\(thisChar)")
You are also missing a return statement.
However, even if you did this and your code compiles, the property you wrote still does not make much sense. I don't think it works as you intended it to, but without more information I can't really be sure.

Use this code-
var modifiedString: String {
var outString = String()
for thisChar in self.characters {
if (thisChar == " "){
// outString = outString.appendingFormat("%%02X",thisChar.description )
outString = outString.appendingFormat("%%02X", "\(thisChar)")
}
else
{
outString = outString.appending("\(thisChar)")
}
}
return outString
}
Hope this helps!

It is not clear enough what you want to do, but I guess you want to do something like this:
extension String {
var modifiedString: String {
var outString = ""
for thisChar in self.characters {
if thisChar == " " {
let charCode = String(thisChar).unicodeScalars.first!.value
outString += "%" + String(format: "%02X", charCode)
//### Or if you prefer... (Do not miss, you need 3 '%'s in the format string.)
//outString = outString.appendingFormat("%%%02X", charCode)
} else {
outString.append(thisChar)
}
}
return outString
}
}
print("what you really want to do?".modifiedString)
//->what%20you%20really%20want%20to%20do?
With looping with for thisChar in self.characters, the type of thisChar becomes Character, it's not easy to get a character code from Character in Swift.
There may be some better ways but let charCode = String(thisChar).unicodeScalars.first!.value works as expected, when thisChar contains only one UnicodeScalar.
And you should better know Swift has a method named addingPercentEncoding:
let result = "what you really want to do?".addingPercentEncoding(withAllowedCharacters: CharacterSet.whitespaces.inverted)!
print(result)
//->what%20you%20really%20want%20to%20do?

Related

How to Check if String begins with Alphabet Letter in Swift 5?

Problem: i am currently trying to Sort a List in SwiftUI according to the Items First Character. I also would like to implement a Section for all Items, which doesn't begin with a Character of the Alphabet (Numbers, Special Chars).
My Code so far:
let nonAlphabetItems = items.filter { $0.name.uppercased() != /* beginns with A - Z */ }
Does anyone has a Solution for this Issue. Of course I could do a huge Loop Construct, however I hope there is a more elegant way.
Thanks for your help.
You can check if a string range "A"..."Z" contains the first letter of your name property:
struct Item {
let name: String
}
let items: [Item] = [.init(name: "Def"),.init(name: "Ghi"),.init(name: "123"),.init(name: "Abc")]
let nonAlphabetItems = items.filter { !("A"..."Z" ~= ($0.name.first?.uppercased() ?? "#")) }
nonAlphabetItems // [{name "123"}]
Expanding on this topic we can extend Character to add a isAsciiLetter property:
extension Character {
var isAsciiLetter: Bool { "A"..."Z" ~= self || "a"..."z" ~= self }
}
This would allow to extend StringProtocol to check is a string starts with an ascii letter:
extension StringProtocol {
var startsWithAsciiLetter: Bool { first?.isAsciiLetter == true }
}
And just a helper to negate a boolean property:
extension Bool {
var negated: Bool { !self }
}
Now we can filter the items collection as follow:
let nonAlphabetItems = items.filter(\.name.startsWithAsciiLetter.negated) // [{name "123"}]
If you need an occasional filter, you could simply write a condition combining standard predicates isLetter and isASCII which are already defined for Character. It's as simple as:
let items = [ "Abc", "01bc", "Ça va", "", " ", "𓀫𓀫𓀫𓀫"]
let nonAlphabetItems = items.filter { $0.isEmpty || !$0.first!.isASCII || !$0.first!.isLetter }
print (nonAlphabetItems) // -> Output: ["01bc", "Ça va", "", " ", "𓀫𓀫𓀫𓀫"]
If the string is not empty, it has for sure a first character $0.first!. It is tempting to use isLetter , but it appears to be true for many characters in many local alphabets, including for example the antique Egyptian hieroglyphs like "𓀫" or the French alphabet with "Ç"and accented characters. This is why you need to restrict it to ASCII letters, to limit yourself to the roman alphabet.
You can use NSCharacterSet in the following way :
let phrase = "Test case"
let range = phrase.rangeOfCharacter(from: characterSet)
// range will be nil if no letters is found
if let test = range {
println("letters found")
}
else {
println("letters not found")
}```
You can deal with ascii value
extension String {
var fisrtCharacterIsAlphabet: Bool {
guard let firstChar = self.first else { return false }
let unicode = String(firstChar).unicodeScalars
let ascii = Int(unicode[unicode.startIndex].value)
return (ascii >= 65 && ascii <= 90) || (ascii >= 97 && ascii <= 122)
}
}
var isAlphabet = "Hello".fisrtCharacterIsAlphabet
The Character type has a property for this:
let x: Character = "x"
x.isLetter // true for letters, false for punctuation, numbers, whitespace, ...
Note that this will include characters from other alphabets (Greek, Cyrillic, Chinese, ...).
As String is a Sequence with Element equal to Character, we can use the .first property to get the first char.
With this, you can filter your items:
let filtered = items.filter { $0.name.first?.isLetter ?? false }
You can get this done through this simple String extension
extension StringProtocol {
var isFirstCharacterAlp: Bool {
first?.isASCII == true && first?.isLetter == true
}
}
Usage:
print ("H1".isFirstCharacterAlp)
print ("ابراهيم1".isFirstCharacterAlp)
Output
true
false
Happy Coding!
Reference

What is the best way to trim a trailing character in Dart?

In Dart the trim(), trimLeft() and trimRight() string methods do not take a parameter to specify unwanted non-whitespace characters.
What is the best way to trim a specific character from the ends of a string in Dart?
I am using this for now, but it feels hard to remember and not very generic:
final trailing = RegExp(r"/+$");
final trimmed = "test///".replaceAll(trailing, "");
assert(trimmed == "test");
There is no specific functionality to trim non-whitespace from the end of a string.
Your RegExp based approach is reasonable, but can be dangerous when the character you want to remove is meaningful in a RegExp.
I'd just make my own function:
String removeTrailing(String pattern, String from) {
if (pattern.isEmpty) return from;
var i = from.length;
while (from.startsWith(pattern, i - pattern.length)) i -= pattern.length;
return from.substring(0, i);
}
Then you can use it as:
final trimmed = removeTrailing("/", "test///")
assert(trimmed == "test");
The corresponding trimLeading function would be:
String trimLeading(String pattern, String from) {
if (pattern.isEmpty) return from;
var i = 0;
while (from.startsWith(pattern, i)) i += pattern.length;
return from.substring(i);
}
Since the existing answer by lrn has a lot of problems - including infinite loop scenarios - I thought I'd post my version.
String trimLeft(String from, String pattern){
if( (from??'').isEmpty || (pattern??'').isEmpty || pattern.length>from.length ) return from;
while( from.startsWith(pattern) ){
from = from.substring(pattern.length);
}
return from;
}
String trimRight(String from, String pattern){
if( (from??'').isEmpty || (pattern??'').isEmpty || pattern.length>from.length ) return from;
while( from.endsWith(pattern) ){
from = from.substring(0, from.length-pattern.length);
}
return from;
}
String trim(String from, String pattern){
return trimLeft(trimRight(from, pattern), pattern);
}
To trim all trailing/right characters by specified characters, use the method:
class StringUtil {
static String trimLastCharacters(String srcStr, String pattern) {
if (srcStr.length > 0) {
if (srcStr.endsWith(pattern)) {
final v = srcStr.substring(0, srcStr.length - 1 - pattern.length);
return trimLastCharacters(v, pattern);
}
return srcStr;
}
return srcStr;
}
}
For example, you want to remove all 0 behind the decimals
$23.67890000
then, invoke the method
StringUtil.trimLastCharacters("$23.67890000", "0")
finally, got the output:
$23.6789

Swift: Remove specific characters of a string only at the beginning

i was looking for an answer but haven't found one yet, so:
For example: i have a string like "#blablub" and i want to remove the # at the beginning, i can just simply remove the first char. But, if i have a string with "#####bla#blub" and i only want to remove all # only at the beginning of the first string, i have no idea how to solve that.
My goal is to get a string like this "bla#blub", otherwise it would be to easy with replaceOccourencies...
I hope you can help.
Swift2
func ltrim(str: String, _ chars: Set<Character>) -> String {
if let index = str.characters.indexOf({!chars.contains($0)}) {
return str[index..<str.endIndex]
} else {
return ""
}
}
Swift3
func ltrim(_ str: String, _ chars: Set<Character>) -> String {
if let index = str.characters.index(where: {!chars.contains($0)}) {
return str[index..<str.endIndex]
} else {
return ""
}
}
Usage:
ltrim("#####bla#blub", ["#"]) //->"bla#blub"
var str = "###abc"
while str.hasPrefix("#") {
str.remove(at: str.startIndex)
}
print(str)
I recently built an extension to String that will "clean" a string from the start, end, or both, and allow you to specify a set of characters which you'd like to get rid of. Note that this will not remove characters from the interior of the String, but it would be relatively straightforward to extend it to do that. (NB built using Swift 2)
enum stringPosition {
case start
case end
case all
}
func trimCharacters(charactersToTrim: Set<Character>, usingStringPosition: stringPosition) -> String {
// Trims any characters in the specified set from the start, end or both ends of the string
guard self != "" else { return self } // Nothing to do
var outputString : String = self
if usingStringPosition == .end || usingStringPosition == .all {
// Remove the characters from the end of the string
while outputString.characters.last != nil && charactersToTrim.contains(outputString.characters.last!) {
outputString.removeAtIndex(outputString.endIndex.advancedBy(-1))
}
}
if usingStringPosition == .start || usingStringPosition == .all {
// Remove the characters from the start of the string
while outputString.characters.first != nil && charactersToTrim.contains(outputString.characters.first!) {
outputString.removeAtIndex(outputString.startIndex)
}
}
return outputString
}
A regex-less solution would be:
func removePrecedingPoundSigns(s: String) -> String {
for (index, char) in s.characters.enumerate() {
if char != "#" {
return s.substringFromIndex(s.startIndex.advancedBy(index))
}
}
return ""
}
A swift 3 extension starting from OOPer's response:
extension String {
func leftTrim(_ chars: Set<Character>) -> String {
if let index = self.characters.index(where: {!chars.contains($0)}) {
return self[index..<self.endIndex]
} else {
return ""
}
}
}
As Martin R already pointed out in a comment above, a regular expression is appropriate here:
myString.replacingOccurrences(of: #"^#+"#, with: "", options: .regularExpression)
You can replace the inner # with any symbol you're looking for, or you can get more complicated if you're looking for one of several characters or a group etc. The ^ indicates it's the start of the string (so you don't get matches for # symbols in the middle of the string) and the + represents "1 or more of the preceding character". (* is 0 or more but there's not much point in using that here.)
Note the outer hash symbols are to turn the string into a raw String so escaping is not needed (though I suppose there's nothing that actually needs to be escaped in this particular example).
To play around with regex I recommend: https://regexr.com/

How to get all strings between particular delimiters?

I have a string called source. This string contains tags, marked with number signs (#) on left and right side.
What is the most efficient way to get tag names from the source string.
Source string:
let source = "Here is tag 1: ##TAG_1##, tag 2: ##TAG_2##."
Expected result:
["TAG_1", "TAG_2"]
Not a very short solution, but here you go:
let tags = source.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: " ,."))
.filter { (str) -> Bool in
return str.hasSuffix("##") && str.hasPrefix("##")
}
.map { (str) -> String in
return str.stringByReplacingOccurrencesOfString("##", withString: "")
}
Split the string at all occurences of ##:
let components = source.components(separatedBy: "##")
// Result: ["Here is tag 1: ", "TAG_1", ", tag 2: ", "TAG_2", "."]
Check that there's an odd number of components, otherwise there's an odd amount of ##s:
guard components.count % 2 == 1 else { fatalError("Unbalanced delimiters") }
Get every second element:
components.enumerated().filter{ $0.offset % 2 == 1 }.map{ $0.element }
In a single function:
import Foundation
func getTags(source: String, delimiter: String = "##") -> [String] {
let components = source.components(separatedBy: delimiter)
guard components.count % 2 == 1 else { fatalError("Unbalanced delimiters") }
return components.enumerated().filter{ $0.offset % 2 == 1 }.map{ $0.element }
}
getTags(source: "Here is tag 1: ##TAG_1##, tag 2: ##TAG_2##.") // ["TAG_1", "TAG_2"]
You can read this post and adapt the answer for your needs: Swift: Split a String into an array
If not you can also create your own method, remember a string is an array of characters, so you can use a loop to iterate through and check for a '#'
let strLength = source.characters.count;
var strEmpty = "";
for( var i=0; i < strLength; i++ )
{
if( source[ i ] == '#' )
{
var j=(i+2);
for( j; source[ (i+j) ] != '#'; j++ )
strEmpty += source[ (i+j) ]; // concatenate the characters to another variable using the += operator
i = j+2;
// do what you need to with the tag
}
}
I am more of a C++ programmer than a Swift programmer, so this is how I would approach it if I didn't want to use standard methods. There may be a better way of doing it, but I don't have any Swift knowledge.
Keep in mind if this does not compile then you may have to adapt the code slightly as I do not have a development environment I can test this in before posting.

Component separated by string crashing app swift

I have an app that converts textField input into an array of Int's by using componentSeparatedByString(",") but when i enter more than one comma in the textfield the app crashes, been trying to find a solution online but no luck, how can i fix this? I can keep it from crashing by checking for characters.first == "," ||characters.last == ",", but not consecutive commas.
enterValueLabel.text = ""
let circuits = circuitNumbersTextField.text!.componentsSeparatedByString(",")
let circuitNumbers = circuits.map { Int($0)!}
CircuitColors(circuitNumber: circuitNumbers, phaseColors: circuitPhaseColors )
if /*circuitNumbersTextField.text!.characters.first != "," || */circuitNumbersTextField.text!.characters.last != "," || (circuitNumbersTextField.text!.characters.first != "," && circuitNumbersTextField.text!.characters.last != ",")
Here's what I would do to make your code work. What is important here is the general idea, not the specific example I'm using (although it should work for you).
First, let's safely unwrap the text label:
if let text = circuitNumbersTextField.text {
}
Now that we avoid using circuitNumbersTextField.text! we know that an error wouldn't come from there.
Then we cut the sentence in components:
if let text = circuitNumbersTextField.text {
let circuits = text.componentsSeparatedByString(",")
}
We use flatMap to safely unwrap the Optionals returned by Int():
if let text = circuitNumbersTextField.text {
let circuits = text.componentsSeparatedByString(",")
let circuitNumbers = circuits.flatMap { Int($0) }
// circuitNumbers will only contain the successfully unwrapped values
}
Your code snippet:
if let text = circuitNumbersTextField.text {
let circuits = text.componentsSeparatedByString(",")
let circuitNumbers = circuits.flatMap { Int($0) }
if (circuits.first != "," && circuits.last != ",") || circuits.first != "," || circuits.last != "," {
// condition is met
} else {
// condition is not met
}
}
You can now safely use circuitNumbers in this code block without crashing.

Resources