Bulleted list with NSTextList in NSTextView example? - swift

I would want to parse user input in a NSTextView, so that a line beginning with "- " would automatically start a bulleted list. What do I have to do to the NSTextView.textStorage to enable a bulleted list so that the next bullet automatically appears when you press Enter after each bullet line?
I've found a few examples but they all insert the bullets by hand, so I was wondering what's the point of specifying let textList = NSTextList(markerFormat: "{box}", options: 0) if you then have to manually insert the box-symbol yourself?
Currently I'm trying to implement this in a custom NSTextView with an overriden shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) So a simple example that would solve the simplest input case of "- " starting an automatic bulleted list, would be highly regarded.
Update:
Here is the code I'm currently using:
override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
if string == "-" && replacementString == " " {
let textList = NSTextList(markerFormat: "{disc}", options: 0)
let textListParagraphStyle = NSMutableParagraphStyle()
textListParagraphStyle.textLists = [textList]
let attributes = [NSParagraphStyleAttributeName: textListParagraphStyle]
string = "\t\(textList.markerForItemNumber(0))\t"
textStorage?.addAttributes(attributes, range: NSMakeRange(0, textStorage!.string.length))
return false
}
else if affectedCharRange.location > 0 && replacementString == "\n\n" {
textStorage?.insertAttributedString(NSAttributedString(string: "\t"), atIndex: affectedCharRange.location)
return true
}
return true
}
I'm just trying to solve the simplest case of the user typing "- " as the first characters in the NSTextView. This is what happens with the code above:
The bigger font in the beginning comes from the typingAttributes I have set. This gets automatically overriden later as you can see.
Returning false from the else if clause and inserting the \n directly there will prevent the NSTextView from automatically adding new bullets. After the first time I return true from there, new bullets are added automatically without calling this method???

Try to add bullets using Unicode character by replace "- " with \u{2022}
textView.text = " \u{2022} This is a list item!

Related

How can I restrict user input when the value inputed is greater than a certain value Swift 5.1

My current code is:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.transferBalanceTextField {
if transferBalanceTextField.text! > currentBalance.text! {
return false
}
}
return true
}
But there are issues with this because if I type in a 6 first and the currentBalance is 582, it doesn't let me type in another digit because it believes the 6 is greater then 582, HELP!
Convert string to Ints in your case, and check it:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.transferBalanceTextField {
if let str1 = transferBalanceTextField.text,
let int1 = Int(str1),
let str2 = currentBalance.text,
let int2 = Int(str2),
int1 > int2 {
return false
}
}
return true
}
Avoid force unwrap as much as possible - it'll help you a lot when code will be big.
UPDATE after comments
Simple code above only shows converting Strings to Ints and testing it. I thought it's clear, you see it and understand.
So - if you want full test according to your case - you have also take into account what exactly this function check (should in its name) and how to deal with it.
You have two states of your field - current and next one after possible update (should you change the text in field on user input or not?). Above simple code only test current state before new user input. You have to do checking with future possible text value when new user input already accepted to the field.
For example how it work now using code above (lets user already input 100 in first field and have 500 in another field):
user press "0" - want to enter 1000
you got call of this method
you check old value - 100 - against another value and decide should it be updated or not
method return true - (should change)
and it change it - field become 1000
after it you can't update field - method now will always return false (1000 is over 500)
What you need to do:
create string what should be in field after user input (in my example it must be 1000) - you have current value + new input + range on this new input
and check against it and another value.
Why it's matter - in this method you can check all cases of user input - simple input one char by one, paste from clipboard, replacing some characters, input in middle of string, full clear and so on - and all this must be done without and before real field visual updating.
Here is full code, you should use for full your case:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == self.loginField {
if let str1 = loginField.text,
let str2 = passwordField.text,
let rangeExpr = Range(range, in: str1) {
let strFuture = str1.replacingCharacters(in: rangeExpr, with: string)
if let int1 = Int(strFuture),
let int2 = Int(str2),
int1 > int2 {
return false
}
}
}
return true
}
main change is in line
let strFuture = str1.replacingCharacters(in: rangeExpr, with: string)
Please, read methods you use, how it's work, and what your should do for it carefully - and you will get your progress much faster.

Swift UITextfield and CharacterSet cannot delete previous user's input

I'm writing code restricting the user's ability to enter alphabetic texts and repeating decimal into the textField.
In the code below my text field were able to accept only user's numeric input and so does the code I commented out however, the only difference is the user can't delete after he/she type in a number.
Whereas, the user were able to delete after he/she type in the number if I replace the current code with the commented code.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimalSeperator = textField.text?.range(of: ".")
let replacementTextHasDecimalSeperator = string.range(of: ".")
// let replacementTextCharacterSet = CharacterSet(charactersIn: string)
// let replacementTextIsAlphabetic = (replacementTextCharacterSet.isDisjoint(with: CharacterSet.letters) == false)
///This means an element in set A intersect with an element in set B
let replacementTextCharacterSet = CharacterSet(charactersIn: string)
let replacementTextIsAlphabetic = (replacementTextCharacterSet.isSubset(of: CharacterSet.letters) == true)
if (existingTextHasDecimalSeperator != nil && replacementTextHasDecimalSeperator != nil) || replacementTextIsAlphabetic {
return false
} else {
return true
}
The code that was commented out above work as intended however, the current code isn't and I failed to understand why.
Can you please explain to me the reason why I can't delete what I input afterward.
If I use .isSubset = true the logic seems the same to me.
you need to handle backspace in your shouldChangeCharactersIn function
https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619599-textfield
string
The replacement string for the specified range. During typing,
this parameter normally contains only the single new character that
was typed, but it may contain more characters if the user is pasting
text. When the user deletes one or more characters, the replacement
string is empty.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (string.isEmpty){
print("Backspace")
}
return true
}

Function textField catches mistake but still displays bad character

In the function textField, the second part (starting with 'let ...') performs perfectly by finding whenever more than one decimal point has been attempted to be entered into a textField, and does not allow more than one decimal point to be displayed in a textField.
The problem is that in the first part. Here it tries to first find if an alphabetic character has been typed. It does recognize that happening, and exits returning 'false'.
Unlike the 'repeated decimal finding part of the code, the textField displays the bad character rather than skipping it.
Maybe I don't understand how this delegated method is intended to work. I though the method somehow when it returns false it prevents that character from being added to the textField display.
func textField(_ textField:UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
for scalar in (textField.text?.unicodeScalars)! {
if NSCharacterSet.letters.contains(scalar) {
print("letter scalar found: \(scalar.value), false")
return false
}
}
let existingTextHasDecimalSeperator = textField.text?.range(of: ".") // search textField for '.', can be 'nil'
let replacementTextHasDecimalSeperator = string.range(of: ".") // next character is '.', can be 'nil'
if existingTextHasDecimalSeperator != nil, replacementTextHasDecimalSeperator != nil {
return false
} else {
return true
}
}
It's because you're searching the existing textField.text, not the new character (replacementString) for alpha characters.
Change (textField.text?.unicodeScalars)! to string.unicodeScalars and you should be set.
func textField(_ textField:UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
for scalar in string.unicodeScalars {
if NSCharacterSet.letters.contains(scalar) {
print("letter scalar found: \(scalar.value), false")
return false
}
}
let existingTextHasDecimalSeperator = textField.text?.range(of: ".") // search textField for '.', can be 'nil'
let replacementTextHasDecimalSeperator = string.range(of: ".") // next character is '.', can be 'nil'
if existingTextHasDecimalSeperator != nil, replacementTextHasDecimalSeperator != nil {
return false
} else {
return true
}
}
The suggestion of replacing the search from textField to string works! Here is a listing of the swift 3.0 version that works.

xcode swift - formatting text as phone number [duplicate]

This question already has answers here:
UITextField for Phone Number
(25 answers)
Closed 8 years ago.
I'm creating an iPhone app using Swift. I'm trying to setup a textfield in which the user can enter a phone number that automatically becomes formatted EXACTLY like it does in the built-in Contacts app as it is typed. I'm hoping xcode has built-in methods for doing this.
As an alternative, I created my own code that would add and delete brackets, dashes, etc as the user types and backspaces, but this quickly became problematic if the user was to move the curser away from the end of the entered text. In the Contacts app, if the cursor is moved just after a bracket and the user hits backspace, it deletes not just the bracket but rather the number preceding it. I'm not sure if this is done with some built-in formatting method or if perhaps there is code that replicates the text shown with brackets, dashes, etc removed and reads the position of the cursor, then calculates what the new string should be, and adds new brackets, dashes, etc.
Specifically, I'd like to know:
1) Is there a built-in method to format text to look like a phone number exactly as is done in the Contacts app?
2) If there is no built-in method, can someone tell me how I can have Swift read in the cursor position?
Thanks!
There is no built-in way to do this. Here's one implementation that uses the UITextField's shouldChangeCharactersInRange method:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
if textField == phoneTextField
{
var newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
var components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet)
var decimalString = "".join(components) as NSString
var length = decimalString.length
var hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)
if length == 0 || (length > 10 && !hasLeadingOne) || length > 11
{
var newLength = (textField.text as NSString).length + (string as NSString).length - range.length as Int
return (newLength > 10) ? false : true
}
var index = 0 as Int
var formattedString = NSMutableString()
if hasLeadingOne
{
formattedString.appendString("1 ")
index += 1
}
if (length - index) > 3
{
var areaCode = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("(%#)", areaCode)
index += 3
}
if length - index > 3
{
var prefix = decimalString.substringWithRange(NSMakeRange(index, 3))
formattedString.appendFormat("%#-", prefix)
index += 3
}
var remainder = decimalString.substringFromIndex(index)
formattedString.appendString(remainder)
textField.text = formattedString
return false
}
else
{
return true
}
}
As previously answered in this thread: UITextField for Phone Number

How do I force a text field to be upper case only in Swift?

I would like the text entry on a text field to be upper case only.
Is there a way to limit the text field to output only upper case letters or even limit the software keyboard to only display upper case letters to users?
let textFieldOutput = "Wait a moment, please."
let newString = textFieldOutput.uppercased()
//The string is now "WAIT A MOMENT, PLEASE."
Step-1. In Main.Storyboard select the textfield and click on attribute inspector
Step-2. Then in text input traits -> select 'All Characters' in Capitalization.
Changing the keyboard input type to All Characters does not prevent the user to switch back to lowercase letters (at least on iOS 13).
I use the following code (Swift 5.1) to capitalise only new characters added to the textfield instead of setting the full string over and over again as suggested in some other answers.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let firstLowercaseCharRange = string.rangeOfCharacter(from: NSCharacterSet.lowercaseLetters)
if let _ = firstLowercaseCharRange {
if let text = textField.text, text.isEmpty {
textField.text = string.uppercased()
}
else {
let beginning = textField.beginningOfDocument
if let start = textField.position(from: beginning, offset: range.location),
let end = textField.position(from: start, offset: range.length),
let replaceRange = textField.textRange(from: start, to: end) {
textField.replace(replaceRange, withText: string.uppercased())
}
}
return false
}
return true
}
You can change the text to upper case with
string.uppercaseStringWithLocale(NSLocale.currentLocale())
You can use the method textField:shouldChangeCharactersInRange:replacementString: to change the text.
There are at least two options:
Use the uppercaseString property of Swift's String class to generate an all-uppercase version of the text. This is a reasonable option if you want an upper case version of whatever is typed into the text field.
Implement the method textField(_:shouldChangeCharactersInRange:replacementString:) in the text field's delegate. Your implementation should do the replacement itself using an upper case version of the replacement string, and then return false. This is the approach you want if it's important that the text appear upper case in the text field.
You can change the string afterwards to uppercase using the below:
var newString = myString.uppercaseString
in swift 3:
var newString = myString.uppercased()
Swift 4.2:
There are two ways.
Firstly. Entered value from keyboard, you can do (upperCase) it in the programmatically.
yourTextField.text = yourString.uppercased()
Or story board force upper keyboard.
StorBoard->TextInputTraits->Capitalization->Select ALL Characters