QuickType predictions take key strokes into account that should be blocked by my UITextFieldDelegate - cocoa-touch

I have a textField, in which I don't want to allow leading whitespace. So I implemented textField(textField:shouldChangeCharactersInRange:replacementString:) and block attempts which would change the text to something that starts with whitespace. This works as expected.
Unfortunately this messes up QuickType. Each time I press space in an empty field, which is then ignored by my textField, the Quicktype text gets prefixed with this space. To be more clear, the text that QuickType will insert get's prefixed, the additional whitespace is not shown in the UI of the QuickType bar.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text as NSString
let proposedText = currentText.stringByReplacingCharactersInRange(range, withString: string)
if !proposedText.isEmpty {
let firstCharacterString = String(proposedText[proposedText.startIndex]) as NSString
if firstCharacterString.rangeOfCharacterFromSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).location == 0 {
println("starts with whitespace \"\(proposedText)\"")
return false
}
}
return true
}
Here is some logging to see what's happening when I press space 5 times followed by press on the I'm QuickType suggestion:
starts with whitespace " " // pressing space key
starts with whitespace " "
starts with whitespace " "
starts with whitespace " "
starts with whitespace " "
starts with whitespace " I'm" // Inserted by QuickType after pressing the "I'm" suggestion
starts with whitespace " " // Inserted by QuickType as well
By inspecting the variables in that delegate method I can verify that the problem is indeed with the replacement string, that I get from the UITextField. It already contains the suggestion prefixed with whitespace.
Does anybody know how I can prevent that, or how I can "reset" QuickType suggestions?
A workaround would be to trim whitespace from multi character inserts, but first I want to see if I'm missing a way to handle the problem in a clean way.

With some more testing I came to the conclusion that this is a bug.
First I thought that Keyboard and QuickType are decoupled from the UITextField. But this is actually not the case. Changing the position of the cursor in a filled textField actually changes quicktype suggestions. So the textField actually communicates with quicktype.
So I filed a bug.
Bug at Apple: rdar://19250739
Bug at OpenRadar: 5794406481264640
In case somebody is interested in the workaround:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text as NSString
let proposedText = currentText.stringByReplacingCharactersInRange(range, withString: string)
if proposedText.hasPrefix(" ") {
// Don't allow space at beginning
println("Ignore text \"\(proposedText)\"")
// workaround
if textField.text == "" && countElements(string) > 1 {
// multi insert into empty field. probably from QuickType
// so we should strip whitespace and set new text directly
let trimmedString = proposedText.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
println("Inserted \"\(trimmedString)\" with Quicktype. ")
textField.text = trimmedString
}
return false
}
return true
}

Related

Jetpack Compose - ModalBottomSheet comes up with soft keyboard

I have a problem with ModalBottomSheet and it's on my work computer so I can't record it to you right now. So basically, after I give focus to one of my TextFields, my keyboard comes up and pushes all the content upwards so I can see the TextField that I'm writing to. When I'm hiding my keyboard I can see that my ModalBottomSheet hides too, but I never set it to come up.
So if you are familiar with this bug, please let me know your solutions.
My coworker, so he inserted a boolean that checks if keyboard is up or not and if it is, dont put ap modal bottom sheet.
You can use this method until this problem is fixed with an additional update.
You can use LaunchedEffect for this. Here is an example for you.
The important thing here is to disable the ModalBottomSheetDialog when the keyboard is opened and re-enable it half a second after the keyboard is closed.
You can trigger the required function by assigning a value to this variable when the keyboard is turned on, and then changing and checking this value when the keyboard is closed.
/*Change this value to "keyboard_on" when the keyboard is turned on and "keyboard_off" when the keyboard is closed again. You can give different names for different usage areas. That's why we're using a string, not a Boolean.*/
var taskCodeValue = remember { mutableStateOf("keyboard_off") }
var sheetOpener by remember { mutableStateOf(true) }
if (taskCodeValue.value == "keyboard_off"){
LaunchedEffect(taskCodeValue.value == "keyboard_off"){
delay(500)
sheetOpener = true
}
}else {
sheetOpener = false
}
/*
By adding the Scaffold, which includes ModalBottomSheet and other compose
elements, into a box, we enable them to work independently of each other.
*/
Box(modifier = Modifier.fillMaxSize()) {
Scaffold(
content = {}
)
if (sheetOpener){
ModalBottomSheetLayout(
sheetState = sheetState,
sheetContent = {}
) {}
}
}

Get the caret position for Blazor text input

I am working on a Blazor textarea input. What I want to achieve is whenever user types "#" character, I am going to popup a small window and they can select something from it. Whatever they select, I will insert that text into the textarea, right after where they typed the "#".
I got this HTML:
<textarea rows="10" class="form-control" id="CSTemplate" #bind="original" #oninput="(e => InputHandler(e.Value))" #onkeypress="#(e => KeyWasPressed(e))"></textarea>
And the codes are:
protected void InputHandler(object value)
{
original = value.ToString();
}
private void KeyWasPressed(KeyboardEventArgs args)
{
if (args.Key == "#")
{
showVariables = true;
}
}
protected void AddVariable(string v)
{
original += v + " ";
showVariables = false;
}
This worked very well. The showVariables boolean is how I control the pop-up window and AddVariable function is how I add the selected text back to the textarea.
However, there is one small problem. If I've already typed certain text and then I go back to any previous position and typed "#", menu will still pop-up no problem, but when user selects the text and the insert is of course only appended to the end of the text. I am having trouble trying to get the exact caret position of when the "#" was so I only append the text right after the "#", not to the end of the input.
Thanks a lot!
I did fast demo app, check it https://github.com/Lupusa87/BlazorDisplayMenuAtCaret
I got it - I was able to use JSInterop to obtain the cursor position $('#CSTemplate').prop("selectionStart") and save the value in a variable. Then use this value later in the AddVariable function.
you can set your condition in InputHandler and when you are checking for the # to see if it's inputed you can also get the length to see that if it's just an # or it has some characters before or after it obviously when the length is 1 and value is # it means there is just an # and if length is more than one then ...

Force NSLayoutManager to draw a glyph with a different font

I'm trying to force NSLayoutManager to draw a glyph at some range with different attributes but it still uses attributes set in NSTextStorage object.
I was trying to create NSGlyph from different font and replace it with the one in NSTypesetter's glyph storage. But it's useles - layout manager still draws that glyph with the font an the color specified in text storage attribute string.
public override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: NSPoint) {
// now set glyphs for invisible characters
if PreferencesManager.shared.shouldShowInvisibles == true {
var spaceRanges = [NSRange]()
let charRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
var substring = (self.currentTextStorage.string as NSString).substring(with: charRange)
let spacesExpression = try? NSRegularExpression(pattern: "[ ]", options: NSRegularExpression.Options.useUnicodeWordBoundaries)
let substringRange = NSRange(location: 0, length: substring.characters.count)
if let matches = spacesExpression?.matches(in: substring, options: .withoutAnchoringBounds, range: substringRange) {
for match in matches {
spaceRanges.append(NSRange(location: charRange.location + match.range.location, length: 1))
}
}
for spaceRange in spaceRanges {
let invisibleFont = Font(name: "Gill Sans", size: 11) ?? Font.systemFont(ofSize: 11)
// get any glyph to test the approach
var glyph = invisibleFont.glyph(withName: "paragraph")
// replce the glyphs
self.typesetter.substituteGlyphs(in: spaceRange, withGlyphs: &glyph)
}
}
// BUT LAYOUT MANAGER IGNORES THE FONT OF THE GLYPH I CREATED
super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)
}
How can I force layout manager to ignore those attributes at some range and to draw glyph I create with the font and color I need? I need this because I'm working on the most efficient way to draw the invisible characters.
You're directly modifying the typesetter here, but when you call super, it's going to re-do all that work. You likely to override getGlyphs(in:glyphs:properties:characterIndexes:bidiLevels:) instead, calling super and then swapping any glyphs you want. Alternately you might call setGlyphs(...) here before calling super.
See Display hidden characters in NSTextView for an example of what you're trying to do using deprecated methods. I am fairly certain that replaceGlyphAtIndex is replaced by setGlyphs.

Swift Error while trying to print variable from alert-view textfield

I have this code here that runs an alertview and shows a textfield asking for the subject name
alertController.addAction(UIAlertAction(title: "Ok", style: .Default, handler:{ (alertAction:UIAlertAction!) in
var textf= alertController.textFields[0] as UITextField
self.items += [self.textf]
println(self.items)
println(textf)
self.view .setNeedsDisplay()
}))
And then Im declaring the variables items and textf out of that function at the top of the code using this code
var items = ["English"]
let textf = ""
but the problem is that when I run my app and click on my button that shows the alertview and i type in my subject and click ok I get this error when it tries to print out string. It just prints out this error and it does not close my app
<_UIAlertControllerTextField: 0x7f9d22d1c430; frame = (4 4; 229 16); text = 'irish'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7f9d22cc55f0>; layer = <CALayer: 0x7f9d22d1c6e0>>
It says in the error the text I typed in but It is not adding it to the variable because when i print out the array items it prints out
[English, ]
and not
[English, irish]
This is weird...
var textf= alertController.textFields[0] as UITextField
self.items += [self.textf]
var textf and self.textf are two different variables. It looks like self.textf is the empty string you set outside your function. So an empty string is what's getting added to the items array.
You also have another problem. You seem to want a string, but your var textf value is a UITextField. You need to snag the text property if you wan the string.
This confusion is why I like explicit typing. If you are expecting a String and have a UITextField the compiler will catch that if you explicitly type your variables.
Which means you need something more like this:
var items: [String] = ["English"]
var textf: UITextField? = nil // this variable optionally holds a text field
// later, inside some function...
func somefunc() {
alertController.addAction(UIAlertAction(title: "Ok", style: .Default, handler:{ (alertAction:UIAlertAction!) in
self.textf = alertController.textFields[0] as UITextField
// append() is preferable to += [], no need for a temporary array
self.items.append(self.textf!.text)
println(self.items)
println(textf)
self.view.setNeedsDisplay()
}))
}

Dynamically setting passwordMask in Titanium

Since Titanium doesn't allow you to manually change the hintText colour of a textfield, I have to set hintText manually. Because of this, I have to dynamically change the passwordMask setting on one of fields I'm using.
However, I'm getting weird behaviour and I can't tell if I'm doing something wrong, or if it's a bug in Titanium.
So, here's my markup:
<TextField id="password" onFocus="passwordFocusEvent" onReturn="passwordReturnEvent" onBlur="passwordBlurEvent" value="password"></TextField>
And some of my controller code:
function passwordFocusEvent(e) {
slideViewUp();
if (e.value === 'password') {
e.source.setPasswordMask(true);
e.source.value = '';
}
}
function passwordBlurEvent(e) {
if (!e.value) {
e.source.setPasswordMask(false);
e.source.value = 'password';
}
}
function passwordReturnEvent(e) {
slideViewDown();
passwordBlurEvent(e);
}
What happens is bizarre. When I focus on the password field, it remains plain text. I enter some text, then click off to another field, stays as plain text.
I click back to the password field, it's STILL plain text.
Now here's the weirdness. Up to this point, I would just assume it's not working. However, when I click off this second time, the passwordMask is set.
Major WTF.
I even tried targeting the field directly using $.password.passwordMask = true; but same thing.
Unfortunately, you cant do this. According to the docs on Ti.UI.TextField in the fine print;
Note: on iOS, passwordMask must be specified when this text field is created.
Its not all bad news though, there are a couple ways you can approach this, one option is to make the password mask yourself, by listening to the change event:
var theStoredPassword = '';
$.password.addEventListener('change', function(e) {
var newpass = e.source.value;
if(newpass.length < theStoredPassword.length) {
// Character deleted from end
theStoredPassword = theStoredPassword.substring(0, theStoredPassword.length-1);
} else {
// Character added to end
theStoredPassword += newpass.substring(newpass.length-1);
}
// Mask the text with unicode ● BLACK CIRCLE, 25CF
$.password.value = new Array(newpass.length + 1).join('●');
});
Another option, would be to have two text fields and swap them out whenever the user focuses the password field, the top one would have the custom hinttext, the bottom one would be passwordMasked. In fact thats probably way easier than what I just coded up. :-)