How do I control the auto-scroll of an iOS Xamarin editor when the soft keyboard appears? - ios

I'm encountering some bizarre behaviour in a Xamarin Forms iOS Editor.
I have needed the editor to expand horizontally with the width of the text, rather than wrapping.
To achive this, I placed the Editor inside a ScrollView:
<local:ScrollViewEx x:Name="svContent" HorizontalScrollBarVisibility="Default" Orientation="Both"
BackgroundColor="Transparent">
<local:EditorEx x:Name="edContent"
VerticalOptions="Fill" HorizontalOptions="Fill"
WrapLongLines="False"
BackgroundColor="Transparent" />
</local:ScrollViewEx>
In the editor renderer, everytime the text changes, I resize the editor's height and width by getting new dimensions from the following method:
Size GetSizeThatFits()
{
CGSize newSize = Control.SizeThatFits(new CGSize(nfloat.MaxValue, nfloat.MaxValue));
return new Size(newSize.Width, newSize.Height);
}
This all works fine.
But when the editor takes focus and the soft keyboard appears, the system will always horizontally scroll to the maximum x value of the line where the cursor sits. This can cause the cursor to be moved offscreen, and will obviously create confusion for users.
I have tried to control the scrolling by holding the x value in place and only allowing an autoscroll of Y as follows:
private void Editor_Focused(object sender, FocusEventArgs e)
{
double x = sv.ScrollX;
Device.BeginInvokeOnMainThread(() =>
{
sv.ScrollToAsync(x, sv.ScrollY, false);
});
}
This succeeds in holding my x value in place, but now Y for some reason is being automatically set to zero, ignoring the value of sv.ScrollY.
Any ideas?
UPDATE 01
I've just tested the app on an iPhone12 device and the problem is even worse.
With every keystroke the scroll will jump to a different part of the screen.
Is there anyway to completely decouple the keyboard and my Editor/ScrollView scrolling? It would be easier for me to write my scrolling behaviour completely by myself than to keep fighting with the built in behaviour which is not making sense.
UPDATE 02
I may be getting a little closer to my goal.
By subclassing my UITextView and enclosing UIScrollView and overriding their scroll related methods I've manage to kill off most of their unpredictable scroll behaviour:
public override void ScrollRectToVisible(CGRect rect, bool animated)
{
//do nothing
}
public override void SetContentOffset(CGPoint contentOffset, bool animated)
{
//do nothing
}
However some remains. The following scenario is causing problems:
TextChanged event
Reconstruct a UIStringAttributes based on new text, and assign it to Control.AttributedText (Scroll has stayed stable up to this point)
Control.TextStorage.BeginEditing();
Control.AttributedText = astr;
Control.TextStorage.EndEditing();
The next click on the editor to trigger cursor position / SelectionChange however causes an editor scroll setting ScrollY, and sometimes ScrollX to zero.
Why????
Step 3 is hard for me to manage because SelectionChange occurs with both a click on the editor and the entry of text. It also occurs before a TextChange so is hard to predict the cause of SelectionChange.
In addition, even if I bypass Step 2 and don't manually reset my AttributedText, the jump at Step 3 still occurs.
Any ideas?

Related

Unable use a TextField at the bottom of the screen in a LazyColumn

I have this code
setContent {
val items = mutableListOf<Int>().apply {
(1..100).forEach { add(it) }
}
LazyColumn {
items(items) { item ->
TextField("$item", {})
}
}
}
With android:windowSoftInputMode="adjustResize" in my AndroidManifest.xml.
If I click on a TextField at the top of the list, I can enter text fine.
If I click on a TextField near the bottom of the screen, the keyboard appears momentarily, then disappears quickly after, and prevents me from entering text.
How can I enter text when the TextField is at the bottom of the screen? Thanks!
Your keyboard disappears after having appeared for a brief, shiny moment. Here's the reason:
You tap the TextField, it calls it's built-in focus requestor, and requests the focus from the OS by calling appropriate (or inappropriate, who's to say) internal methods, as a result of which, the keyboard pops out (again, built-in mechanism.) and you can type all the more you want... AS LONG AS THE FIELD IS VISIBLE; or in Compose terms, long as the Composable holding the requestor, is in composition.
It should be clear, that once the Composable that owns the focus requestor goes off the screen, it is destroyed (well not every time, there are only certain specific cases when it is destroyed..., but yours is one of them), and when the Composable is destroyed, the focus requestor is destroyed. No focus requestor, no focus owner - the keyboard vanishes.
Now this is really important because you are using a LazyColumn, a lazy Composable, infamous for its merciless slaughtering of the Composables that are no longer visible to the user. HENCE, as long as it is the top (or other "visible") textfield that is in concern, it stays and works as expected. However, the bottom textfield, as the keyboard pops up, goes so much out of the visible bounds, that it gets slaughtered (recycled is the technical term, but what fun is that?), taking away the purpose of the keyboard.
Now, the lazy column, like any criminal, left a clue behind, and didn't notice it, which lead us to catch him red-handed. It is quite interesting, really, to see the column in action.
Now, workarounds include using a column, instead of a lazy column; creating your own lazy column using a custom lazy Composable, and manually handling the heap size for the Composable so the final field doesn't go out of composition.
That's it, you're cured.
Just leaving another answer, since OP didn't verify other purpose of him specifying adjustResize,if whether removing or changing it will be fine, but another solution aside from the mentioned comment about different API version is specifying adjustPan, which worked in my case, either
via AndroidManifest
android:windowSoftInputMode="adjustPan"
or programmatically
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
Same code base but with adjustPan
This issue is fixed in the current alpha version 1.4.0-alpha05
https://issuetracker.google.com/issues/179203700

React Native - Auto scroll down with webview text being input

I'm using react-native-pell-rich-editor which is built upon the WebView component. As soon as I get to the bottom of the visible WebView (after typing about 20 lines) the text that I'm typing into the RichEditor is appearing below the visible area - so the text is being input, but I just can't see it! I have to scroll down every new line to see what I'm typing.
The KeyboardAvoidingView is pushing up my RichEditor (WebView) when the RichEditor is focused.
Is there a way to auto scroll down to where the cursor is when typing? Perhaps there is a better solution?
<ScrollView>
<KeyboardAvoidingView behavior={'padding'}>
<RichToolbar
actions={[ actions.keyboard, actions.setBold, actions.setItalic, actions.setUnderline, actions.setStrikethrough, actions.blockquote, actions.code, actions.alignCenter, actions.alignLeft, actions.alignRight ]}
editor={that.richText}/>
<RichEditor
initialContentHTML={messageBody}
initialFocus={true}
placeholder={'Compose email'}
ref={that.richText}/>
</KeyboardAvoidingView>
</ScrollView>
There are two parts to the way I solved this problem. I did not use KeyboardAvoidingView.
I also placed the RichEditor inside of a ScrollView, but called the ScrollView.scrollToEnd() method after the editor is initialized so that the cursor would be above the keyboard. This happens after a short timeout after calling RichEditor.focusContentEditor in editorInitializedCallback. Note: doing it this way means you do not need to use the initialFocus prop.
For moving the position while the user is typing, call ScrollView.scrollTo() in the ScrollView's onContentSizeChanged prop. You can use the dimension values that are passed to this prop's function to calculate how many pixels to scroll based on the previous dimensions and scroll position stored in the component state.

Vaadin's SplitLayout.setSplitterPosition(80) only works the first time; subsequent calls do not seem to respond

In Vaadin 12, I have created a button which, when clicked, sets the split layout position to some non-zero, non-100 value, as shown below:
btnHelp.addClickListener(event -> {
log.info("info pressed");
MainApp.sp.setSplitterPosition(80);
MainApp.iFrameHelp = new Html( "<iframe src=\"https://docs.readthedocs.io/en/latest/intro/getting-started-with-sphinx.html/intro/getting-started-with-sphinx.html\"></iframe>");
//btnHelp.setIcon(new Icon(VaadinIcon.INFO_CIRCLE));
});
This works great. However, if I pretend to be a user and, via the Chrome browser, I adjust the split layout (by dragging the vertical layout) such that I "close" (or just reduce the size of) the second vertical "panel", and THEN I click on the button again, it does NOT seem to obey the command to reset the splitter position to 80. It only seems to obey the command on the first call. Is this a bug? If so, is there a workaround? (Or, should I do this differently?)
This is a side effect of https://github.com/vaadin/vaadin-split-layout-flow/issues/50. What happens is basically that the server-side still believes that the split position is set to 80 which makes it ignore the setSplitterPosition(80) call.
You can work around this by using low-level APIs to set the position in a way that bypasses the server's dirty checking logic:
MainApp.sp.getPrimaryComponent().getElement().executeJavaScript(
"this.style.flexBasis='80%'");
MainApp.sp.getSecondaryComponent().getElement().executeJavaScript(
"this.style.flexBasis='20%'");

Set prompt text to the center of TextArea control in JavaFX

I was wondering if there is a way to set prompt text location in TextArea.
Basically I am trying to create the similar effect as ListView Placeholder does. It is just to keep consistency in UI, so that everything mostly would look similar.
Any suggestions with this.
I think you can only do this by a dirty hack:
Set alignment to center.
Depending on your desired behavior either add a focus listener which sets the alignment to left again when focused and back to center when focus left.
text.focusedProperty().addListener((p,o,n)->{
if(n){
text.setAlignment(Pos.CENTER_LEFT);
}else {
text.setAlignment(Pos.CENTER);
}
});
Or add a keylistener to get the left aligned text while typing(and a focus-left listener to reset it if needed, eg. empty)
setOnKeyPressed(e->text.setAlignment(Pos.CENTER_LEFT));

BlackBerry software keyboard listener on OS 4.5 (or later) compatible code

I am developing an app which supposed to work on devices that have OS 4.5 or later. In my application I need to know when the virtual keyboard is visible or invisible. Because if virtual keyboard is visible, the text area which the user is supposed to type in is behind the keyboard. If I could determine the moment of virtual keyboards state changed, I could refresh the screen and move the text area upper location.
Is there a way to do that?
Edit: the next button is at the status panel. The edit field is at the custom horizontal field manager.
When I touch the edit field, the virtual keyboard opens and the contents of the edit field is lost.
There is no way to do that with the same code. You need to divide your code in two. One of them handles 4.5 - 4.7. The other handles 4.7 and later.
You can add a keyboard listener to 4.7 (and later) code that should check whether the screen changes in a continuous thread. It is not good, but it can work.
You have two choices. The first choice is better:
Figure out an invariant that works with the keyboard visible or hidden. The screen layout method is invoked when the visibility state of the keyboard changes, and the vertical size is reduced for a visible keyboard. If your invariants take advantage of that then you can just implement the logic in the screen layout method.
In this case, I would suggest a layout method that always keeps the 'next' button at the bottom of the screen, and puts the username textbox in the center of the remaining space.
Use conditional compilation so you can write code that makes reference to the VirtualKeyboard class on OS 4.7+, and that code goes away in the older BlackBerry releases. 4 July: by conditional compilation, I mean use the BlackBerry preprocessor.
There is no event for this, but you can determine current state of virtual keyboard and set required state.
For example hide it
if(VirtualKeyboard.isSupported() == true){
VirtualKeyboard keyboard = getVirtualKeyboard();
if(keyboard != null)
keyboard.setVisibility(VirtualKeyboard.HIDE);
}
It is a quite challenging job. However, I believe there is no direct API or way to determine the virtual Keyboard state. The only way is to override the setLayout() method and determine if the screen width and height has been changed. And also you need to check the GUI layouts of your screen.
Set the VERTICAL_SCROLL style for the Manager which holds the EditField, or you can use a Screen with VERTICAL_SCROLL style. Doing this, the EditField automatically scrolls when the keyboard is displayed.
Use following class, maybe this is helpful for you:
class FocusableManager extends MainScreen implements FocusChangeListener
{
private BasicEditField b;
public FocusableManager()
{
VerticalFieldManager vfm=new VerticalFieldManager(VERTICAL_SCROLL);
vfm.add(new ButtonField("first"));
b=new BasicEditField();
b.setFocusListener(this);
vfm.add(b);
vfm.add(new ButtonField("second"));
vfm.setMargin(250,0,0,0);
add(vfm);
}
public void focusChanged(Field field, int eventType)
{
if(field==b)
{
if(eventType==1)//when edit field gain focus
{
VirtualKeyboard virtKbd;
virtKbd = getScreen().getVirtualKeyboard();
virtKbd.setVisibility(VirtualKeyboard.SHOW_FORCE);
}
else if(eventType==3)//when edit field lost focus
{
VirtualKeyboard virtKbd;
virtKbd = getScreen().getVirtualKeyboard();
virtKbd.setVisibility(VirtualKeyboard.HIDE_FORCE);
}
}
}
}

Resources