Why does shouldChangeTextInRange get called multiple times using predictive input? - objective-c

The predictive-input of iOS8 calls the following delegate method of UITextView multiple times resulting in the selected word being inserted multiple times into the view.
This code works for typing single letters and copy/paste but not when using the predictive-input bar; why not?
- (BOOL) textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text
{
textView.text = [textView.text stringByReplacingCharactersInRange:range withString:text];
return false;
}
With this code; if I enter an empty UITextView and tap on "The" in the predictive text (autocomplete) view it inserts "The The" into the view by way of making three calls on this method. The parameters passed in for each call are:
range : {0,0} text : #"The"
range : {0,0} text : #"The"
range : {3,0} text : #" "
The space I can understand; but why insert "The" twice?

I got this same issue. It appears that with predictive text, setting textView.text in that delegate method triggers an immediate call to that delegate method again (this only happens with predictive text as far as I know).
I fixed it by just surrounding my textView changes with a guard:
private var hack_shouldIgnorePredictiveInput = false
func textView(textView: UITextView!, shouldChangeTextInRange range: NSRange, replacementText text: String!) -> Bool {
if hack_shouldIgnorePredictiveInput {
hack_shouldIgnorePredictiveInput = false
return false
}
hack_shouldIgnorePredictiveInput = true
textView.text = "" // Modify text however you need. This will cause shouldChangeTextInRange to be called again, but it will be ignored thanks to hack_shouldIgnorePredictiveInput
hack_shouldIgnorePredictiveInput = false
return false
}

I modified the accepted answer from Richard Venable, because, as JLust noted in the comment, that 3rd call with the space was throwing me off.
I added
private var predictiveTextWatcher = 0
And
if predictiveTextWatcher == 1 {
predictiveTextWatcher = 0
return false
}
if hack_shouldIgnorePredictiveInput {
predictiveTextWatcher += 1
hack_shouldIgnorePredictiveInput = false
return false
}
It's all pretty hacky, but better than nothing.
Best,

Not an answer, but a safer workaround:
class TextViewTextChangeChecker {
private var timestamp: TimeInterval = 0
private var lastRange: NSRange = NSRange(location: -1, length: 0)
private var lastText: String = ""
func shouldChange(text:String,in range: NSRange) -> Bool {
let SOME_SHORT_TIME = 0.1
let newStamp = Date().timeIntervalSince1970
let same = lastText == text && range == lastRange && newStamp - timestamp < SOME_SHORT_TIME
timestamp = newStamp
lastRange = range
lastText = text
return !same
}
}
still this didn't helped me because changing the textView from the shouldChangeTextInRange function changed the autocapitalizationType to .word (only by behaviour, not the field itself).

Related

tried check valid palindrom string problem solving using kotlin but there is one of the testcase is not passed i tried several times

this is the problem
A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.
Given a string s, return true if it is a palindrome, or false otherwise.
Example 1:
Input: s = "A man, a plan, a canal: Panama"
Output: true
Explanation: "amanaplanacanalpanama" is a palindrome.
Example 2:
Input: s = "race a car"
Output: false
Explanation: "raceacar" is not a palindrome.
myCode
class Solution {
fun isPalindrome(s:String):Boolean {
var s1 = s.toLowerCase()
var myStringBuilder = StringBuilder()
var n = s1.length-1
var n1=myStringBuilder.length
for ( i in 0..n) {
if (Character.isLetterOrDigit(s1[i])) {
myStringBuilder.append(s1[i])
}
}
for( i in 0 .. (n1/2)-1){
if(myStringBuilder[i] != myStringBuilder[n1-i-1]){
return false
}
}
return true
}
}
the first case passed
but this is not passed as per the result Input: s = "race a car result true expected is false
You're initialising n1 too early:
// create an -empty- StringBuilder
var myStringBuilder = StringBuilder()
...
// since it's empty, n1 == 0
var n1=myStringBuilder.length
You're setting it to the length of the StringBuilder contents before you've actually put anything in it. This is a simple value you're setting, it's not a reference to the length getter that will give the current value when you access it. You set it once and that's its value forever.
So your last loop, the one that checks if it's a palindrome or not, never actually runs:
// since n1 is 0, this is for (i in 0..0)
for( i in 0 .. (n1/2)-1){
You can fix it by initialising n1 when you've finished adding your content to the StringBuilder, so you can get its final length:
for ( i in 0..n) {
if (Character.isLetterOrDigit(s1[i])) {
myStringBuilder.append(s1[i])
}
}
// StringBuilder is complete, grab its final length
var n1 = myStringBuilder.length
// now you can use it
for (i in 0..(n1/2)-1) {
Just fyi, there's also an until operator that works like .. except it doesn't include the last value of the range. So you can write
for (i in 0 until (n1/2))
if you want!
You can use this simple solution.
fun isPalindrome(s:String):Boolean {
val str = s.filter { it.isLetterOrDigit() }.lowercase()
for (i in 0..str.length/2 ){
if (str[i]!=str[str.length-i-1])
return false
}
return true
}
Edit:
By the #cactustictacs comment, you can do this in much more simple way.
fun isPalindrome(s:String):Boolean {
val str = s.filter { it.isLetterOrDigit() }.lowercase()
return str == str.reversed()
}

SwiftUI: Is it possible to automatically move to the next textfield after 1 character is entered?

I trying to make a SwiftUI app where after entering one letter in a TextField the cursor automatically moves to the next TextField. The UI is pretty much like this.
In Swift/IB, it looks like this was done with delegates and adding a target like in this post:
How to move to the next UITextField automatically in Swift
But can't find any documentation for using delegates/targets in SwiftUI.
I tried following this post:
SwiftUI TextField max length
But this has not worked for me. Setting the .prefix(1) does not seem to make a difference. The TextField still accepts any amount of characters and when moved to the next TextField does not reduce the characters entered to only the first character.
In SwiftUI's current state, is it possible to automatically move to the next TextField after 1 character is entered?
Thanks for any help!
It can be done in iOS 15 with FocusState
import SwiftUI
///Sample usage
#available(iOS 15.0, *)
struct PinParentView: View {
#State var pin: Int = 12356
var body: some View {
VStack{
Text(pin.description)
PinView(pin: $pin)
}
}
}
#available(iOS 15.0, *)
struct PinView: View {
#Binding var pin: Int
#State var pinDict: [UniqueCharacter] = []
#FocusState private var focusedField: UniqueCharacter?
var body: some View{
HStack{
ForEach($pinDict, id: \.id, content: { $char in
TextField("pin digit", text:
Binding(get: {
char.char.description
}, set: { newValue in
let newest: Character = newValue.last ?? "0"
//This check is only needed if you only want numbers
if Int(newest.description) != nil{
char.char = newest
}
//Set the new focus
DispatchQueue.main.async {
setFocus()
}
})
).textFieldStyle(.roundedBorder)
.focused($focusedField, equals: char)
})
}.onAppear(perform: {
//Set the initial value of the text fields
//By using unique characters you can keep the order
pinDict = pin.description.uniqueCharacters()
})
}
func setFocus(){
//Default to the first box when focus is not set or the user reaches the last box
if focusedField == nil || focusedField == pinDict.last{
focusedField = pinDict.first
}else{
//find the index of the current character
let idx = pinDict.firstIndex(of: focusedField!)
//Another safety check for the index
if idx == nil || pinDict.last == pinDict[idx!]{
focusedField = pinDict.first
}else{
focusedField = pinDict[idx! + 1]
}
}
//Update the Binding that came from the parent
setPinBinding()
}
///Updates the binding from the parent
func setPinBinding(){
var newPinInt = 0
for n in pinDict{
if n == pinDict.first{
newPinInt = Int(n.char.description) ?? 0
}else{
newPinInt = Int(String(newPinInt) + n.char.description) ?? 0
}
}
pin = newPinInt
}
}
//Convert String to Unique characers
extension String{
func uniqueCharacters() -> [UniqueCharacter]{
let array: [Character] = Array(self)
return array.uniqueCharacters()
}
func numberOnly() -> String {
self.trimmingCharacters(in: CharacterSet(charactersIn: "-0123456789.").inverted)
}
}
extension Array where Element == Character {
func uniqueCharacters() -> [UniqueCharacter]{
var array: [UniqueCharacter] = []
for char in self{
array.append(UniqueCharacter(char: char))
}
return array
}
}
//String/Characters can be repeating so yu have to make them a unique value
struct UniqueCharacter: Identifiable, Equatable, Hashable{
var char: Character
var id: UUID = UUID()
}
#available(iOS 15.0, *)
struct PinView_Previews: PreviewProvider {
static var previews: some View {
PinParentView()
}
}

Include both assignment and test on assigned value in while loop for Kotlin

I'm looking to find the last line of a text file using a rather standard while-loop idiom I find often used in Java.
I have a less compact version working. But the one I would like to use does not appear to be valid syntax in Kotlin. My preferred method includes an assignment and a Boolean test on that assignment in the same line.
Admittedly this is a small matter, but I'm looking to better implement my Kotlin code.
fun readLastLine(file:File):String {
val bufferedReader = file.bufferedReader()
var lastLine=""
//valid
var current = bufferedReader.readLine()
while(current != null) {
lastLine=current
current = bufferedReader.readLine()
}
//return lastLine
//not valid...
//while((current=bufferedReader.readLine())!=null){
// lastLine=current
//}
//responding to comment below,
//preferred/terse answer using file.readLines
//this reads all the lines into a list, then returns the last
return file.readLines().last()
}
In Kotlin, an assignment is not an expression whose value is equal to the assigned value.
You can combine two statements using run function in Kotlin. This function returns the value of the the last expression.
var current = ""
while (run {
current = bufferedReader.readLine()
current != null
}) { // or while (run { current = bufferedReader.readLine(); current != null }) {
lastLine = current
}
However, you can further reduce the code using File.forEachLine() in Kotlin.
fun readLastLine(file: File): String {
var lastLine = ""
file.forEachLine { line ->
lastLine = line
}
return lastLine
}
Or shorter,
fun readLastLine(file: File): String {
var lastLine = ""
file.forEachLine { lastLine = it }
return lastLine
}
This uses BufferedReader and closes automatically internally: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/for-each-line.html

Calculator in Swift a method [rangeOfString]

I am making calculator in Swift for IOS 8. All operand and operator works very well. Only problem is with "."
For example, 192.168.0.1 is invalid. The "." should only display once. Instead 192.16801 is valid.
See below code -
//Assume user has entered 192.168
//User cannot press "." button again now, "." should not appear again
var display.text 192.168`
if display.text.rangeOfString(".") != nil{
println("exists")
}
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.delegate = self
}
//Textfield delegates
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text
switch string {
case "0","1","2","3","4","5","6","7","8","9":
return true
case ".":
let array = Array(textField.text)
var decimalCount = 0
for character in array {
if character == "." {
decimalCount++
}
}
if decimalCount == 1 {
return false
} else {
return true
}
default:
let array = Array(string)
if array.count == 0 {
return true
}
return false
}
}
}
the textField functions will return a value of true if you have entered a valid number, it will return a value of false if you input anything else than a number or if you enter a second “.”. If this functions returns false it will not change the value of the text field. Example: if a user (tries to) input 3.14a9 the a will be ignored and the textfield text will be 3.149. If a user inputs 3.45.12 the textfield will have a text value of 3.45.12
I’m not sure this is the question, but if you’re looking for a second “.” you can use the find function with a where for this:
let decimal: Character = "."
if let first = find(display.text, decimal)
where (find(display.text[first.successor()..<display.text.endIndex], decimal) != nil) {
println("second exists")
}
else {
println("none or one")
}
You can use regular expression to validate your textfield whenever its value changes.
Example of a regulator
/*
^ //begin string
([0-9]+)? //optional: has 0 or more digits
(\\.)? //optional: has a point
([0-9]+)? //optional: has 0 or more digits
$ //end string
*/
extension String {
func isValidNumber() -> Bool {
let regex = NSRegularExpression(pattern: "^([0-9]+)?(\\.)?([0-9]+)?$", options: .CaseInsensitive, error: nil)
return regex?.firstMatchInString(self, options: nil, range: NSMakeRange(0, countElements(self))) != nil
}
}
Used in the call-back of your UITextField:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return textField.text.isValidNumber()
}
var text = "192.168.2"
var isValid = true
let range = text.rangeOfString(".")
if let range = range {
let subText = text.substringFromIndex(range.endIndex)
let range2 = subText.rangeOfString(".")
if range2 != nil {
isValid = false
}
}
println("isValid: \(isValid)") // isValid: false

Displaying a fading balloon type message box

I need to have a balloon like message box that displays for a few seconds and then fades away (not disappears at once)
Please advise how to do this.
Thanks
Furqan
Try this:
Dim buttonToolTip As New ToolTip()
with buttonToolTip
.ToolTipTitle = "Button Tooltip"
.UseFading = True
.UseAnimation = True
.IsBalloon = True
.ShowAlways = True
.AutoPopDelay = 5000
.InitialDelay = 1000
.ReshowDelay = 500
.IsBalloon = True
.SetToolTip(Button1, "Click me to execute.")
End With
EDITED:
If you need a fading MessageBox, you can do this:
create a form that accepts a status and a text in constructor
swtich on status drawing correct icon and place text on form
create a timer and on Tick event check if it's time to close
if it's time to close call FadeForm method
Example:
public enum Status
{
None,
Error,
Question,
Warning,
Info
}
public class FadingForm: Form
{
dt: TDateTime;
public FadingForm(Status status, string msg)
{
this.lbl.Text = msg; // I imagine you have a Label named lbl
switch (status)
{
case Warning:
img.Image = warning; // I imagine you have a PictureBox named img
break;
case ...
}
dt = DateTime.Now;
tmr.Enabled = true;
}
public void Tmr_Tick()
{
if (DateTime.Now - dt) > limit
{
FadeForm(10); //Just an example
Close();
}
}
public void FadeForm(byte NumberOfSteps)
{
float StepVal = (float)(100f / NumberOfSteps);
float fOpacity = 100f;
for (byte b = 0; b < NumberOfSteps; b++)
{
this.Opacity = fOpacity / 100;
this.Refresh();
fOpacity -= StepVal;
}
}
}
I think this behavior depends on users windows settings, if you want to achieve this you can code a balloon message your self and decrease its opacity before closing it completely.