Creating Range<String.Index> from constant Ints - string

What is wrong with this piece of code for constructing a range that should then serve in a call to substringWithRange?
let range = Range<String.Index>(start: 0, end: 3)
The Swift compiler (in Xcode 7.1.1) marks it with this error message:
Cannot invoke initializer for type 'Range<Index>' with an argument list of type '(start: Int, end: Int)'

You need to reference the startIndex of a specific string, then advance:
let longString = "Supercalifragilistic"
let startIndex = longString.startIndex
let range = Range(start: startIndex, end: startIndex.advancedBy(3))

You can use various ways
let startIndex = text.startIndex
let endIndex = text.endIndex
var range1 = startIndex.advancedBy(1) ..< text.endIndex.advancedBy(-4)
var range2 = startIndex.advancedBy(0) ..< startIndex.advancedBy(5)
var range3 = startIndex ..< endIndex
let substring = text.substringWithRange(range)

Related

Swift 3.0 convert Double() to NSMutableAttributedString

in advance thanks for help.
I am trying to make calculator application (for specific purposes) and I would like to know, if there exist a way how to convert Double() to NSMutableAttributedString. I need this for label output answer.
Reason of using NSMutableAttributedString is because I would like to have answer with subscripts and upper-scripts.
//example of my code
var a = Double(), b = Double(), c = Double()
a = Double(textField1.text!)
b = Double(textField2.text!)
c = a + b
let font:UIFont? = UIFont(name: "Courier", size:12)
let fontSuper:UIFont? = UIFont(name: "Courier", size:10)
//for x_1 (subscript for "1")
x1_t:NSMutableAttributedString = NSMutableAttributedString(string: "x1", attributes: [NSFontAttributeName:font!])
x1_t.setAttributes([NSFontAttributeName:fontSuper!,NSBaselineOffsetAttributeName:-4], range: NSRange(location:1,length:1))
var result = NSMutableAttributedText()
// what to do to get output for a label like "x_1 = String(c) m"
If there exist another way like append String() to NSAtributedString() - I am looking forward for answers.
As I understand it, your input strings (named "prestring1" and "afterstring1" in your own answer) could just be normal strings without attributes, because you only need the final result to be an attributed string.
This would drastically simplify your function, for example you could use string interpolation first and then only make an attributed string and move up (or down) the last part (or any part you want, I'm using an hardcoded range in my example but it's just an example).
Like:
let randomstring = "Random ="
let afterstring = "m2"
let result: Double = 42.1
func stringer (pre: String,
result: Double,
post: String) -> NSMutableAttributedString
{
let base = "\(pre) \(result) \(post)"
let mutable = NSMutableAttributedString(string: base)
mutable.addAttribute(NSBaselineOffsetAttributeName, value: 4,
range: NSRange(location: mutable.length - 2, length: 2))
return mutable
}
let attributedString = stringer(pre: randomstring, result: result, post: afterstring)
Gives:
I am still not quit sure how to do it, but I could create simple function, which is approximately doing what I need. Here I am sharing my answer in case someone has the same question, but in case someone knows better answer, share it with others :)
var randomstring = "Random ="
var prestring1 = NSMutableAttributedString(string: randomstring)
var afterstring1 = NSMutableAttributedString(string: "m2")
var result1 = Double()
result1 = 42.1
func stringer (prestring: NSMutableAttributedString, result: Double, afterstring: NSMutableAttributedString) -> NSMutableAttributedString {
var mutableatributedresult = NSMutableAttributedString(string: String(result))
var mutableaddition = NSMutableAttributedString(string: " ")
var alltext = NSMutableAttributedString()
alltext.append(prestring)
alltext.append(mutableaddition)
alltext.append(mutableatributedresult)
alltext.append(mutableaddition)
alltext.append(afterstring)
return alltext
}
stringer(prestring: prestring1, result: result1, afterstring: afterstring1)
//This should give result of "Random = 42.1 m2"
If someone knows better solution I am curious.

Spaces in string causing fatal error: cannot increment beyond endIndex

I have a string "PIXEL STUDIOS - TEST1" My code works until I reach the first space in the string.
var str = label.stringValue
let c = str.characters
let r = c.index(c.startIndex, offsetBy: 6)..<c.index(c.endIndex, offsetBy: 0)
let substring = str[r]
print(substring)
When I run my code and offsetBy 5 it works but when I try to offset past that point I get the error. Is there something else I need to do to handle spaces in my string?
Your code is working with the given string:
var str = "PIXEL STUDIOS - TEST1"
let c = str.characters
let r = c.index(c.startIndex, offsetBy: 6)..<c.index(c.endIndex, offsetBy: 0)
let substring = str[r]
print(substring)
prints:
STUDIOS - TEST1
Conclusion: label.stringValue is fishy.
Print it out for further investigations.

find the position of character in a substring [duplicate]

This question already has an answer here:
how to find the index of a character in a string from specific position
(1 answer)
Closed 6 years ago.
How can I find the first position of a character in a substring. Not in the string overall, but the first after a specified character position.
Example:
var str = "This is a test string"
//find the position of first "i" after "is"
let position = str.firstPositionOfIAfterPosition(5) // returns 18
I know I can find the overall first position with code below. How can I extend this to start looking only after a specified character position?
let position = str.rangeOfString("i").startIndex
var s = "This is a test string"
var targetRange = s.characters.indices
targetRange.startIndex = targetRange.startIndex.advancedBy(6) // skip past
let r = s.rangeOfString("i", options: [], range: targetRange, locale: nil)
// 18..<19
var str = "This is a test string"
func getIndexAfterString(string: String) -> Int {
let firstIndex = str.rangeOfString(string)?.startIndex.advancedBy(string.characters.count)
let i: Int = str.startIndex.distanceTo(firstIndex!)
let secondIndex = str.substringFromIndex(firstIndex!).rangeOfString("i")?.startIndex
let j: Int = str.startIndex.distanceTo(secondIndex!)
return i + j
}
let index: Int = getIndexAfterString(" is ") //18
Similar to matt's answer, but as String extension and with error handling
extension String {
func firstPositionOf(string: String, afterPosition index: Int) -> String.Index?
{
if self.isEmpty || self.characters.count - 1 < index { return nil }
let subRange = Range<String.Index>(self.startIndex.advancedBy(index + 1)..<self.endIndex)
guard let foundRange = self.rangeOfString(string, options: [], range: subRange) else { return nil }
return foundRange.startIndex
}
}
let str = "This is a test string"
let position = str.firstPositionOf("i", afterPosition:5) // -> 18

In Swift NSAttributedString has more characters than String?

I am trying to add attributes to some ranges in Swift String.
I found ranges of first and last symbol in substring and color the text between them (including) in red.
let mutableString = NSMutableAttributedString(string: text)
let str = mutableString.string
//Red symbols
var t = 0
let symbols = mutableString.string.characters.count
while t < symbols {
if str[t] == "[" {
let startIndex = t
while str[t] != "]" {
t += 1
}
t += 1
let endIndex = t
mutableString.addAttribute(
NSForegroundColorAttributeName,
value: UIColor.redColor(),
range: NSMakeRange(startIndex, endIndex - startIndex))
}
t += 1
}
But I found that ranges in String and in NSMutableAttributedString are not equal. Range in String is shorter (this text is not in Unicode encoding).
Is there a some way to find ranges not in underlying String but in NSAttributedString to find it correctly?
Example:
print(mutableString.length) //550
print(mutableString.string.characters.count) //548
Why is this difference?
Yes it is possible to find ranges in NSMutableAttributedString.
Example :
let text = "[I love Ukraine!]"
var start = text.rangeOfString("[")
var finish = text.rangeOfString("]")
let mutableString = NSMutableAttributedString(string: text)
let startIndex = mutableString.string.rangeOfString("[")
let finishIndex = mutableString.string.rangeOfString("]")
Example output from playground:
Distinguish between String and NSString, even though they are bridged to one another. String is native Swift, and you define a range in terms of String character index. NSString is Cocoa (Foundation), and you define a range in terms of NSRange.
Yes, I found it.
Windows end-of-line "\r\n" is two symbols in NSAttributedString but only one character in Swift String.
I use checking in my code:
let symbols = mutableString.string.characters.count
var extendedSymbols = 0
while t < symbols {
if str[t] == "\r\n" { extendedSymbols += 1 }
else if str[t] == "[" {
let startIndex = t + extendedSymbols
while str[t] != "]" {
t += 1
}
t += 1
let endIndex = t + extendedSymbols
mutableString.addAttribute(
NSForegroundColorAttributeName,
value: UIColor.redColor(),
range: NSMakeRange(startIndex, endIndex - startIndex))
}
t += 1
}
Thank you all for help!!!

Change text of an attributed string and retain attributes in Swift

For output in a database program, I have certain text that I've inserted marks to indicate bold or italics, as well as some text that is substituted for images. For instance:
"%Important% ^All employees to the breakroom^" should have final output as:
Important All employees to the breakroom
I have code written to find the text with "%" signs around it and "^" signs, but the trouble I have now is the text outputs like:
%Important% ^All employees to the breakroom^
I'd like to remove these % and ^'s while retaining the string's formatting.
This is the code I'm using up until it breaks:
func processText(inString string: String) -> NSAttributedString {
let pattern = ["(?<=\\^).*?(?=\\^)","(?<=\\%).*?(?=\\%)","\\^", "\\%"]
let italicsRegex = NSRegularExpression(pattern: pattern[0], options: .allZeros, error: nil)
let range = NSMakeRange(0, count(string))
let italicsMatches = italicsRegex?.matchesInString(string, options: .allZeros, range: range) as? [NSTextCheckingResult]
var attributedText = NSMutableAttributedString(string: string)
for match in italicsMatches! {
attributedText.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica-Oblique", size: 14.0)!, range: match.range)
}
let boldRegex = NSRegularExpression(pattern: pattern[1], options: .allZeros, error: nil)
let boldMatches = boldRegex?.matchesInString(string, options: .allZeros, range: range) as? [NSTextCheckingResult]
for match in boldMatches! {
attributedText.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica-Bold", size: 14.0)!, range: match.range)
}
let removeItalicsMarksRegex = NSRegularExpression(pattern: pattern[2], options: .allZeros, error: nil)
let removeItalicsMarksMatches = removeItalicsMarksRegex?.matchesInString(string, options: .allZeros, range: range) as? [NSTextCheckingResult]
var numberOfLoops = 0
for match in removeItalicsMarksMatches! {
attributedText.replaceCharactersInRange(match.range, withString: "")
}
return attributedText.copy() as! NSAttributedString
}
This works for the % match (but only the first character) and causes a crash on the ^ character immediately.
Any help or advice with resolving this would be appreciated. Thanks.
Martin,
I ended up using something very similar, but I decided to change the regular expression to include the ^ marks. In doing so, I was able to then clip the first and last characters of the included attributed substring with the "replaceCharactersInRange" method. This works a little better for my purposes so far because it's working from the attributed string so it doesn't screw up or remove any of its attributes.
I've attached the regex and the portion of the code that deals with italics for anyone's future reference (and thanks, again!):
func processText(inString string: String) -> NSAttributedString {
let pattern = ["\\^.*?\\^"] //Presented as an array here because in the full code there are a lot of patterns that are run.
let italicsRegex = NSRegularExpression(pattern: pattern[0], options: .allZeros, error: nil)
//In addition to building the match for this first regular expression, I also gather build the regular expressions and gather matches for all other matching patterns on the initial string ("string") before I start doing any processing.
let range = NSMakeRange(0, count(string.utf16))
let italicsMatches = italicsRegex?.matchesInString(string, options: .allZeros, range: range) as? [NSTextCheckingResult]
var attributedText = NSMutableAttributedString(string: string)
var charactersRemovedFromString = 0
for match in italicsMatches! {
let newRange = NSMakeRange(match.range.location - charactersRemovedFromString, match.range.length) // Take the updated range for when this loop iterates, otherwise this crashes.
attributedText.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica-Oblique", size: 12.0)!, range: newRange)
let rangeOfFirstCharacter = NSMakeRange(match.range.location - charactersRemovedFromString, 1)
attributedText.replaceCharactersInRange(rangeOfFirstCharacter, withString: "")
charactersRemovedFromString += 2
let rangeOfLastCharacter = NSMakeRange(match.range.location + match.range.length - charactersRemovedFromString, 1)
attributedText.replaceCharactersInRange(rangeOfLastCharacter, withString: "")
}
return attributedText
}
Here is a possible solution, essentially a translation of
how to catch multiple instances special indicated **characters** in an NSString and bold them in between?
from Objective-C to Swift.
The idea is to add the attributes and remove the delimiters in one loop. The shift
variable is needed to adjust the matching ranges after the first delimiters have been removed.
For the sake of simplicity, only the "^...^" processing is shown.
func processText(inString string: String) -> NSAttributedString {
let pattern = "(\\^)(.*?)(\\^)"
let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil)!
var shift = 0 // number of characters removed so far
let attributedText = NSMutableAttributedString(string: string)
regex.enumerateMatchesInString(string, options: nil, range: NSMakeRange(0, count(string.utf16))) {
(result, _, _) -> Void in
var r1 = result.rangeAtIndex(1) // Location of the leading delimiter
var r2 = result.rangeAtIndex(2) // Location of the string between the delimiters
var r3 = result.rangeAtIndex(3) // Location of the trailing delimiter
// Adjust locations according to the string modifications:
r1.location -= shift
r2.location -= shift
r3.location -= shift
// Set attribute for string between delimiters:
attributedText.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica-Oblique", size: 14.0)!, range: r2)
// Remove leading and trailing delimiters:
attributedText.mutableString.deleteCharactersInRange(r3)
attributedText.mutableString.deleteCharactersInRange(r1)
// Update offset:
shift += r1.length + r3.length
}
return attributedText.copy() as! NSAttributedString
}
Note that enumerateMatchesInString() takes an NSRange, therefore you have to compute
the number of UTF-16 characters and not the number of Swift characters.
Example:
let text = "🇩🇪😀aaa ^🇭🇰😁bbb^ 🇳🇱😆eee"
let attrText = processText(inString: text)
println(attrText)
Output:
🇩🇪😀aaa {
}🇭🇰😁bbb{
NSFont = " font-family: \"Helvetica-Oblique\"; font-weight: normal; font-style: italic; font-size: 14.00pt";
} 🇳🇱😆eee{
}
That worked for me!
extension UILabel {
func updateAttributedText(_ text: String) {
if let attributedText = attributedText {
let mutableAttributedText = NSMutableAttributedString(attributedString: attributedText)
mutableAttributedText.mutableString.setString(text)
self.attributedText = mutableAttributedText
}
}
}

Resources