KeyboardExtension adjustTextPosition issues with emojis - ios

I'm helping build a keyboardextension and I've recently run into an issue with Swift 4 and emojis. The new UTF-16 emoji support for Swift 4 is really nice but there is an issue with adjustTextPosition in UIInputViewController.
If we call adjustTextPosition to step over an emoji it will simply not step far enough, it seems like the characteroffset used by UIInputViewController doesn't match the character count used by the system.
To test simply write a text with emojis and whenever some key is clicked call:
super.textDocumentProxy.adjustTextPosition(byCharacterOffset: 1)
What can be observed is that we have to click it more than what is to be expected.

Swift 5, it seems that the following code works well on iOS 12.
let count: Int = String(text).utf16.count
textDocumentProxy.adjustTextPosition(byCharacterOffset: count)

Adjusting caret position measured in grapheme clusters (Swift Characters):
func adjustCaretPosition(offset: Int) {
guard let textAfterCaret = textDocumentProxy.documentContextAfterInput else { return }
if let offsetIndex = offset > 0 ? textAfterCaret.index(textAfterCaret.startIndex, offsetBy: offset, limitedBy: textAfterCaret.endIndex) : textBeforeCaret.index(textBeforeCaret.endIndex, offsetBy: offset, limitedBy: textAfterCaret.startIndex),
let offsetIndex_utf16 = offsetIndex.samePosition(in: offset > 0 ? textAfterCaret.utf16 : textBeforeCaret.utf16)
{
let offset = offset > 0 ? textAfterCaret.utf16.distance(from: textAfterCaret.utf16.startIndex, to: offsetIndex_utf16) : textBeforeCaret.utf16.distance(from: textBeforeCaret.utf16.endIndex, to: offsetIndex_utf16)
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
else {
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
}
UPD: A messy hack, to try and fix Safari inconsistency. Since there's no way to discriminate between safari and not safari, to act based on result looks like the only solution.
func adjustCaretPosition(offset: Int) {
// for convenience
let textAfterCaret = textDocumentProxy.documentContextAfterInput ?? ""
let textBeforeCaret = textDocumentProxy.documentContextBeforeInput ?? ""
if let offsetIndex = offset > 0 ? textAfterCaret.index(textAfterCaret.startIndex, offsetBy: offset, limitedBy: textAfterCaret.endIndex) : textBeforeCaret.index(textBeforeCaret.endIndex, offsetBy: offset, limitedBy: textAfterCaret.startIndex),
let offsetIndex_utf16 = offsetIndex.samePosition(in: offset > 0 ? textAfterCaret.utf16 : textBeforeCaret.utf16)
{
// part of context before caret adjustment
let previousText = offset > 0 ? textAfterCaret : textBeforeCaret
// what we expect after adjustment
let expectedText = offset > 0 ? String(textAfterCaret[offsetIndex..<textAfterCaret.endIndex]) : String(textBeforeCaret[textBeforeCaret.startIndex..<offsetIndex])
// offset in UTF-16 characters
let offset_utf16 = offset > 0 ? textAfterCaret.utf16.distance(from: textAfterCaret.utf16.startIndex, to: offsetIndex_utf16) : textBeforeCaret.utf16.distance(from: textBeforeCaret.utf16.endIndex, to: offsetIndex_utf16)
// making adjustment
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
// part of context after caret adjustment
let compareText = offset > 0 ? textAfterCaret : textBeforeCaret
// rollback if got unwanted results
// then adjust by grapheme clusters offset
if compareText != "", expectedText != compareText, compareText != previousText {
textDocumentProxy.adjustTextPosition(byCharacterOffset: -offset_utf16)
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
}
else {
// we probably stumbled upon a textDocumentProxy inconsistency, i.e. context got divided by an emoji
// adjust by grapheme clusters offset
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
}

try this
let correctedOffset = adjust(offset: offset)
textDocumentProxy.adjustTextPosition(byCharacterOffset: correctedOffset)
private func adjust(offset: Int) -> Int {
if offset > 0, let after = textDocumentProxy.documentContextAfterInput {
let offsetStringIndex = after.index(after.startIndex, offsetBy: offset)
let chunk = after[..<offsetStringIndex]
let characterCount = chunk.utf16.count
return characterCount
} else if offset < 0, let before = textDocumentProxy.documentContextBeforeInput {
let offsetStringIndex = before.index(before.endIndex, offsetBy: offset)
let chunk = before[offsetStringIndex...]
let characterCount = chunk.utf16.count
return -1*characterCount
} else {
return offset
}
}

Related

Get Current Paragraph Index

I am wanting to find the current paragraph that the user is typing in(where the caret is). Example: Here it would be in the 2nd Paragraph.
I know I can separate paragraphs using: let components = textView.text.components(separatedBy: "\n") but I am unsure how I would run a check for the current editing paragraph. Any ideas?
Here is one approach...
Get the Y position of the caret (insertion point). Then, loop through an enumeration of the paragraphs in the textView, comparing their bounding rects to the caret position:
extension UITextView {
func boundingFrame(ofTextRange range: Range<String.Index>?) -> CGRect? {
guard let range = range else { return nil }
let length = range.upperBound.encodedOffset-range.lowerBound.encodedOffset
guard
let start = position(from: beginningOfDocument, offset: range.lowerBound.encodedOffset),
let end = position(from: start, offset: length),
let txtRange = textRange(from: start, to: end)
else { return nil }
return selectionRects(for: txtRange).reduce(CGRect.null) { $0.union($1.rect) }
}
}
// return value will be Zero-based index of the paragraphs
// if the textView has no text, return -1
#objc func getParagraphIndex(in textView: UITextView) -> Int {
// this will make sure the the text container has updated
theTextView.layoutManager.ensureLayout(for: theTextView.textContainer)
// make sure we have some text
guard let str = theTextView.text else { return -1 }
// get the full range
let textRange = str.startIndex..<str.endIndex
// we want to enumerate by paragraphs
let opts:NSString.EnumerationOptions = .byParagraphs
var caretYPos = CGFloat(0)
if let selectedTextRange = theTextView.selectedTextRange {
caretYPos = theTextView.caretRect(for: selectedTextRange.start).origin.y + 4
}
var pIndex = -1
var i = 0
// loop through the paragraphs, comparing the caret Y position to the paragraph bounding rects
str.enumerateSubstrings(in: textRange, options: opts) {
(substring, substringRange, enclosingRange, b) in
// get the bounding rect for the sub-rects in each paragraph
if let boundRect = self.theTextView.boundingFrame(ofTextRange: substringRange) {
if caretYPos > boundRect.origin.y && caretYPos < boundRect.origin.y + boundRect.size.height {
pIndex = i
b = true
}
i += 1
}
}
return pIndex
}
Usage:
let paraIndex = getParagraphIndex(in myTextView)

Gradually and randomly visualize string

I am currently working on a simple program to gradually and randomly visualize a string in two iteration. Right now I have managed to get the first iteration but I'm not sure how to do the second one. If someone could give any example or advice I would be very grateful. My code looks like this:
let s = "Hello playground"
let factor = 0.25
let factor2 = 0.45
var n = s.filter({ $0 != " " }).count // # of non-space characters
var m = lrint(factor * Double(n)) // # of characters to display
let t = String(s.map { c -> Character in
if c == " " {
// Preserve space
return " "
} else if Int.random(in: 0..<n) < m {
// Replace
m -= 1
n -= 1
return c
} else {
// Keep
n -= 1
return "_"
}
})
print(t) // h_l__ _l_______d
To clarify, I want to use factor2 in the second iteration to print something that randomly add letters on top of t that looks something like this h_l_o pl_g_____d.
Replacing Characters
Starting from #MartinR's code, you should remember the indices that have been replaced. So, I am going to slightly change the code that replaces characters :
let s = "Hello playground"
let factor = 0.25
let factor2 = 0.45
var n = s.filter({ $0 != " " }).count // # of non-space characters
let nonSpaces = n
var m = lrint(factor * Double(n)) // # of characters to display
var indices = Array(s.indices)
var t = ""
for i in s.indices {
let c = s[i]
if c == " " {
// Preserve space
t.append(" ")
indices.removeAll(where: { $0 == i })
} else if Int.random(in: 0..<n) < m {
// Keep
m -= 1
n -= 1
t.append(c)
indices.removeAll(where: { $0 == i })
} else {
// Replace
n -= 1
t.append("_")
}
}
print(t) //For example: _e___ ______ou_d
Revealing Characters
In order to do that, we should calculate the number of characters that we want to reveal:
m = lrint((factor2 - factor) * Double(nonSpaces))
To pick three indices to reveal randomly, we shuffle indices and then replace the m first indices :
indices.shuffle()
var u = t
for i in 0..<m {
let index = indices[i]
u.replaceSubrange(index..<u.index(after: index), with: String(s[index]))
}
indices.removeSubrange(0..<m)
print(u) //For example: _e__o _l__g_ou_d
I wrote StringRevealer struct, that handle all revealing logic for you:
/// Hide all unicode letter characters as `_` symbol.
struct StringRevealer {
/// We need mapping between index of string character and his position in state array.
/// This struct represent one such record
private struct Symbol: Hashable {
let index: String.Index
let position: Int
}
private let originalString: String
private var currentState: [Character]
private let charactersCount: Int
private var revealed: Int
var revealedPercent: Double {
return Double(revealed) / Double(charactersCount)
}
private var unrevealedSymbols: Set<Symbol>
init(_ text: String) {
originalString = text
var state: [Character] = []
var symbols: [Symbol] = []
var count = 0
var index = originalString.startIndex
var i = 0
while index != originalString.endIndex {
let char = originalString[index]
if CharacterSet.letters.contains(char.unicodeScalars.first!) {
state.append("_")
symbols.append(Symbol(index: index, position: i))
count += 1
} else {
state.append(char)
}
index = originalString.index(after: index)
i += 1
}
currentState = state
charactersCount = count
revealed = 0
unrevealedSymbols = Set(symbols)
}
/// Current state of text. O(n) conplexity
func text() -> String {
return currentState.reduce(into: "") { $0.append($1) }
}
/// Reveal one random symbol in string
mutating func reveal() {
guard let symbol = unrevealedSymbols.randomElement() else { return }
unrevealedSymbols.remove(symbol)
currentState[symbol.position] = originalString[symbol.index]
revealed += 1
}
/// Reveal random symbols on string until `revealedPercent` > `percent`
mutating func reveal(until percent: Double) {
guard percent <= 1 else { return }
while revealedPercent < percent {
reveal()
}
}
}
var revealer = StringRevealer("Hello товарищ! 👋")
print(revealer.text())
print(revealer.revealedPercent)
for percent in [0.25, 0.45, 0.8] {
revealer.reveal(until: percent)
print(revealer.text())
print(revealer.revealedPercent)
}
It use CharacterSet.letters inside, so most of languages should be supported, emoji ignored and not-alphabetic characters as well.

Jaro Winkler distance in Objective-C or Swift

I need to do fuzzy comparison of a large number of strings and am looking at Jaro-Winkler which respects differences in the order of letters. Is anyone aware of a way to do this in Objective-C or Swift either using Jaro-Winkler or some method native to IOS?
Thanks for any recommendations or suggestions.
I took an inspiration in Apache Commons and rewritten it to Swift:
extension String {
static func jaroWinglerDistance(_ first: String, _ second: String) -> Double {
let longer = Array(first.count > second.count ? first : second)
let shorter = Array(first.count > second.count ? second : first)
let (numMatches, numTranspositions) = jaroWinklerData(longer: longer, shorter: shorter)
if numMatches == 0 {
return 0
}
let defaultScalingFactor = 0.1;
let percentageRoundValue = 100.0;
let jaro = [
numMatches / Double(first.count),
numMatches / Double(second.count),
(numMatches - numTranspositions) / numMatches
].reduce(0, +) / 3
let jaroWinkler: Double
if jaro < 0.7 {
jaroWinkler = jaro
} else {
let commonPrefixLength = Double(commonPrefix(first, second).count)
jaroWinkler = jaro + Swift.min(defaultScalingFactor, 1 / Double(longer.count)) * commonPrefixLength * (1 - jaro)
}
return round(jaroWinkler * percentageRoundValue) / percentageRoundValue
}
private static func commonPrefix(_ first: String, _ second: String) -> String{
return String(
zip(first, second)
.prefix { $0.0 == $0.1 }
.map { $0.0 }
)
}
private static func jaroWinklerData(
longer: Array<Character>,
shorter: Array<Character>
) -> (numMatches: Double, numTranspositions: Double) {
let window = Swift.max(longer.count / 2 - 1, 0)
var shorterMatchedChars: [Character] = []
var longerMatches = Array<Bool>(repeating: false, count: longer.count)
for (offset, shorterChar) in shorter.enumerated() {
let windowRange = Swift.max(offset - window, 0) ..< Swift.min(offset + window + 1, longer.count)
if let matchOffset = windowRange.first(where: { !longerMatches[$0] && shorterChar == longer[$0] }) {
shorterMatchedChars.append(shorterChar)
longerMatches[matchOffset] = true
}
}
let longerMatchedChars = longerMatches
.enumerated()
.filter { $0.element }
.map { longer[$0.offset] }
let numTranspositions: Int = zip(shorterMatchedChars, longerMatchedChars)
.lazy
.filter { $0.0 != $0.1 }
.count / 2
return (
numMatches: Double(shorterMatchedChars.count),
numTranspositions: Double(numTranspositions)
)
}
}
Tested by the examples found in the original code:
print(String.jaroWinglerDistance("", ""))
print(String.jaroWinglerDistance("", "a"))
print(String.jaroWinglerDistance("aaapppp", ""))
print(String.jaroWinglerDistance("frog", "fog"))
print(String.jaroWinglerDistance("fly", "ant"))
print(String.jaroWinglerDistance("elephant", "hippo"))
print(String.jaroWinglerDistance("hippo", "elephant"))
print(String.jaroWinglerDistance("hippo", "zzzzzzzz"))
print(String.jaroWinglerDistance("hello", "hallo"))
print(String.jaroWinglerDistance("ABC Corporation", "ABC Corp"))
print(String.jaroWinglerDistance("D N H Enterprises Inc", "D & H Enterprises, Inc."))
print(String.jaroWinglerDistance("My Gym Children's Fitness Center", "My Gym. Childrens Fitness"))
print(String.jaroWinglerDistance("PENNSYLVANIA", "PENNCISYLVNIA"))
I have also found another implementation of String similarity functions in github.

Swift - How to remove a decimal from a float if the decimal is equal to 0?

I'm displaying a distance with one decimal, and I would like to remove this decimal in case it is equal to 0 (ex: 1200.0Km), how could I do that in swift?
I'm displaying this number like this:
let distanceFloat: Float = (currentUser.distance! as NSString).floatValue
distanceLabel.text = String(format: "%.1f", distanceFloat) + "Km"
Swift 3/4:
var distanceFloat1: Float = 5.0
var distanceFloat2: Float = 5.540
var distanceFloat3: Float = 5.03
extension Float {
var clean: String {
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
}
}
print("Value \(distanceFloat1.clean)") // 5
print("Value \(distanceFloat2.clean)") // 5.54
print("Value \(distanceFloat3.clean)") // 5.03
Swift 2 (Original answer)
let distanceFloat: Float = (currentUser.distance! as NSString).floatValue
distanceLabel.text = String(format: distanceFloat == floor(distanceFloat) ? “%.0f" : "%.1f", distanceFloat) + "Km"
Or as an extension:
extension Float {
var clean: String {
return self % 1 == 0 ? String(format: "%.0f", self) : String(self)
}
}
Use NSNumberFormatter:
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 2
// Avoid not getting a zero on numbers lower than 1
// Eg: .5, .67, etc...
formatter.numberStyle = .decimal
let nums = [3.0, 5.1, 7.21, 9.311, 600.0, 0.5677, 0.6988]
for num in nums {
print(formatter.string(from: num as NSNumber) ?? "n/a")
}
Returns:
3
5.1
7.21
9.31
600
0.57
0.7
extension is the powerful way to do it.
Extension:
Code for Swift 2 (not Swift 3 or newer):
extension Float {
var cleanValue: String {
return self % 1 == 0 ? String(format: "%.0f", self) : String(self)
}
}
Usage:
var sampleValue: Float = 3.234
print(sampleValue.cleanValue)
3.234
sampleValue = 3.0
print(sampleValue.cleanValue)
3
sampleValue = 3
print(sampleValue.cleanValue)
3
Sample Playground file is here.
Update of accepted answer for swift 3:
extension Float {
var cleanValue: String {
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
}
}
usage would just be:
let someValue: Float = 3.0
print(someValue.cleanValue) //prints 3
To format it to String, follow this pattern
let aFloat: Float = 1.123
let aString: String = String(format: "%.0f", aFloat) // "1"
let aString: String = String(format: "%.1f", aFloat) // "1.1"
let aString: String = String(format: "%.2f", aFloat) // "1.12"
let aString: String = String(format: "%.3f", aFloat) // "1.123"
To cast it to Int, follow this pattern
let aInt: Int = Int(aFloat) // "1"
When you use String(format: initializer, Swift will automatically round the final digit as needed based on the following number.
You can use an extension as already mentioned, this solution is a little shorter though:
extension Float {
var shortValue: String {
return String(format: "%g", self)
}
}
Example usage:
var sample: Float = 3.234
print(sample.shortValue)
Swift 5
for Double it's same as #Frankie's answer for float
var dec: Double = 1.0
dec.clean // 1
for the extension
extension Double {
var clean: String {
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
}
}
Swift 5.5 makes it easy
Just use the new formatted() api with a default FloatingPointFormatStyle:
let values: [Double] = [1.0, 4.5, 100.0, 7]
for value in values {
print(value.formatted(FloatingPointFormatStyle()))
}
// prints "1, 4.5, 100, 7"
In Swift 4 try this.
extension CGFloat{
var cleanValue: String{
//return String(format: 1 == floor(self) ? "%.0f" : "%.2f", self)
return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(format: "%.2f", self)//
}
}
//How to use - if you enter more then two-character after (.)point, it's automatically cropping the last character and only display two characters after the point.
let strValue = "32.12"
print(\(CGFloat(strValue).cleanValue)
Formatting with maximum fraction digits, without trailing zeros
This scenario is good when a custom output precision is desired.
This solution seems roughly as fast as NumberFormatter + NSNumber solution from MirekE, but one benefit could be that we're avoiding NSObject here.
extension Double {
func string(maximumFractionDigits: Int = 2) -> String {
let s = String(format: "%.\(maximumFractionDigits)f", self)
var offset = -maximumFractionDigits - 1
for i in stride(from: 0, to: -maximumFractionDigits, by: -1) {
if s[s.index(s.endIndex, offsetBy: i - 1)] != "0" {
offset = i
break
}
}
return String(s[..<s.index(s.endIndex, offsetBy: offset)])
}
}
(works also with extension Float, but not the macOS-only type Float80)
Usage: myNumericValue.string(maximumFractionDigits: 2) or myNumericValue.string()
Output for maximumFractionDigits: 2:
1.0 → "1"
0.12 → "0.12"
0.012 → "0.01"
0.0012 → "0"
0.00012 → "0"
Simple :
Int(floor(myFloatValue))
NSNumberFormatter is your friend
let distanceFloat: Float = (currentUser.distance! as NSString).floatValue
let numberFormatter = NSNumberFormatter()
numberFormatter.positiveFormat = "###0.##"
let distance = numberFormatter.stringFromNumber(NSNumber(float: distanceFloat))!
distanceLabel.text = distance + " Km"
Here's the full code.
let numberA: Float = 123.456
let numberB: Float = 789.000
func displayNumber(number: Float) {
if number - Float(Int(number)) == 0 {
println("\(Int(number))")
} else {
println("\(number)")
}
}
displayNumber(numberA) // console output: 123.456
displayNumber(numberB) // console output: 789
Here's the most important line in-depth.
func displayNumber(number: Float) {
Strips the float's decimal digits with Int(number).
Returns the stripped number back to float to do an operation with Float(Int(number)).
Gets the decimal-digit value with number - Float(Int(number))
Checks the decimal-digit value is empty with if number - Float(Int(number)) == 0
The contents within the if and else statements doesn't need explaining.
This might be helpful too.
extension Float {
func cleanValue() -> String {
let intValue = Int(self)
if self == 0 {return "0"}
if self / Float (intValue) == 1 { return "\(intValue)" }
return "\(self)"
}
}
Usage:
let number:Float = 45.23230000
number.cleanValue()
Maybe stringByReplacingOccurrencesOfString could help you :)
let aFloat: Float = 1.000
let aString: String = String(format: "%.1f", aFloat) // "1.0"
let wantedString: String = aString.stringByReplacingOccurrencesOfString(".0", withString: "") // "1"

How to create a String with format?

I need to create a String with format which can convert Int, Int64, Double, etc types into String. Using Objective-C, I can do it by:
NSString *str = [NSString stringWithFormat:#"%d , %f, %ld, %#", INT_VALUE, FLOAT_VALUE, DOUBLE_VALUE, STRING_VALUE];
How to do same but in Swift?
I think this could help you:
import Foundation
let timeNow = time(nil)
let aStr = String(format: "%#%x", "timeNow in hex: ", timeNow)
print(aStr)
Example result:
timeNow in hex: 5cdc9c8d
nothing special
let str = NSString(format:"%d , %f, %ld, %#", INT_VALUE, FLOAT_VALUE, LONG_VALUE, STRING_VALUE)
let str = "\(INT_VALUE), \(FLOAT_VALUE), \(DOUBLE_VALUE), \(STRING_VALUE)"
Update: I wrote this answer before Swift had String(format:) added to it's API. Use the method given by the top answer.
No NSString required!
String(format: "Value: %3.2f\tResult: %3.2f", arguments: [2.7, 99.8])
or
String(format:"Value: %3.2f\tResult: %3.2f", 2.7, 99.8)
I would argue that both
let str = String(format:"%d, %f, %ld", INT_VALUE, FLOAT_VALUE, DOUBLE_VALUE)
and
let str = "\(INT_VALUE), \(FLOAT_VALUE), \(DOUBLE_VALUE)"
are both acceptable since the user asked about formatting and both cases fit what they are asking for:
I need to create a string with format which can convert int, long, double etc. types into string.
Obviously the former allows finer control over the formatting than the latter, but that does not mean the latter is not an acceptable answer.
First read Official documentation for Swift language.
Answer should be
var str = "\(INT_VALUE) , \(FLOAT_VALUE) , \(DOUBLE_VALUE), \(STRING_VALUE)"
println(str)
Here
1) Any floating point value by default double
EX.
var myVal = 5.2 // its double by default;
-> If you want to display floating point value then you need to explicitly define such like a
EX.
var myVal:Float = 5.2 // now its float value;
This is far more clear.
let INT_VALUE=80
let FLOAT_VALUE:Double= 80.9999
let doubleValue=65.0
let DOUBLE_VALUE:Double= 65.56
let STRING_VALUE="Hello"
let str = NSString(format:"%d , %f, %ld, %#", INT_VALUE, FLOAT_VALUE, DOUBLE_VALUE, STRING_VALUE);
println(str);
The accepted answer is definitely the best general solution for this (i.e., just use the String(format:_:) method from Foundation) but...
If you are running Swift ≥ 5, you can leverage the new StringInterpolationProtocol protocol to give yourself some very nice syntax sugar for common string formatting use cases in your app.
Here is how the official documentation summarizes this new protocol:
Represents the contents of a string literal with interpolations while it’s being built up.
Some quick examples:
extension String.StringInterpolation {
/// Quick formatting for *floating point* values.
mutating func appendInterpolation(float: Double, decimals: UInt = 2) {
let floatDescription = String(format: "%.\(decimals)f%", float)
appendLiteral(floatDescription)
}
/// Quick formatting for *hexadecimal* values.
mutating func appendInterpolation(hex: Int) {
let hexDescription = String(format: "0x%X", hex)
appendLiteral(hexDescription)
}
/// Quick formatting for *percents*.
mutating func appendInterpolation(percent: Double, decimals: UInt = 2) {
let percentDescription = String(format: "%.\(decimals)f%%", percent * 100)
appendLiteral(percentDescription)
}
/// Formats the *elapsed time* since the specified start time.
mutating func appendInterpolation(timeSince startTime: TimeInterval, decimals: UInt = 2) {
let elapsedTime = CACurrentMediaTime() - startTime
let elapsedTimeDescription = String(format: "%.\(decimals)fs", elapsedTime)
appendLiteral(elapsedTimeDescription)
}
}
which could be used as:
let number = 1.2345
"Float: \(float: number)" // "Float: 1.23"
"Float: \(float: number, decimals: 1)" // "Float: 1.2"
let integer = 255
"Hex: \(hex: integer)" // "Hex: 0xFF"
let rate = 0.15
"Percent: \(percent: rate)" // "Percent: 15.00%"
"Percent: \(percent: rate, decimals: 0)" // "Percent: 15%"
let startTime = CACurrentMediaTime()
Thread.sleep(forTimeInterval: 2.8)
"∆t was \(timeSince: startTime)" // "∆t was 2.80s"
"∆t was \(timeSince: startTime, decimals: 0)" // "∆t was 3s"
This was introduced by SE-0228, so please be sure to read the original proposal for a deeper understanding of this new feature. Finally, the protocol documentation is helpful as well.
I know a lot's of time has passed since this publish, but I've fallen in a similar situation and create a simples class to simplify my life.
public struct StringMaskFormatter {
public var pattern : String = ""
public var replecementChar : Character = "*"
public var allowNumbers : Bool = true
public var allowText : Bool = false
public init(pattern:String, replecementChar:Character="*", allowNumbers:Bool=true, allowText:Bool=true)
{
self.pattern = pattern
self.replecementChar = replecementChar
self.allowNumbers = allowNumbers
self.allowText = allowText
}
private func prepareString(string:String) -> String {
var charSet : NSCharacterSet!
if allowText && allowNumbers {
charSet = NSCharacterSet.alphanumericCharacterSet().invertedSet
}
else if allowText {
charSet = NSCharacterSet.letterCharacterSet().invertedSet
}
else if allowNumbers {
charSet = NSCharacterSet.decimalDigitCharacterSet().invertedSet
}
let result = string.componentsSeparatedByCharactersInSet(charSet)
return result.joinWithSeparator("")
}
public func createFormattedStringFrom(text:String) -> String
{
var resultString = ""
if text.characters.count > 0 && pattern.characters.count > 0
{
var finalText = ""
var stop = false
let tempString = prepareString(text)
var formatIndex = pattern.startIndex
var tempIndex = tempString.startIndex
while !stop
{
let formattingPatternRange = formatIndex ..< formatIndex.advancedBy(1)
if pattern.substringWithRange(formattingPatternRange) != String(replecementChar) {
finalText = finalText.stringByAppendingString(pattern.substringWithRange(formattingPatternRange))
}
else if tempString.characters.count > 0 {
let pureStringRange = tempIndex ..< tempIndex.advancedBy(1)
finalText = finalText.stringByAppendingString(tempString.substringWithRange(pureStringRange))
tempIndex = tempIndex.advancedBy(1)
}
formatIndex = formatIndex.advancedBy(1)
if formatIndex >= pattern.endIndex || tempIndex >= tempString.endIndex {
stop = true
}
resultString = finalText
}
}
return resultString
}
}
The follow link send to the complete source code:
https://gist.github.com/dedeexe/d9a43894081317e7c418b96d1d081b25
This solution was base on this article:
http://vojtastavik.com/2015/03/29/real-time-formatting-in-uitextfield-swift-basics/
There is a simple solution I learned with "We <3 Swift" if you can't either import Foundation, use round() and/or does not want a String:
var number = 31.726354765
var intNumber = Int(number * 1000.0)
var roundedNumber = Double(intNumber) / 1000.0
result: 31.726
Use this following code:
let intVal=56
let floatval:Double=56.897898
let doubleValue=89.0
let explicitDaouble:Double=89.56
let stringValue:"Hello"
let stringValue="String:\(stringValue) Integer:\(intVal) Float:\(floatval) Double:\(doubleValue) ExplicitDouble:\(explicitDaouble) "
The beauty of String(format:) is that you can save a formatting string and then reuse it later in dozen of places. It also can be localized in this single place. Where as in case of the interpolation approach you must write it again and again.
Simple functionality is not included in Swift, expected because it's included in other languages, can often be quickly coded for reuse. Pro tip for programmers to create a bag of tricks file that contains all this reuse code.
So from my bag of tricks we first need string multiplication for use in indentation.
#inlinable func * (string: String, scalar: Int) -> String {
let array = [String](repeating: string, count: scalar)
return array.joined(separator: "")
}
and then the code to add commas.
extension Int {
#inlinable var withCommas:String {
var i = self
var retValue:[String] = []
while i >= 1000 {
retValue.append(String(format:"%03d",i%1000))
i /= 1000
}
retValue.append("\(i)")
return retValue.reversed().joined(separator: ",")
}
#inlinable func withCommas(_ count:Int = 0) -> String {
let retValue = self.withCommas
let indentation = count - retValue.count
let indent:String = indentation >= 0 ? " " * indentation : ""
return indent + retValue
}
}
I just wrote this last function so I could get the columns to line up.
The #inlinable is great because it takes small functions and reduces their functionality so they run faster.
You can use either the variable version or, to get a fixed column, use the function version. Lengths set less than the needed columns will just expand the field.
Now you have something that is pure Swift and does not rely on some old objective C routine for NSString.
Since String(format: "%s" ...) is crashing at run time, here is code to allow write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib:
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}
Success to try it:
var letters:NSString = "abcdefghijkl"
var strRendom = NSMutableString.stringWithCapacity(strlength)
for var i=0; i<strlength; i++ {
let rndString = Int(arc4random() % 12)
//let strlk = NSString(format: <#NSString#>, <#CVarArg[]#>)
let strlk = NSString(format: "%c", letters.characterAtIndex(rndString))
strRendom.appendString(String(strlk))
}

Resources