# Swift for Xojo Developers - Part 10 : String Extension

**URL:** <https://ifnotnil.com/t/swift-for-xojo-developers-part-10-string-extension/143>\
**Category:** Swift\
**Tags:** swift, tutorials\
**Created:** [25 February 2020 20:56 UTC](https://ifnotnil.com/t/swift-for-xojo-developers-part-10-string-extension/143 "2020-02-25T20:56:15Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![DaveS](https://ifnotnil.com/user_avatar/ifnotnil.com/daves/32/79_2.png) [@DaveS](https://ifnotnil.com/u/DaveS)\
**Post date:** [25 February 2020 20:56 UTC](https://ifnotnil.com/t/swift-for-xojo-developers-part-10-string-extension/143/1 "2020-02-25T20:56:15Z")

</div>

This libary is not a class, but an extension to the Swift String Datatype. It creates string functions with the more familiar syntax.

The extensions add features using this syntax

```swift
returnValue = StringValue.function([arg])

```

in addition there are function versions of these with a syntax of

```swift
returnValue = function(StringValue[,arg])

```

These like all the ones I have already posted, and most of what I will post in the future follow a syntax similar to Xojo API 1.0

```swift
//
// libSTRINGS.swift
// rdsBASIC
//
// Created by Dave Sisemore on 04-Aug-2016
// Updated for Swift 4.1 on 22-Jul-2018
// Updated for Swift 5.1 on 17-Feb-2020
// Copyright © 2018 Dave Sisemore. All rights reserved.
//

import UIKit
import Foundation

extension String {
    /// returns a string containing ONLY numeric digits from the source string
    /// - example
    /// var s="ABC123def456xyz789"
    /// var t=s.onlyDigits()
    /// t="123456789"
    public func onlyDigits() -> String {
        let filtredUnicodeScalars = unicodeScalars.filter{CharacterSet.decimalDigits.contains($0)}
        return String(String.UnicodeScalarView(filtredUnicodeScalars))
    }

    /// Returns the integer code point for the first character in the string using the character's encoding.
    /// result = stringVariable.Asc()
    public func Asc() -> Int { return AscAt(self,Index:1) }

    /// Returns the number of characters in the specified string.
    /// result=stringVariable.Len()
    public func Len() -> Int { return self.count }

    /// Returns a portion of a string. The first character is numbered 1.
    /// result=stringVariable.Mid(start [,length])
    public func Mid( _ start:Int,_ length:Int=(-1)) -> String {
        let strSize : Int = self.count
        var mStart : Int = start
        var mLen : Int = length
        if mStart <= 0 { return "" }
        if (mLen < 1) || (mStart+mLen>strSize) { mLen = strSize-mStart+1 }
        mStart=mStart-1 // Swift strings are 0 based
        return (self as NSString).substring( with: NSRange(location: mStart, length: mLen ) )
    }

    /// Returns the first n characters in a source string.
    /// result=stringVariable.Left(count)
    public func Left(_ count:Int) -> String {
        if count<=0 { return "" }
        return self.Mid(1,count)
    }

    /// Returns the last n characters from the string specified.
    /// result=stringVariable.Right(count)
    public func Right(_ count:Int) -> String {
        let strSize = self.count
        var indexFirst=strSize-count+1
        if indexFirst<1 { indexFirst=1 }
        return Mid(indexFirst,count)
    }
    /// Returns the position of the first occurrence of a string inside another string. The first character is numbered 1.
    /// result = stringVariable.InStr(find,[checkcase])
    /// return is not case sensitive unless checkcase=true
    public func InStr(_ lookfor:String,_ checkcase:Bool=false) -> Int {
        var self_test:String=self
        var othr_test:String=lookfor
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        let myRange: NSRange = (self_test as NSString).range(of: othr_test)
        var loc:Int=myRange.location
        if (loc<0) || (loc>self_test.count) {
            loc=0
        } else {
            loc+=1
        }
        return loc
    }

    /// same as above, but a starting index can be specified
    /// result = stringVariable.InStr([start,] find)
    /// return is not case sensitive unless checkcase=true
    public func InStr(_ start:Int,_ lookfor:String,_ checkcase:Bool=false) -> Int {
        var loc : Int=0
        var strt : Int=start
        if (strt<=0) { strt=1 }
        if strt<self.Len() {
            let temp:String=self.Mid(strt)
            loc=temp.InStr(lookfor,checkcase)
            if loc>0 { loc=loc+strt-1 }
        }
        return loc
    }

    /// compares to another string [-1 self<other 0=self=other +1=self>other]
    /// compare is not case sensitive unless checkcase=true
    public func Compare(_ other:String,_ checkcase:Bool=false) -> Int {
        // -1 less than 0=equal 1=greater
        var flag : Int=0
        var self_test:String=self
        var othr_test:String=other
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        if self_test==othr_test { flag=0 }
        if self_test<othr_test { flag=(-1) }
        if self_test>othr_test { flag=1 }
        return flag
    }

    /// returns true if string begins with specified value
    /// compare is not case sensitive unless checkcase=true
    public func BeginsWith(_ other:String,_ checkcase:Bool=false) -> Bool {
        var self_test:String=self
        var othr_test:String=other
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        return self_test.hasPrefix(othr_test)

    }
    /// returns true if string ends with specified value
    /// compare is not case sensitive unless checkcase=true
    public func EndsWith(_ other:String,_ checkcase:Bool=false) -> Bool {
        var self_test:String=self
        var othr_test:String=other
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        return self_test.hasSuffix(othr_test)
    }

    /// Returns the string passed with leading and trailing whitespaces removed.
    public func Trim() -> String {
        return self.trimmingCharacters(in: .whitespacesAndNewlines)
    }

    /// Returns the string passed with leading (left side) whitespaces removed.
    public func LTrim() -> String {
        var x:Int
        let cs:CharacterSet = .whitespacesAndNewlines
        x=self.Len()
        for i in(0...x-1) {
            if !cs.contains(UnicodeScalar(characterAtIndex(i))!) {
                return (self as NSString).substring(from: i)
            }
        }
        return self
    }

    /// Returns the string data type passed with trailing (right side) whitespaces removed.
    public func RTrim() -> String {
        var x:Int
        let cs:CharacterSet = .whitespacesAndNewlines
        x=self.Len()
        for i in(0...x-1).reversed() {
            if !cs.contains(UnicodeScalar(characterAtIndex(i))!) {
                return (self as NSString).substring(to: i+1)
            }
        }
        return self
    }

    /// Returns a field from a row of data. The first field is numbered 1.
    /// result=stringVariable.NthField(fieldNumber,[separator])
    /// NOTE : arguments are in REVERSE order from Xojo
    public func NthField(_ fieldNumber:Int,_ sep_by:String=",") -> String {
        let arr:[String]=self.components(separatedBy: sep_by)
        let x=fieldNumber-1
        if x<0 || fieldNumber>arr.count { return "" }
        return arr[x]
    }

    /// Returns the number of values (fields) in the string passed that are separated by the separator string passed.
    /// result = stringVariable.CountFields([separator])
    public func CountFields(_ sep_by:String=",") -> Int {
        let arr:[String]=self.components(separatedBy: sep_by)
        return arr.count
    }

    /// Creates a one-dimensional array from the String passed.
    /// array=source.Split([delimiter])
    public func Split(_ sep_by:String=",") -> [String] {
        let arr:[String]=self.components(separatedBy: sep_by)
        return arr
    }

    public func Lowercase() -> String { return self.lowercased() }
    public func Uppercase() -> String { return self.uppercased() }
    public func Titlecase() -> String { return self.capitalized }

    /// Returns the Double equivalent of the passed string.
    /// result=stringVariable.CDbl()
    /// supports Hex, Oct and Binary values (&H, &O, &B)
    public func CDbl() -> Double {
        var retDbl:Double=0
        var retStr:String = ""
        var mTest :String = ""
        if self.Len()>2 {
            retStr = self.Mid(3)
            mTest = self.Left(2)
        }
        switch mTest {
        case "&H" : retDbl=Double(Hex2Int(retStr))
        case "&O" : retDbl=Double(Oct2Int(retStr))
        case "&B" : retDbl=Double(Bin2Int(retStr))
        default : retDbl=(self as NSString).doubleValue
        }
        return retDbl
    }

    /// Returns the numeric form of a string as an Int64.
    /// result=stringVariable.CLong
    public func CLong() -> Int64 { return Int64(CDbl()) }

    /// Alias for CDBL()
    /// result=stringVariable.Val()
    public func Val() -> Double { return CDbl() }

    /// Returns the Integer equivalent of the passed string.
    /// result=stringVariable.intVal()
    /// supports Hex, Oct and Binary values (&H, &O, &B)
    /// this is required due to Swift strict typecasting
    public func intVal() -> Int { return Int(Val()) }

    /// Returns true if string is empty ("")
    /// result=stringVariable.Empty()
    public func Empty() -> Bool { return self.isEmpty }

    /// Replaces the first occurrence of a string with another string.
    /// result=stringVariable.Replace(oldString, newString)
        /// compare is not case sensitive unless checkcase=true
    public func Replace(_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String {
        var self_test:String=self
        var othr_test:String=oldStr
        if !checkcase {
            self_test=self_test.uppercased()
            othr_test=othr_test.uppercased()
        }
        var temp:String=""
        let x:Int=self_test.InStr(othr_test)
        let y:Int=x+oldStr.Len()
        if x>0 {
            if x>1 { temp=self.Left(x-1) }
            temp=temp+newStr+self.Mid(y)
        } else {
            temp=self
        }
        return temp
    }

    /// Replaces all occurrences of a string with another string.
    /// at this time seems checkcase is only a placeholder
    public func ReplaceAll(_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String {
        return self.replacingOccurrences(of: oldStr, with: newStr)//, options: nil, range: nil)
    }

    /// Indicates whether the value passed is numeric.
/// result=stringVariable.isNumeric()
    public func isNumeric() -> Bool {
        let length = self.count
        let scanner = Scanner(string: self)
        scanner.charactersToBeSkipped = nil
        var value: Double = 0.0
        if scanner.scanDouble(&value) && (scanner.scanLocation == length) {
            return true
        }
        return false
    }

    / ******P R I V A T E S U P P O R T F U N C T I O N S****** /
    private func AscAt(_ s:String,Index:Int) -> Int {
        var x:UInt32=0
        var i:Int=0
        for c in s.unicodeScalars {
            x=UnicodeScalar(c).value
            i+=1
            if (i==Index) { break }
        }
        return Int(x)
    }

    private func Base2Int(_ s:String,_ base:Int) -> Int {
        var retInt:Int=0
        // var i:Int
        var x:Int
        var valid:String=""
        var c:String=""
        let mStr:String = s.uppercased()
        let strSize :Int = mStr.count
        switch base {
        case 2 : valid="01"
        case 8 : valid="01234567"
        case 16 : valid="0123456789ABCDEF"
        default : return 0
        }

        for i in 1 ... strSize {
            c=mStr.Mid(i,1)
            x=valid.InStr(c)
            if (x==0) {
                retInt=0
                break
            }
            retInt=(retInt * base)+(x-1)
        }
        return retInt
    }

    private func Hex2Int(_ s:String) -> Int { return Base2Int(s,16) }
    private func Oct2Int(_ s:String) -> Int { return Base2Int(s,8) }
    private func Bin2Int(_ s:String) -> Int { return Base2Int(s,2) }
    private func characterAtIndex(_ index: Int) -> unichar { return (self as NSString).character(at: index)
    }
} /* END - String extension */

// --------- End of Framework ---------

/ ***FUNCTION Version of all String Properties*** /
/***these mirror the extension, but are result=function(sourceString,[parameters])***/

public func Asc( _ s:String) -> Int { return s.Asc() }
public func BeginsWith( _ s:String, other:String,_ checkcase:Bool=false) -> Bool { return s.BeginsWith(other,checkcase) }
public func Compare( _ s:String, other:String, checkcase:Bool=false) -> Int { return s.Compare(other,checkcase) }
public func Empty( _ s:String) -> Bool { return s.Empty() }
public func EndsWith( _ s:String, other:String,_ checkcase:Bool=false) -> Bool { return s.EndsWith(other,checkcase) }
public func CDbl( _ s:String) -> Double { return s.CDbl() }
public func CLong( _ s:String) -> Int64 { return s.CLong() }
public func Mid( _ s:String,_ start:Int,_ length:Int=(-1)) -> String { return s.Mid(start,length) }
public func Left( _ s:String,_ indx:Int) -> String { return s.Left(indx) }
public func Right( _ s:String,_ indx:Int) -> String { return s.Right(indx) }
public func InStr( _ s:String,_ find:String,_ checkcase:Bool=false) -> Int { return s.InStr(find,checkcase) }
public func InStr(_ start:Int,s:String,_ find:String,_ checkcase:Bool=false) -> Int { return s.InStr(start,find,checkcase) }
public func isNumeric( _ s:String) -> Bool { return s.isNumeric() }
public func Len( _ s:String) -> Int { return s.Len() }
public func Trim( _ s:String) -> String { return s.Trim() }
public func LTrim( _ s:String) -> String { return s.LTrim() }
public func RTrim( _ s:String) -> String { return s.RTrim() }
public func NthField( _ s:String,_ fieldNumber:Int,_ sep_by:String=",") -> String { return s.NthField(fieldNumber,sep_by) }
public func CountFields( _ s:String,_ sep_by:String=",") -> Int { return s.CountFields(sep_by) }
public func Uppercase( _ s:String) -> String { return s.Uppercase() }
public func Lowercase( _ s:String) -> String { return s.Lowercase() }
public func Titlecase( _ s:String) -> String { return s.Titlecase() }
public func Val( _ s:String) -> Double { return s.Val() }
public func intVal( _ s:String) -> Int { return s.intVal() }
public func Split( _ s:String,_ sep_by:String=",") -> [String] { return s.Split(sep_by) }
public func Replace( _ s:String,_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String { return s.Replace(oldStr,newStr,checkcase) }
public func ReplaceAll( _ s:String,_ oldStr:String,_ newStr:String,_ checkcase:Bool=false) -> String { return s.ReplaceAll(oldStr,newStr,checkcase) }

/* **Other String Related Functions** */

public func Chr(_ v:Int) -> String { return String(describing: UnicodeScalar(v)) }
public func Chr(_ v:CGFloat) -> String { return Chr(Int(v)) }

public func Join(_ s:[String],_ sep_by:String=",") -> String { return s.joined(separator: sep_by) }

public func Str(_ x:Any,dp:Int=12) -> String {
    var retStr:String=""
    switch x {
    case let mBool as Bool:
        if mBool {
            retStr = "true"
        } else {
            retStr = "false"
        }
    case let mInt as Int : retStr = NSString(format:"%d",mInt) as String
    case let mDbl as Double : retStr = NSString(format:"%.\(dp)f" as NSString,mDbl) as String
    case let mStr as String : retStr = mStr
    default : retStr=""
    }
    return retStr
}

public func numericLen(_ s:String) -> Int {
    let scanner = Scanner(string: s)
    scanner.charactersToBeSkipped = nil
    var value: Double = 0.0
    if scanner.scanDouble(&value) && (scanner.scanLocation > 0) {
        return scanner.scanLocation
    }
    return 0
}

```
