So the method I used in Swift 2 no longer works because of changes in Swift 3, regarding String indices and ranges. Previously I had
func configureLabel(defaultColor: UIColor, highlightColor: UIColor, boldKeyText: Bool) {
if let index = self.text?.characters.indexOf(Character("|")) {
self.text = self.text!.stringByReplacingOccurrencesOfString("|", withString: "")
let labelLength:Int = Int(String(index))! // Now returns nil
var keyAttr: [String:AnyObject] = [NSForegroundColorAttributeName: highlightColor]
var valAttr: [String:AnyObject] = [NSForegroundColorAttributeName: defaultColor]
if boldKeyText {
keyAttr[NSFontAttributeName] = UIFont.systemFontOfSize(self.font.pointSize)
valAttr[NSFontAttributeName] = UIFont.systemFontOfSize(self.font.pointSize, weight: UIFontWeightHeavy)
}
let attributeString = NSMutableAttributedString(string: self.text!)
attributeString.addAttributes(keyAttr, range: NSRange(location: 0, length: (self.text?.characters.count)!))
attributeString.addAttributes(valAttr, range: NSRange(location: 0, length: labelLength))
self.attributedText = attributeString
}
}
Basically I would be able to take a string like "First Name:| Gary Oak" and have all the parts before and after the | character be different colors, or make part of it bold, but the line I commented above no longer returns a value, which breaks everything else afterwards. Any ideas on how to do this?
In Swift 3 you can use something like this:
func configureLabel(defaultColor: UIColor, highlightColor: UIColor, boldKeyText: Bool) {
if let index = self.text?.characters.index(of: Character("|")) {
self.text = self.text!.replacingOccurrences(of: "|", with: "")
let position = text.distance(from: text.startIndex, to: index)
let labelLength:Int = Int(String(describing: position))!
var keyAttr: [String:AnyObject] = [NSForegroundColorAttributeName: defaultColor]
var valAttr: [String:AnyObject] = [NSForegroundColorAttributeName: highlightColor]
if boldKeyText {
keyAttr[NSFontAttributeName] = UIFont.systemFont(ofSize: self.font.pointSize)
valAttr[NSFontAttributeName] = UIFont.systemFont(ofSize: self.font.pointSize, weight: UIFontWeightHeavy)
}
let attributeString = NSMutableAttributedString(string: self.text!)
attributeString.addAttributes(keyAttr, range: NSRange(location: 0, length: (self.text?.characters.count)!))
attributeString.addAttributes(valAttr, range: NSRange(location: 0, length: labelLength))
self.attributedText = attributeString
}
}
the main idea that using let position = text.distance(from: text.startIndex, to: index) you got not the integer representation of string position but the string Index value. Using text.distance(from: text.startIndex, to: index) you can find int position for string Index
Related
var str:String = "When I was ###young$$$, I thought ###time$$$ was money"
VStsck{
Text(str)
}
enter image description here
You can parse your string using regular expression and build Text based on found ranges:
struct HighlightedText: View{
let text: Text
private static let regularExpression = try! NSRegularExpression(
pattern: "###(?<content>((?!\\$\\$\\$).)*)\\$\\$\\$"
)
private struct SubstringRange {
let content: NSRange
let full: NSRange
}
init(_ string: String) {
let ranges = Self.regularExpression
.matches(
in: string,
options: [],
range: NSRange(location: 0, length: string.count)
)
.map { match in
SubstringRange(
content: match.range(withName: "content"),
full: match.range(at: 0)
)
}
var nextNotProcessedSymbol = 0
var text = Text("")
let nsString = string as NSString
func appendSubstringStartingNextIfNeeded(until endLocation: Int) {
if nextNotProcessedSymbol < endLocation {
text = text + Text(nsString.substring(
with: NSRange(
location: nextNotProcessedSymbol,
length: endLocation - nextNotProcessedSymbol
)
))
}
}
for range in ranges {
appendSubstringStartingNextIfNeeded(until: range.full.location)
text = text + Text(nsString.substring(with: range.content))
.foregroundColor(Color.red)
nextNotProcessedSymbol = range.full.upperBound
}
appendSubstringStartingNextIfNeeded(until: string.count)
self.text = text
}
var body: some View {
text
}
}
Usage:
HighlightedText("When I was ###young$$$, I thought ###time$$$ was money")
func hilightText(str:String) -> Text{
var resultText:Text = Text("")
if(str.contains("###")){
let titleArr:Array<String> = str.components(spearatedBy:"###")
for(title in titleArr){
resultText = resultText
+ Text(title.components(spearatedBy:"$$$")[0]).foregroundColor(Color.red)
+ Text(title.components(spearatedBy:"$$$")[1]).foregroundColor(Color.gray)
}else{
resultText = resultText + Text(title).foregroundColor(Color.gray)
}
}
return resultText
}else{
return Text(str).foregroundColor(Color.gray)
}
struct View1:View{
#State var title = "When I was ###young$$$, I thought ###time$$$ was money"
var body : some View{
hilightText(str:title)
}
}
I want to change the background color of the view, while the text field has the Special characters like 123!#%^&() etc otherwise background colors should be the same color.
I have implemented but changing according to the each character it should not be like this if in entire text field any special character means color has to change.
Here is my code.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let specialCharacters = "!~`##$%^&*-+();:={}[],.<>?\\/\"\'"
let searchText = textField.text! + string
//
let character = CharacterSet(charactersIn: specialCharacters)
if (string.rangeOfCharacter(from: character) != nil){
print("matched")
self.view.backgroundColor = UIColor.gray
}else
{
self.view.backgroundColor = UIColor.white
}
Please find below working solutions Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let specialCharacters = "!~`##$%^&*-+();:={}[],.<>?\\/\"\'"
var searchText = textField.text! + string
let characterSet = CharacterSet(charactersIn: specialCharacters)
if string == "" {
searchText.removeLast()
}
if (string.rangeOfCharacter(from: characterSet) != nil) {
print("matched")
self.view.backgroundColor = UIColor.gray
textField.textColor = UIColor.white
return true
} else if (searchText.rangeOfCharacter(from: characterSet) == nil ) {
self.view.backgroundColor = UIColor.white
textField.textColor = UIColor.black
return true
}
return true
}
I use this code but it should return anyone. But I need to use this two scenarios. So how can I change this? Anyone help me. I restrict a (.0 symbol and at the same time only allowed 4 digit amount. How can i do this.???
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = amountField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= 4 // Bool
let ACCEPTABLE_CHARACTERS = "1234567890"
let cs = CharacterSet(charactersIn: ACCEPTABLE_CHARACTERS).inverted
let filtered: String = string.components(separatedBy: cs).joined(separator: "")
return string == filtered
}
Possible solution for your issue.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var newLength = string.utf16.count - range.length
if let text = textfield.text {
newLength = text.utf16.count + string.utf16.count - range.length
}
let characterSet = NSMutableCharacterSet()
characterSet.addCharacters(in: "1234567890")
if string.rangeOfCharacter(from: characterSet.inverted) != nil || newLength > 4 {
return false
}
return true
}
I have a UICollectionView which takes the results of an API search. The search is triggered by the following code. The results are appended to a dictionary [[String: Any]] and I call self.collectionView.reloadData() after my query completes.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var newValue = textField.text!
let location = min(range.location, newValue.characters.count)
let startIndex = newValue.characters.index(newValue.startIndex, offsetBy: location)
let endIndex = newValue.characters.index(newValue.startIndex, offsetBy: location + range.length)
let newRangeValue = Range<String.Index>(startIndex ..< endIndex)
newValue.replaceSubrange(newRangeValue, with: string)
searchView.searchFieldValueChanged(newValue)
return true
}
Then, if I want to change the search string and search again I want to empty the dictionary and call reloadData() again I get an app crash.
The error is
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist:
Here is my datasource implementation
var searchResults = [[String: Any]]()
let layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
layout.estimatedItemSize.height = 200
layout.estimatedItemSize.width = 200
layout.minimumInteritemSpacing = 10
layout.minimumLineSpacing = 10
return layout
}()
collectionView = UICollectionView(frame: self.frame, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(LanesCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.backgroundColor = .yellow // Constants.APP_BACKGROUND_COLOR
collectionView.alwaysBounceVertical = true
collectionView.clipsToBounds = true
collectionView.translatesAutoresizingMaskIntoConstraints = false
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if searchResults.count == 0 {
collectionView.alpha = 0.0
} else {
collectionView.alpha = 1.0
}
return searchResults.count
}
after query
func parseMoreData(jsonData: [String: Any]) {
let items = jsonData["items"] as! [[String: Any]]
self.collectionView.layoutIfNeeded()
self.collectionView.reloadData()
}
}
func searchFieldValueChanged(_ textValue: String) {
searchResults = []
This looks fixed by using this instead of layoutIfNeeded()
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
I'm trying to limit the users First_Name to allow only letters so it should respond with an error for numbers or special characters after clicking on send_button. I found some examples here but because they are build on older swift version I'm having problems to make it work. Until now I've managed to read the First Name and throw an error if the first element on this textfield is not a letter but the code allow things like this (First name = "J123g") or ("Mark##$") and I don't want this to be the case.
func isOneLetter(in text: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: "[a-zA-Z]")
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
let temp = results.map { nsString.substring(with: $0.range)}
return temp.isEmpty
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return false
}
}
#IBAction func Send_Button(_ sender: AnyObject) {
let UserName = First_Name.text
if (isOneLetter(in: UserName!))
{
DisplayMyAlertMessage(userMessage: "First Name must contain only letter and spaces")
}
}
Your regex pattern checks for one alphanumeric character which matches both unwanted examples.
This regex checks for from beginning (^) to the end ($) of the string there must be one or more (+) alphanumeric characters ([a-zA-Z]). The benefit is that it treats an empty string also as bad.
^[a-zA-Z]+$
1st you have to inherit the UITextViewDelegate class with you own
class
class ViewController: UIViewController, UITextViewDelegate {
2nd add an IBOutlet
#IBOutlet weak var firstName: UITextField!
3rd you have to assure this object is using
override func viewDidLoad() {
super.viewDidLoad()
firstName.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == firstName {
let allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
let allowedCharacterSet = CharacterSet(charactersIn: allowedCharacters)
let typedCharacterSet = CharacterSet(charactersIn: string)
let alphabet = allowedCharacterSet.isSuperset(of: typedCharacterSet)
return alphabet
}
}
Another way could be:
let userInput = ""
let set = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLKMNOPQRSTUVWXYZ")
if userInput.rangeOfCharacter(from: set.inverted) != nil {
print("ERROR: There are numbers included!")
}
Updated for swift 3:
if you want to validate name and allow only letters for User Name then used below simple lines of code :
// function definition:
func isValidName(_ nameString: String) -> Bool {
var returnValue = true
let mobileRegEx = "[A-Za-z]{3}" // {3} -> at least 3 alphabet are compulsory.
do {
let regex = try NSRegularExpression(pattern: mobileRegEx)
let nsString = nameString as NSString
let results = regex.matches(in: nameString, range: NSRange(location: 0, length: nsString.length))
if results.count == 0
{
returnValue = false
}
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
returnValue = false
}
return returnValue
}
// function call :
var firstName = mEnterFirstNameTextField.text!
let isFirstNameValid = isValidName(firstName)
if isFirstNameValid{
// do user logic
}else{
// show error msg: -> "Enter name is not valid, please enter again..."
}