function to find all NSRanges for a string in string in swift - swift

I would like to have all CG letters pairs in red color in DNA sequences (string). I can do it by using NSMutableAttributedString.
For this I need NSRanges of all CG positions and I have tried to make the function below but in the line
indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn)
rangeOfString with range cannot be invoked with an argument list of type (String, options: Int, range: NSRange). Where is a mistake ? What i did wrong ?
func searchForNSRangesOfStringInString(stringOut: String, stringIn: String) -> [NSRange]
{
var arrayOfindexesOfStringIn = [NSRange]()
if stringIn.characters.count > 1
{
var rangeNS = (stringOut as NSString).rangeOfString(stringIn)
if rangeNS.location != NSNotFound
{
let endIndexOfStringOut = (stringOut as NSString).length - 1
var indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn)
arrayOfindexesOfStringIn = [indexesOfStringIn]
var lastIndexOfStringIn = (stringOut as NSString).rangeOfString(stringIn).location
var rangeForIndexStringIn = NSRange(lastIndexOfStringIn...endIndexOfStringOut)
while indexesOfStringIn.location != NSNotFound
{
indexesOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn)
if indexesOfStringIn.location != NSNotFound
{
arrayOfindexesOfStringIn.append(indexesOfStringIn)
lastIndexOfStringIn = (stringOut as NSString).rangeOfString(stringIn, options: 0, range: rangeForIndexStringIn).location
rangeForIndexStringIn = NSRange(lastIndexOfStringIn...endIndexOfStringOut)
}
}
}
}
return arrayOfindexesOfStringIn
}

As suggested by uchuugaka it is much easier to use NSRegularExpression to return an array of the matches ranges in a string as follow:
extension String {
func findOccurencesOf(text text:String) -> [NSRange] {
return !text.isEmpty ? try! NSRegularExpression(pattern: text, options: []).matchesInString(self, options: [], range: NSRange(0..<characters.count)).map{ $0.range } : []
}
}
let str = "CGCGCGCGCG"
let ranges = str.findOccurencesOf(text: "CG")
print(ranges.count) // 5
Just add a control event for your textField Editing changed and loop through the ranges as follow:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: "coloring:", forControlEvents: UIControlEvents.EditingChanged)
textField.becomeFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func coloring(sender:UITextField) {
let attText = NSMutableAttributedString(string: sender.text ?? "")
let ranges = sender.text!.uppercaseString.findOccurencesOf(text: "CG")
for range in ranges {
attText.addAttributes([NSForegroundColorAttributeName : UIColor.redColor()], range: range)
}
sender.attributedText = attText
}
}
extension String {
func findOccurencesOf(text text:String) -> [NSRange] {
return !text.isEmpty ? try! NSRegularExpression(pattern: text, options: []).matchesInString(self, options: [], range: NSRange(0..<characters.count)).map{ $0.range } : []
}
}

Related

How to check if text is underlined

I am struggling to determine if some selected text in a UITextView is underlined. I can quite easily check for bold, italics etc with the following code:
let isItalic = textView.font!.fontDescriptor.symbolicTraits.contains(.traitItalic)
However, I can't figure out how to check for underline?
I have just created a sample project and I think you could do something like the following:
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let attrText1 = NSMutableAttributedString(string: "TestTest", attributes: [.foregroundColor : UIColor.systemTeal, .underlineStyle: NSUnderlineStyle.single.rawValue])
let attrText2 = NSAttributedString(string: " - not underlined", attributes: [.foregroundColor : UIColor.red])
attrText1.append(attrText2)
textView.attributedText = attrText1
}
func isTextUnderlined(attrText: NSAttributedString?, in range: NSRange) -> Bool {
guard let attrText = attrText else { return false }
var isUnderlined = false
attrText.enumerateAttributes(in: range, options: []) { (dict, range, value) in
if dict.keys.contains(.underlineStyle) {
isUnderlined = true
}
}
return isUnderlined
}
#IBAction func checkButtonDidTap(_ sender: UIButton) {
print(isTextUnderlined(attrText: textView.attributedText, in: textView.selectedRange))
}
}
Create an extension to get the selectedRange as NSRange:
extension UITextInput {
var selectedRange: NSRange? {
guard let range = selectedTextRange else { return nil }
let location = offset(from: beginningOfDocument, to: range.start)
let length = offset(from: range.start, to: range.end)
return NSRange(location: location, length: length)
}
}
I believe underline is not part of the font traits, it must rather be an attribute to the text. You might find the answer to this question useful. I hope it helps you! Enumerate over a Mutable Attributed String (Underline Button)
func checkForUnderline(){
let allWords = self.testView.text.split(separator: " ")
for word in allWords {
let result = self.isLabelFontUnderlined(textView: self.testView,
subString: word as NSString)
if(result == true){
print(word+" is underlined")
}else{
print(word+" is not underlined")
}
}
}
func isLabelFontUnderlined (textView: UITextView, subString:
NSString) -> Bool {
let nsRange = NSString(string: textView.text).range(of: subString as
String, options: String.CompareOptions.caseInsensitive)
if nsRange.location != NSNotFound {
return self.isLabelFontUnderlined(textView: textView,
forRange: nsRange)
}
return false
}
func isLabelFontUnderlined (textView: UITextView, forRange: NSRange) ->
Bool{
let attributedText = testView.attributedText!
var isRangeUnderline = false
attributedText.enumerateAttributes(in: forRange,
options:.longestEffectiveRangeNotRequired) { (dict, range, value) in
if dict.keys.contains(.underlineStyle) {
if (dict[.underlineStyle] as! Int == 1){
isRangeUnderline = true
} else{
isRangeUnderline = false
}
}else{
isRangeUnderline = false
}
}
return isRangeUnderline
}

Change a specific words color in a UITextView

What I'm Trying
Code
func textViewDidChange(_ textView: UITextView) {
var output = " " + textView.text
var outArray = output.components(separatedBy: " ").dropFirst()
for W in outArray {
if W == "//" {
textView.textColor = UIColor.lightGray
}
}
}
Problem
The problem I have is that I am trying to change the color of only a specific words (such as "//" would be red and "let" would be green) in the UITextView, so I was wondering how you might do that.
Try this code
import UIKit
extension String {
func getRanges(of string: String) -> [NSRange] {
var ranges:[NSRange] = []
if contains(string) {
let words = self.components(separatedBy: " ")
var position:Int = 0
for word in words {
if word.lowercased() == string.lowercased() {
let startIndex = position
let endIndex = word.count
let range = NSMakeRange(startIndex, endIndex)
ranges.append(range)
}
position += (word.count + 1)
}
}
return ranges
}
func setColorToChar(_ chars: [String] , color: [UIColor]) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(string: self)
if chars.count != color.count {
fatalError("Colors are not added correctly")
}
// let ranges = getRanges(of: char)
for i in 0..<chars.count {
let ranges = getRanges(of: chars[i])
for range in ranges {
attributedString.addAttributes([NSForegroundColorAttributeName: color[i]], range: range)
}
}
return attributedString
}
}
class ViewController: UIViewController,UITextViewDelegate {
#IBOutlet weak var myTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
myTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textView.attributedText = textView.text.setColorToChar(["//","let"], color: [.red,.green])
}
}
Result screenshot
You can do this easily by making extension for UITextView
extension UITextView {
func halfTextColorChange (fullText : String , changeText : String ) {
let strNumber: NSString = fullText as NSString
let range = (strNumber).range(of: changeText)
let attribute = NSMutableAttributedString.init(string: fullText)
attribute.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor(red: 76/255, green: 76/255, blue:76/255, alpha: 1.0) , range: range)
self.attributedText = attribute
}
}
Usage
txtView.halfTextColorChange(fullText: "ABCXYZ", changeText: "XYZ")

How to add a grouping separator while user is typing a number into UITextField?

I am doing a currency type UITextField. The behavior I need is like this:
I managed to create the decimal part. But I have problems adding in the grouping separators for thousands. How do I group the integer part here?
Here is the code so far:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let DIGITS = ["0","1","2","3","4","5","6","7","8","9","0"]
let DECIMAL_SEPERATOR = ","
let THOUSAND_SEPERATOR = "."
let DECIMAL_DIGITS = 2
let DECIMAL_DIGITS_DEFAULT_STRING = "00"
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.text = "0\(DECIMAL_SEPERATOR)\(DECIMAL_DIGITS_DEFAULT_STRING)"
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Textfield
func textFieldTriggerDone(){
if !textField.text!.containsString(DECIMAL_SEPERATOR){
textField.text = textField.text! + DECIMAL_SEPERATOR + DECIMAL_DIGITS_DEFAULT_STRING
}
print("Add missing parts here")
}
func textFieldTriggerDecimalTyped(){
textField.text = textField.text! + DECIMAL_SEPERATOR
}
func textFieldShouldAddNumber(text: String, range: NSRange, replacement: String, dots: Int) -> Bool{
let nsstring = NSString(string: text)
let decimalRange = nsstring.rangeOfString(DECIMAL_SEPERATOR)
if range.location > decimalRange.location{
let parts = text.componentsSeparatedByString(DECIMAL_SEPERATOR)
if parts.count > 1{
if parts[1].characters.count > (DECIMAL_DIGITS-1){
return false
}
}
}else if range.location < decimalRange.location{
if textField.text!.characters.count == 1 && textField.text! == "0"{
textField.text = replacement
return false
}else{
let insertIndex = text.startIndex.advancedBy(range.location-dots)
var finalText = text
if replacement.characters.count > 0 {
finalText.insert(replacement.characters.first!, atIndex: insertIndex)
textField.text = finalText
let begin = textField.beginningOfDocument
let pos = textField.positionFromPosition(begin, offset: (range.location+1))
let cursorpos = textField.textRangeFromPosition(pos!, toPosition: pos!)
textField.selectedTextRange = cursorpos
//textFieldAddThousandSeperators()
return false
}
}
}
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
textFieldTriggerDone()
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let dots = textField.text!.occurancesOf(THOUSAND_SEPERATOR)
if string == ""{
if textField.text!.characters.count > 1{
return true
}else{
textField.text! = "0"
return false
}
}
if range.location == 0 && string == "0"{
return false
}
if DIGITS.contains(string){
return textFieldShouldAddNumber(textField.text!, range: range, replacement: string, dots: dots)
}
if string == DECIMAL_SEPERATOR || string == THOUSAND_SEPERATOR{
if textField.text!.containsString(DECIMAL_SEPERATOR){
return false
}
textFieldTriggerDecimalTyped()
return false
}
return false
}
}
Check out this answer:
https://stackoverflow.com/a/40469426/6863743
code:
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
To get it to convert back to something you can do calculations with, you can just write a function to take out the "," and "." and so on.
Hope this helps a bit, it's the best solution I've found for currency formatting.

Swift 3 - how do I add clickable links to another view controller in the body of a TextView? [duplicate]

I am trying to display an attributed string in a UITextview with clickable links. I've created a simple test project to see where I'm going wrong and still can't figure it out. I've tried enabling user interaction and setting the shouldInteractWithURLs delegate method, but it's still not working. Here's my code (for a view controller that only contains a textview)
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let string = "Google"
let linkString = NSMutableAttributedString(string: string)
linkString.addAttribute(NSLinkAttributeName, value: NSURL(string: "https://www.google.com")!, range: NSMakeRange(0, string.characters.count))
linkString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue", size: 25.0)!, range: NSMakeRange(0, string.characters.count))
textView.attributedText = linkString
textView.delegate = self
textView.selectable = true
textView.userInteractionEnabled = true
}
And here are the delegate methods I've implemented:
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
return false
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
return true
}
This still isn't working. I've searched on this topic and nothing has helped yet. Thanks so much in advance.
Just select the UITextView in your storyboard and go to "Show Attributes inspector" and select selectable and links. As the image below shows. Make sure Editable is unchecked.
For swift3.0
override func viewDidLoad() {
super.viewDidLoad()
let linkAttributes = [
NSLinkAttributeName: NSURL(string: "http://stalwartitsolution.co.in/luminutri_flow/terms-condition")!
] as [String : Any]
let attributedString = NSMutableAttributedString(string: "Please tick box to confirm you agree to our Terms & Conditions, Privacy Policy, Disclaimer. ")
attributedString.setAttributes(linkAttributes, range: NSMakeRange(44, 18))
attributedString.addAttribute(NSUnderlineStyleAttributeName, value: NSNumber(value: 1), range: NSMakeRange(44, 18))
textview.delegate = self
textview.attributedText = attributedString
textview.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.red]
textview.textColor = UIColor.white
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true
}
Swift 3 iOS 10: Here's Clickable extended UITextView that detect websites inside the textview automatically as long as the link start with www. for example: www.exmaple.com if it exist anywhere in the text will be clickable. Here's the class:
import Foundation
import UIKit
public class ClickableTextView:UITextView{
var tap:UITapGestureRecognizer!
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
print("init")
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
// Add tap gesture recognizer to Text View
tap = UITapGestureRecognizer(target: self, action: #selector(self.myMethodToHandleTap(sender:)))
// tap.delegate = self
self.addGestureRecognizer(tap)
}
func myMethodToHandleTap(sender: UITapGestureRecognizer){
let myTextView = sender.view as! UITextView
let layoutManager = myTextView.layoutManager
// location of tap in myTextView coordinates and taking the inset into account
var location = sender.location(in: myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length {
let orgString = myTextView.attributedText.string
//Find the WWW
var didFind = false
var count:Int = characterIndex
while(count > 2 && didFind == false){
let myRange = NSRange(location: count-1, length: 2)
let substring = (orgString as NSString).substring(with: myRange)
// print(substring,count)
if substring == " w" || (substring == "w." && count == 3){
didFind = true
// print("Did find",count)
var count2 = count
while(count2 < orgString.characters.count){
let myRange = NSRange(location: count2 - 1, length: 2)
let substring = (orgString as NSString).substring(with: myRange)
// print("Did 2",count2,substring)
count2 += 1
//If it was at the end of textView
if count2 == orgString.characters.count {
let length = orgString.characters.count - count
let myRange = NSRange(location: count, length: length)
let substring = (orgString as NSString).substring(with: myRange)
openLink(link: substring)
print("It's a Link",substring)
return
}
//If it's in the middle
if substring.hasSuffix(" "){
let length = count2 - count
let myRange = NSRange(location: count, length: length)
let substring = (orgString as NSString).substring(with: myRange)
openLink(link: substring)
print("It's a Link",substring)
return
}
}
return
}
if substring.hasPrefix(" "){
print("Not a link")
return
}
count -= 1
}
}
}
func openLink(link:String){
if let checkURL = URL(string: "http://\(link.replacingOccurrences(of: " ", with: ""))") {
if UIApplication.shared.canOpenURL(checkURL) {
UIApplication.shared.open(checkURL, options: [:], completionHandler: nil)
print("url successfully opened")
}
} else {
print("invalid url")
}
}
public override func didMoveToWindow() {
if self.window == nil{
self.removeGestureRecognizer(tap)
print("ClickableTextView View removed from")
}
}
}

How to format currency in text field [duplicate]

I have a number let’s say 0.00.
When the user taps 1. We should have 0.01
When the user taps 2. We should display 0.12
When the user taps 3. We should display 1.23
When the user taps 4. We should display 12.34
How can I do that with Swift?
For Swift 3. Input currency format on a text field (from right to left)
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}
#objc func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
You can create a currency text field subclassing UITextField. Add a target for UIControlEvents .editingChanged. Add a selector method to filter the digits from your textfield string. After filtering all non digits from your string you can format again your number using NumberFormatter as follow:
Xcode 11.5 • Swift 5.2 or later
import UIKit
class CurrencyField: UITextField {
var decimal: Decimal { string.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
var maximum: Decimal = 999_999_999.99
private var lastValue: String?
var locale: Locale = .current {
didSet {
Formatter.currency.locale = locale
sendActions(for: .editingChanged)
}
}
override func willMove(toSuperview newSuperview: UIView?) {
// you can make it a fixed locale currency if needed
// self.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
Formatter.currency.locale = locale
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .right
sendActions(for: .editingChanged)
}
override func deleteBackward() {
text = string.digits.dropLast().string
// manually send the editingChanged event
sendActions(for: .editingChanged)
}
#objc func editingChanged() {
guard decimal <= maximum else {
text = lastValue
return
}
text = decimal.currency
lastValue = text
}
}
extension CurrencyField {
var doubleValue: Double { (decimal as NSDecimalNumber).doubleValue }
}
extension UITextField {
var string: String { text ?? "" }
}
extension NumberFormatter {
convenience init(numberStyle: Style) {
self.init()
self.numberStyle = numberStyle
}
}
private extension Formatter {
static let currency: NumberFormatter = .init(numberStyle: .currency)
}
extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self { filter (\.isWholeNumber) }
}
extension String {
var decimal: Decimal { Decimal(string: digits) ?? 0 }
}
extension Decimal {
var currency: String { Formatter.currency.string(for: self) ?? "" }
}
extension LosslessStringConvertible {
var string: String { .init(self) }
}
View Controller
class ViewController: UIViewController {
#IBOutlet weak var currencyField: CurrencyField!
override func viewDidLoad() {
super.viewDidLoad()
currencyField.addTarget(self, action: #selector(currencyFieldChanged), for: .editingChanged)
currencyField.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
}
#objc func currencyFieldChanged() {
print("currencyField:",currencyField.text!)
print("decimal:", currencyField.decimal)
print("doubleValue:",(currencyField.decimal as NSDecimalNumber).doubleValue, terminator: "\n\n")
}
}
Sample project
SwiftUI version of this post here
I started with Leo Dabus' answer (which didn't work out of the box for me) and in the process of trying to simplify and make it work ended up with this, which I think is pretty lean & clean if I do say so myself 😎
class CurrencyTextField: UITextField {
/// The numbers that have been entered in the text field
private var enteredNumbers = ""
private var didBackspace = false
var locale: Locale = .current
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
override func deleteBackward() {
enteredNumbers = String(enteredNumbers.dropLast())
text = enteredNumbers.asCurrency(locale: locale)
// Call super so that the .editingChanged event gets fired, but we need to handle it differently, so we set the `didBackspace` flag first
didBackspace = true
super.deleteBackward()
}
#objc func editingChanged() {
defer {
didBackspace = false
text = enteredNumbers.asCurrency(locale: locale)
}
guard didBackspace == false else { return }
if let lastEnteredCharacter = text?.last, lastEnteredCharacter.isNumber {
enteredNumbers.append(lastEnteredCharacter)
}
}
}
private extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()
}
private extension String {
func asCurrency(locale: Locale) -> String? {
Formatter.currency.locale = locale
if self.isEmpty {
return Formatter.currency.string(from: NSNumber(value: 0))
} else {
return Formatter.currency.string(from: NSNumber(value: (Double(self) ?? 0) / 100))
}
}
}
Try this piece of code:
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(s:String) {
if count(fraction) < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if count(fraction) == 0 { return "00" }
if count(fraction) == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
val.stringVal
val.enter("2")
val.stringVal
val.enter("3")
val.stringVal
val.enter("4")
val.stringVal
My final code thanks for your help
extension Double {
var twoDigits: Double {
let nf = NSNumberFormatter()
nf.numberStyle = NSNumberFormatterStyle.DecimalStyle
nf.minimumFractionDigits = 2
nf.maximumFractionDigits = 2
return self
}
}
var cleanText:String!
let number:String = sender.currentTitle as String!
if(amountDisplay.text != nil)
{
cleanText = String(Array(amountDisplay.text!).map{String($0)}.filter{ $0.toInt() != nil }.map{Character($0)} ) as String
cleanText = cleanText + number
}else{
cleanText = number
}
amount = (Double(cleanText.toInt()!) / 100).twoDigits
formatter.locale = NSLocale(localeIdentifier: currencies[current_currency_index])
amountDisplay.text = "\(formatter.stringFromNumber(amount!)!)"
Here is a code for swift 2
#IBOutlet weak var txtAmount: UITextField!
//MARK: - UITextField Delegate -
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if string.characters.count == 0 {
return true
}
let userEnteredString = textField.text ?? ""
var newString = (userEnteredString as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
newString = newString.stringByReplacingOccurrencesOfString(".", withString: "")
let centAmount : NSInteger = newString.integerValue
let amount = (Double(centAmount) / 100.0)
if newString.length < 16 {
let str = String(format: "%0.2f", arguments: [amount])
txtAmount.text = str
}
return false //return false for exact out put
}
Note : Connect delegate for textField from storyboard or programatically
Just for fun: copied Thomas's answer (full credits -and points- to him please) into a file to run as a Swift 4.1 script (with minor fixes):
dotnum.swift:
#!/usr/bin/swift
struct DotNum {
private var fraction:String = ""
private var intval:String = ""
init() {}
mutating func enter(_ s:String) {
if fraction.count < 2 {
fraction = s + fraction
} else {
intval = s + intval
}
}
private var sFract:String {
if fraction.count == 0 { return "00" }
if fraction.count == 1 { return "0\(fraction)" }
return fraction
}
var stringVal:String {
if intval == "" { return "0.\(sFract)" }
return "\(intval).\(sFract)"
}
}
var val = DotNum()
val.enter("1")
print(val.stringVal)
val.enter("2")
print(val.stringVal)
val.enter("3")
print(val.stringVal)
val.enter("4")
print(val.stringVal)
Then run it in a terminal:
$ chmod +x dotnum.swift
$ ./dotnum.swift
0.01
0.21
3.21
43.21
Thanks to everyone here. From all the answers here I managed to come out with mine.
First I set up the initial value of the textField to be:
private func commonInit() {
amountTextField.text = "0.00"
}
Then I use the UITextFieldDelegate to get the input value and the current textview.text:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Need to check if the textfield.text can be evaluated as number or not before passing it to the function
//Get the current text value, and current user input and pass it to the
let formattedAmount = formatAmount(oldAmount: textField.text, userInput: string)
textField.text = formattedAmount
return false
}
Here go my private function to format the number to move from right to left:
private func formatAmount(currentText: String, userInput: String) -> String {
let amount = currentText.components(separatedBy: ".")
var intValue: String = amount[0]
var decimalValue: String = amount[1]
//backspace registered, need to move the number to the right
if userInput.isEmpty {
decimalValue.remove(at: decimalValue.index(before: decimalValue.endIndex))
decimalValue = intValue.last!.string + decimalValue
intValue.remove(at: intValue.index(before: intValue.endIndex))
if intValue.isEmpty {
intValue = "0"
}
} else {
//Need to consider if user paste value
if userInput.count > 2 {
decimalValue = String(userInput.suffix(2))
intValue = String(userInput.dropLast(2))
} else {
decimalValue = rmAmount[1] + userInput
//Add to int value (move to the right)
intValue = intValue + decimalValue.first!.string
if Int(intValue) == 0 {
intValue = "0" //00 -> 0
} else if intValue.first == "0" {
//remove 0 from at the first position in intValue
intValue.remove(at: intValue.startIndex) //01 -> 1
}
//Remove tenth place from decimal value since it goes to Int already
decimalValue.remove(at: decimalValue.startIndex)
}
}
return intValue + "." + decimalValue
}
This is basically it. Other extra implementations can be added by your own initiatives. Let me know if there is any problem with my implementation.
PS: This is of course only works for certain currency only, in my case, my apps is set up only for that local so thats why I use this way.
After a lot of trial and error with the suggested answers, I found a pretty straight forward solution:
The setup for the textField needs to be called in your view's setup.
In the switch statement, if the user puts in a number between 0 and 9, the number is added to the previous string value. The default case covers the backspace button and removes the last character from the string.
The locale for the numberFormatter is set to current, so it works with different currencies.
func setupTextField() {
textField.delegate = self
textField.tintColor = .clear
textField.keyboardType = .numberPad
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
setFormattedAmount(string)
return false
}
private func setFormattedAmount(_ string: String) {
switch string {
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
amountString = amountString + string
default:
if amountString.count > 0 {
amountString.removeLast()
}
}
let amount = (NSString(string: amountString).doubleValue) / 100
textField.text = formatAmount(amount)
}
private func formatAmount(_ amount: Double) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
if let amount = formatter.string(from: NSNumber(value: amount)) {
return amount
}
return ""
}