For example string = "Hello #manan123 and #man"
i am doing tagging functionality in textview and i want when user press backspace if word contains # then whole word will delete not single character.
but i am facing problem while two tagged word have same characters. So in above example when i am trying to delete last word #man then #manan123 also convert to the an123.
Here is my code on textview delegate method
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "" {
if let selectedRange = textView.selectedTextRange {
let cursorOffset = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
if let myText = textView.text {
let index = myText.index(myText.startIndex, offsetBy: cursorOffset)
let substring = myText[..<index]
if let lastword = substring.components(separatedBy: " ").last {
if lastword.hasPrefix("#") {
//Check complete word
let completeLastword = myText.components(separatedBy: " ").filter{$0.contains(lastword)}.last
textView.text = myText.replacingOccurrences(of: completeLastword!, with: "")
return false
}
}
}
}
}
return true
}
Hi I have updated your code please check it.
if text == "" {
if let selectedRange = textView.selectedTextRange {
let cursorOffset = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
if let myText = textView.text {
let index = myText.index(myText.startIndex, offsetBy: cursorOffset)
let substring = myText[..<index]
if let lastword = substring.components(separatedBy: " ").last {
if lastword.hasPrefix("#") {
var newText = myText
let start = newText.index(myText.startIndex, offsetBy: cursorOffset - lastword.count);
let end = newText.index(myText.startIndex, offsetBy: (cursorOffset - lastword.count) + lastword.count);
newText.replaceSubrange(start..<end, with: "")
textView.text = newText
return false
}
}
}
}
}
Hope this helps.
how to put a $ coin mask in alert.addTextfield?
currency ?
with textfield to do with?
https://code.i-harness.com/en/q/1c673c6
func showalert(with marcas: Marcas?){
let title = marcas == nil ? "Adicionar" : "Editar"
let alert = UIAlertController(title: title + " Marca", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in textField.placeholder = "Nome da Marca"
if let name = marcas?.nome {
textField.text = name
}
}
alert.addTextField { (textFieldValor) in textFieldValor.placeholder = "Preço"
if let valor = marcas?.valor {
textFieldValor.text = valor
}
}
textFieldValor.addTarget(self, action: #selector(self.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 = "R$"
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)!
}
}
I want to get ride of the white spaces in front and at the end of my NSAttributedString(Trimming it). I can't simply convert it to string and do trimming because there are images(attachments) in it.
How can i do it?
Create extension of NSAttributedString as below.
extension NSAttributedString {
public func attributedStringByTrimmingCharacterSet(charSet: CharacterSet) -> NSAttributedString {
let modifiedString = NSMutableAttributedString(attributedString: self)
modifiedString.trimCharactersInSet(charSet: charSet)
return NSAttributedString(attributedString: modifiedString)
}
}
extension NSMutableAttributedString {
public func trimCharactersInSet(charSet: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: charSet as CharacterSet)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: charSet)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
}
}
}
and use in viewController where you want to use. like this
let attstring = NSAttributedString(string: "this is test message. Please wait. ")
let result = attstring.attributedStringByTrimmingCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
This works even with emoji in the text
extension NSAttributedString {
/** Will Trim space and new line from start and end of the text */
public func trimWhiteSpace() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.utf16.description.rangeOfCharacter(from: invertedSet)
let endRange = string.utf16.description.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.utf16.distance(from: string.startIndex, to: startLocation) - 1
let length = string.utf16.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
USAGE
let attributeString = NSAttributedString(string: "\n\n\n Hi π π©βπ©βπ§π©βπ©βπ¦βπ¦π©βπ©βπ§βπ§π¨βπ¨βπ¦π©βπ¦π¨βπ¨βπ§βπ§π¨βπ¨βπ¦βπ¦π¨βπ¨βπ§βπ¦π©βπ§βπ¦π©βπ¦βπ¦π©βπ§βπ§π¨βπ¦ buddy. ")
let result = attributeString.trimWhiteSpace().string // Hi π π©βπ©βπ§π©βπ©βπ¦βπ¦π©βπ©βπ§βπ§π¨βπ¨βπ¦π©βπ¦π¨βπ¨βπ§βπ§π¨βπ¨βπ¦βπ¦π¨βπ¨βπ§βπ¦π©βπ§βπ¦π©βπ¦βπ¦π©βπ§βπ§π¨βπ¦ buddy.
Swift 4 and above
extension NSMutableAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
use:
let string = "This is string with some space in the end. "
let attributedText = NSMutableAttributedString(string: string).trimmedAttributedString()
It turns out that Unicode strings are hard hahaha! The other solutions posted here are a great starting point, but they crashed for me when using non-latin strings.
Whenever using indexes or ranges in Swift Strings, we need to use String.Index instead of plain Int. Creating an NSRange from a Range<String.Index> has to be done with NSRange(swiftRange, in: String).
That being said, this code builds on the other answers, but makes it unicode-proof:
public extension NSMutableAttributedString {
/// Trims new lines and whitespaces off the beginning and the end of attributed strings
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.lowerBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let trimmedRange = startLocation...endLocation
return attributedSubstring(from: NSRange(trimmedRange, in: string))
}
}
I made a swift 3 implementation, just in case anyone is interested:
/**
Trim an attributed string. Can for example be used to remove all leading and trailing spaces and line breaks.
*/
public func attributedStringByTrimmingCharactersInSet(set: CharacterSet) -> NSAttributedString {
let invertedSet = set.inverted
let rangeFromStart = string.rangeOfCharacter(from: invertedSet)
let rangeFromEnd = string.rangeOfCharacter(from: invertedSet, options: .backwards)
if let startLocation = rangeFromStart?.upperBound, let endLocation = rangeFromEnd?.lowerBound {
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let newRange = NSRange(location: location, length: length)
return self.attributedSubstring(from: newRange)
} else {
return NSAttributedString()
}
}
Swift 3.2 Version:
extension NSAttributedString {
public func trimmingCharacters(in characterSet: CharacterSet) -> NSAttributedString {
let modifiedString = NSMutableAttributedString(attributedString: self)
modifiedString.trimCharacters(in: characterSet)
return NSAttributedString(attributedString: modifiedString)
}
}
extension NSMutableAttributedString {
public func trimCharacters(in characterSet: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: characterSet)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: characterSet)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: characterSet, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: characterSet, options: .backwards)
}
}
}
Following code will work for your requirement.
var attString: NSAttributedString = NSAttributedString(string: " this is att string")
let trimmedString = attString.string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
I am getting a string from html parse that is;
string = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
my code is something like
var startIndex = text.rangeOfString("'")
var endIndex = text.rangeOfString("',")
var range2 = startIndex2...endIndex
substr= string.substringWithRange(range)
i am not sure if my second splitting string should be "'" or "',"
i want my outcome as
substr = "Info/99/something"
extension String {
func slice(from: String, to: String) -> String? {
return (range(of: from)?.upperBound).flatMap { substringFrom in
(range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
String(self[substringFrom..<substringTo])
}
}
}
}
"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
.sliceFrom("'", to: "',")
I'd use a regular expression to extract substrings from complex input like this.
Swift 3.1:
let test = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
if let match = test.range(of: "(?<=')[^']+", options: .regularExpression) {
print(test.substring(with: match))
}
// Prints: Info/99/something
Swift 2.0:
let test = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
if let match = test.rangeOfString("(?<=')[^']+", options: .RegularExpressionSearch) {
print(test.substringWithRange(match))
}
// Prints: Info/99/something
I rewrote one of the top Swift answers to understand what it was doing with map. I prefer a version using guard, IMO.
extension String {
func slice(from: String, to: String) -> String? {
guard let rangeFrom = range(of: from)?.upperBound else { return nil }
guard let rangeTo = self[rangeFrom...].range(of: to)?.lowerBound else { return nil }
return String(self[rangeFrom..<rangeTo])
}
}
behavior:
let test1 = "a[b]c".slice(from: "[", to: "]") // "b"
let test2 = "abc".slice(from: "[", to: "]") // nil
let test3 = "a]b[c".slice(from: "[", to: "]") // nil
let test4 = "[a[b]c]".slice(from: "[", to: "]") // "a[b"
To find all substrings that are between a starting string and an ending string:
extension String {
func sliceMultipleTimes(from: String, to: String) -> [String] {
components(separatedBy: from).dropFirst().compactMap { sub in
(sub.range(of: to)?.lowerBound).flatMap { endRange in
String(sub[sub.startIndex ..< endRange])
}
}
}
}
let str = "start A end ... start B end"
str.sliceMultipleTimes(from: "start", to: "end") // ["A", "B"]
This works if it is always the second split:
let subString = split(string, isSeparator: "'")[1]
You can use var arr = str.componentsSeparatedByString(",") as your second split which will return you array
Swift 4.2:
extension String {
//right is the first encountered string after left
func between(_ left: String, _ right: String) -> String? {
guard let leftRange = range(of: left), let rightRange = range(of: right, options: .backwards)
,leftRange.upperBound <= rightRange.lowerBound else { return nil }
let sub = self[leftRange.upperBound...]
let closestToLeftRange = sub.range(of: right)!
return String(sub[..<closestToLeftRange.lowerBound])
}
}
Consider using a regular expression to match everything between single quotes.
let string = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
let pattern = "'(.+?)'"
let regex = NSRegularExpression(pattern: pattern, options: nil, error: nil)
let results = regex!.matchesInString(string, options: nil, range: NSMakeRange(0, count(string))) as! [NSTextCheckingResult]
let nsstring = string as NSString
let matches = results.map { result in return nsstring.substringWithRange(result.range)}
// First match
println(matches[0])
Swift 5
extension String {
///Returns an empty string when there is no path.
func substring(from left: String, to right: String) -> String {
if let match = range(of: "(?<=\(left))[^\(right)]+", options: .regularExpression) {
return String(self[match])
}
return ""
}
}
In Swift 4 or later you can create an extension method on StringProtocol to support substrings as well. You can just return a Substring instead of a new String:
edit/update: Swift 5 or later
extension StringProtocol {
func substring<S: StringProtocol>(from start: S, options: String.CompareOptions = []) -> SubSequence? {
guard let lower = range(of: start, options: options)?.upperBound
else { return nil }
return self[lower...]
}
func substring<S: StringProtocol>(through end: S, options: String.CompareOptions = []) -> SubSequence? {
guard let upper = range(of: end, options: options)?.upperBound
else { return nil }
return self[..<upper]
}
func substring<S: StringProtocol>(upTo end: S, options: String.CompareOptions = []) -> SubSequence? {
guard let upper = range(of: end, options: options)?.lowerBound
else { return nil }
return self[..<upper]
}
func substring<S: StringProtocol, T: StringProtocol>(from start: S, upTo end: T, options: String.CompareOptions = []) -> SubSequence? {
guard let lower = range(of: start, options: options)?.upperBound,
let upper = self[lower...].range(of: end, options: options)?.lowerBound
else { return nil }
return self[lower..<upper]
}
func substring<S: StringProtocol, T: StringProtocol>(from start: S, through end: T, options: String.CompareOptions = []) -> SubSequence? {
guard let lower = range(of: start, options: options)?.upperBound,
let upper = self[lower...].range(of: end, options: options)?.upperBound
else { return nil }
return self[lower..<upper]
}
}
Usage:
let string = "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
let substr = string.substring(from: "'") // "Info/99/something', 'City Hall',1, 99);"
let through = string.substring(through: "Info") // "javascript:getInfo"
let upTo = string.substring(upTo: "Info") // "javascript:get"
let fromUpTo = string.substring(from: "'", upTo: "',") // "Info/99/something"
let fromThrough = string.substring(from: "'", through: "',") // "Info/99/something',"
let fromUpToCaseInsensitive = string.substring(from: "'info/", upTo: "/something", options: .caseInsensitive) // "99"
If you want to support also from the start or end of the string
extension String {
func slice(from: String, to: String) -> String? {
return (from.isEmpty ? startIndex..<startIndex : range(of: from)).flatMap { fromRange in
(to.isEmpty ? endIndex..<endIndex : range(of: to, range: fromRange.upperBound..<endIndex)).map({ toRange in
String(self[fromRange.upperBound..<toRange.lowerBound])
})
}
}
}
"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
.slice(from: "'", to: "',") // "Info/99/something"
"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
.slice(from: "", to: ":") // "javascript"
"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
.slice(from: ":", to: "") // "getInfo(1,'Info/99/something', 'City Hall',1, 99);"
"javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
.slice(from: "", to: "") // "javascript:getInfo(1,'Info/99/something', 'City Hall',1, 99);"
if you want another syntax, maybe more readable
extension String {
func slice(from: String, to: String) -> String? {
guard let fromRange = from.isEmpty ? startIndex..<startIndex : range(of: from) else { return nil }
guard let toRange = to.isEmpty ? endIndex..<endIndex : range(of: to, range: fromRange.upperBound..<endIndex) else { return nil }
return String(self[fromRange.upperBound..<toRange.lowerBound])
}
}
Swift 4 version of #litso. To find all values in text
func find(inText text: String, pattern: String) -> [String]? {
do {
let regex = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let result = regex.matches(in: text, options: .init(rawValue: 0), range: NSRange(location: 0, length: text.count))
let matches = result.map { result in
return (text as NSString).substring(with: result.range)
}
return matches
} catch {
print(error)
}
return nil
}
I have a function:
func IphoneName() -> String
{
let device = UIDevice.currentDevice().name
return device
}
Which returns the name of the iPhone (simple). I need to remove the "'s Iphone" from the end. I have been reading about changing it to NSString and use ranges, but I am a bit lost!
What about this:
extension String {
func removeCharsFromEnd(count:Int) -> String{
let stringLength = countElements(self)
let substringIndex = (stringLength < count) ? 0 : stringLength - count
return self.substringToIndex(advance(self.startIndex, substringIndex))
}
func length() -> Int {
return countElements(self)
}
}
Test:
var deviceName:String = "Mike's Iphone"
let newName = deviceName.removeCharsFromEnd("'s Iphone".length()) // Mike
But if you want replace method use stringByReplacingOccurrencesOfString as #Kirsteins posted:
let newName2 = deviceName.stringByReplacingOccurrencesOfString(
"'s Iphone",
withString: "",
options: .allZeros, // or just nil
range: nil)
You don't have to work with ranges in this case. You can use:
var device = UIDevice.currentDevice().name
device = device.stringByReplacingOccurrencesOfString("s Iphone", withString: "", options: .allZeros, range: nil)
In Swift3:
var device = UIDevice.currentDevice().name
device = device.replacingOccurrencesOfString("s Iphone", withString: "")
Swift 4 Code
//Add String extension
extension String {
func removeCharsFromEnd(count:Int) -> String{
let stringLength = self.count
let substringIndex = (stringLength < count) ? 0 : stringLength - count
let index: String.Index = self.index(self.startIndex, offsetBy: substringIndex)
return String(self[..<index])
}
func length() -> Int {
return self.count
}
}
//Use string function like
let deviceName:String = "Mike's Iphone"
let newName = deviceName.removeCharsFromEnd(count: "'s Iphone".length())
print(newName)// Mike