I have a TextInput inside a Modal with autoFocus={true}. As soon as Modal is opened TextInput is focused automatically but keyboard is not opening automatically.And surprisingly this is happening randomly. sometimes keyboard opens and sometimes not. This is happening in both emulator and real device.
Any suggestions to overcome this behavior? Thank You.
you can pass focus to TextInput using reference whenever a Modal is visible
<Modal onShow={() => {this.textInput.focus()}} >
<TextInput ref={input => {this.textInput = input}} />
</Modal>
I'm having the same problem currently. I used the solution suggestes by saumil and others previously; but adjusted for functional components:
import React, { useRef } from 'react';
...
let textInput = useRef(null);
<Modal onShow={() => { textInput.focus(); }} ...>
<TextInput ref={(input) => { textInput = input; }} ... />
</Modal>
It works, but I don't quite know what I'm doing (explanations welcomed). It's important to delete the autoFocus={true} to get consistent results.
Need to check though,
// Keyboard IpM
InputMethodManager imm = (InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE);
// input is TextInputEditText in here.
// adding a listener
input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean isFocused) {
if (isFocused) {
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
});
You can try focus method. Like this;
focus() {
this.textInputRef.focus();
}
render() {
return (
<TextInput
ref={ref => {
this.textInputRef = ref;
}}
/>
);
}
Now you can call focus function whenever you want. It will focus on the textinput and open the keyboard immediately.
This code is working for me. Use android:focusedByDefault="true"
<com.google.android.material.textfield.TextInputLayout
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:focusedByDefault="true" />
</com.google.android.material.textfield.TextInputLayout>
Related
I have 2 screens A and B, in screen A I have a TextInput, it's value is taken from another screen to this screen, its code looks like this
const ScreenA = (props) => {
const { position, suffix } = props;
const navigation = useNavigation();
const [textValue, setTextValue] = useState(props.name || '');
const navigationScreen = () => {
navigation.navigate('screenB');
};
return (
<>
<View>
<View>
<TextInput
value={textValue}
onChangeText={(text) => {
setTextValue(text);
}}
/>
</View>
<TouchableOpacity onPress={navigationScreen}>
<View>
<View style={styles.rectangle} />
</View>
</TouchableOpacity>
</View>
</>
);
};
export default ScreenA;
And after I press the button in screen A it moves to screen B, but after I go back from screen B to screen A, how can I keep my changed value at screen A,
The example is as follows: initially I go to screen A, the value of textValue will be "Abcd", then I edit the value of TextInput to be "A913" then I press submit button, and when in screen B rotates about screen A, i want the value of TextInput to be "A913" not "Abcd"
Can someone help me to solve this problem?
You should store your textinput value in Global state. and you can do so by using React Context or Redux
Imagine I have an application with StackNavigator, that is when user clicks something on Page 1 he/she is redirected to Page 2 (using Stack Navigator), etc.
I want to have an app bar, which is global per the application, that is, each page (Page 1, Page2, etc.) should show this app bar.
This is what is meant by app bar.
[![enter image description here][1]][1]
I found a component on documentation which looks like this:
<ToolbarAndroid
logo={require('./app_logo.png')}
title="AwesomeApp"
actions={[{title: 'Settings', icon: require('./icon_settings.png'), show: 'always'}]}
onActionSelected={this.onActionSelected} />
Does it mean I need to put this component inside render of each page separately? (Page 1, Page2). Like I said I want only a single toolbar for whole app.
Can someone show me some code example how to achieve this?
You could create a separate component called MyHeader.js and import to a file you want to use it. Give props to customize it. Example that used NativeBase:
MyHeader.js
import React from 'react';
import { Text, View, Image } from 'react-native';
import { Container, Header, Left, Body, Right, Title, Button, Icon } from 'native-base';
import styles from '../assets/style';
export default class MyHeader extends React.Component {
render() {
const goBack = this.props.goBackProp;
return (
<Container>
<Header>
<Left>
<Button
transparent
onPress={() => goBack()}
>
<Icon name="ios-arrow-back" />
</Button>
</Left>
<Body>
<Title> {this.props.title} </Title>
</Body>
<Right />
</Header>
</Container>
);
}
}
Now I could import it to any component like this:
import MyHeader from '../components/MyHeader';
export default class BlogScreen extends React.Component {
render() {
const {goBack} = this.props.navigation;
return (
<MyHeader goBackProp={goBack} title={'Blog'}/>
);
}
}
I am using FlatList component of ReactNative and it is working perfectly. Now i want to scroll it to bottom whenever user enters new element/comment.
<FlatList style={styles.commentList}
data={commentList}
keyExtractor={(item, index) => index}
renderItem={this.renderRow.bind(this)}
/>
Now whenever user enters new comment, commentList will be updated and FlatList also gets refresh, but i want to be scroll to the bottom so that user can read latest comment posted.
Check this out:
Its not my solution, but i got working using this method.
https://exp.host/#xavieramoros/snack-SyN4FJikz
source code:
https://github.com/xavieramoros/flatlist-initialScrollIndexIssue/blob/master/App.js
Expo 22 - RN 0.49
A great thanks to https://expo.io/#xavieramoros
Edited: If you just want to scroll to bottom then scrollToEnd (https://facebook.github.io/react-native/docs/flatlist.html#scrolltoend) is available in new RN.
use it like
<FlatList
ref={ (ref) => { this.flatList = ref; }}
data={this.data}
renderItem={ this.renderItem }
keyExtractor={ this.keyExtractor}
onScroll={ this.scrollToEnd}
/>
......
and in your method
scrollToEnd = () => {
const wait = new Promise((resolve) => setTimeout(resolve, 0));
wait.then( () => {
this.flatList.scrollToEnd({ animated: true });
});
}
Try FlatList with inverted prop as true. (might as well reverse the data array)
Since flatlist is inverted, the new data is added on the first position, and because of this update flatlist automatically scrolls to first position, which in your case is the bottom location
Lets say there is a login page with username\password TextFields and login Button. When the button is pressed a request is set to a server and ActivityIndicator is shown.
Currently I put StackLayout on top of all other controls not to give the user a possibility to click on them while processing the request. But in some cases TextField stays focused and the user can type there.
I'm already using a component to wrap all TextFields to show validation errors:
#Component({
selector: "field",
template: "<grid-layout><ng-content></ng-content>...</grid-layout>"
})
export class FieldComponent {
#ContentChild(NgModel) private input: NgModel;
...
}
My question is can I set isEnabled property to false on TextField inside ng-content from FieldComponent having NgModel or in some another way?
If it is impossible what is the best practices in this case to disable inputs when an app is busy?
Here is my solution for NativeScript+Angular:
setControlInteractionState() is recursive.
the TextField cursor is hidden (using native android API).
XML:
<GridLayout #mainGrid rows="*" columns="*">
<!-- Main page content here... -->
<GridLayout *ngIf="isBusy" rows="*" columns="*">
<GridLayout rows="*" columns="*" style="background-color: black; opacity: 0.35">
</GridLayout>
<ActivityIndicator width="60" height="60" busy="true">
</ActivityIndicator>
</GridLayout>
</GridLayout>
or
<GridLayout #mainGrid rows="*" columns="*">
<!-- Main page content here... -->
</GridLayout>
<GridLayout *ngIf="isBusy" rows="*" columns="*">
<GridLayout rows="*" columns="*" style="background-color: black; opacity: 0.35">
</GridLayout>
<ActivityIndicator width="60" height="60" busy="true">
</ActivityIndicator>
</GridLayout>
TypeScript:
import { Component, ViewChild, ElementRef } from "#angular/core";
import { View } from "ui/core/view";
import { LayoutBase } from "ui/layouts/layout-base";
import { isAndroid, isIOS } from "platform";
#Component({
templateUrl: "./SignIn.html"
})
export class SignInComponent {
#ViewChild("mainGrid")
MainGrid: ElementRef;
isBusy: boolean = false;
submit() : void {
try {
this.isBusy = true;
setControlInteractionState(<View>this.MainGrid.nativeElement, false);
//sign-in here...
}
finally {
this.isBusy = false;
setControlInteractionState(<View>this.MainGrid.nativeElement, true);
}
}
setControlInteractionState(view: View, isEnabled: boolean) : void {
view.isUserInteractionEnabled = isEnabled;
if (isAndroid) {
if (view.android instanceof android.widget.EditText) {
let control = <android.widget.EditText>view.android;
control.setCursorVisible(isEnabled);
}
}
if (view instanceof LayoutBase) {
let layoutBase = <LayoutBase>view;
for (let i = 0, length = layoutBase.getChildrenCount(); i < length; i++) {
let child = layoutBase.getChildAt(i);
setControlInteractionState(child, isEnabled);
}
}
}
}
NS 2.5.0
There are a couple way you can do this;
You can use a ngIf or binding on isEnabled to disable it based on a data bound value.
You can create a simple routine that you call (my preferred method).
require("nativescript-dom");
function screenEnabled(isEnabled) {
runAgainstTagNames('TextEdit', function(e) { e.isEnabled = isEnabled; });
runAgainstTagNames('Button', function(e) { e.isEnabled = isEnabled; });
}
The nativescript-dom plugin has the runAgainst*, or getElementBy* wrappers to talk to the native layer like you were talking to a html dom.
Full disclosure, I'm the author of nativescript-dom, it is one of the plugins that I use in almost every app/demo I do.
I found an easiest solution in Angular, so i am posting here for any future reference. First in app.component.html file i added a Grid and ScrollView like following:
<GridLayout>
<page-router-outlet></page-router-outlet>
<!-- hack to block UI -->
<ScrollView isUserInteractionEnabled="false" *ngIf="isLoading">
<ActivityIndicator busy="true"></ActivityIndicator>
</ScrollView>
</GridLayout>
Notice the page-router-outlet which is inside the Grid. By default it will be place at row="0". The next thing is ScrollView which has isUserInteractionEnabled set to false.
Now in your app.component.ts file add a a variable called isLoading and toggle it using some kind of events e.g RxJs Observable events
Expanding #KTCO's answer to get the size of the overlay exactly the same as the main grid:
import { Size, View } from "tns-core-modules/ui/core/view";
import { GridLayout } from "tns-core-modules/ui/layouts/grid-layout/grid-layout";
...
...
dialogSize: Size;
mainGrid: GridLayout;
...
submit() {
this.mainGrid = <GridLayout>this.MainGrid.nativeElement;
this.dialogSize = this.mainGrid.getActualSize();
.....
.....
<GridLayout *ngIf="isBusy" rows="auto" columns="auto">
<GridLayout rows="*" columns="*" [width]="dialogSize.width" [height]="dialogSize.height" style="background-color: black; opacity: 0.35">
</GridLayout>
<ActivityIndicator width="50" height="50" busy="true">
</ActivityIndicator>
</GridLayout>
I know this is a little old, but sometimes I just got to do things my way. If you want to accomplish this programmatically:
const excludedView = someViewToBeExcluded;
const enabler = (parentView:View, enable:boolean) => {
parentView.eachChildView(childView => {
if (childView != excludedView) {
enabler(childView, enable);
childView.isEnabled = enable;
}
return true;
});
};
enabler(page, false);
Note: This will not disable/enable the initial parentView (ie. the page in this example)
view xml(List view template)
<Alloy>
<ItemTemplate id="test">
<View onTouchstart="touch" width="Ti.UI.SIZE" height="Ti.UI.SIZE">
<Label>test</Label>
</View>
</ItemTemplate>
</Alloy>
controller js
function touch(e){
Ti.API.debug('touch!!');
};
not fire only android..(iOS is good)
and under don't use ListView code, no problem android(and iOS).
<Alloy>
<Window class="container">
<View onTouchstart="touch" width="Ti.UI.SIZE" height="Ti.UI.SIZE">
<Label>test</Label>
</View>
</Window>
</Alloy>
I do not know anyone solution?
There are no 'touchstart' method for ListViews, you could use 'dragstart' and 'scrollstart' as alternative.
Check out the available events:
http://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.ListView
I've tried once to add the 'change' event to a TextField inside the ItemTemplace, but I've to create an workaround:
Change event on TextField inside ListView Template
If it's not possible, you have to use the 'itemclick' and get the selected row:
$.list.addEventListener('itemclick',function(list) {
var row = $.list.sections[0].getItemAt(list.itemIndex);
if(row && row.title) {
row.title.text = 'hello world';
//when you click the View with 'cancel' bindId
if(list.bindId == 'cancel') yourCancelAction();
//when you click the View with 'save' bindId
//(if you need to pass the row index to the function,
//in order to update something on the clicked row with a external function)
if(list.bindId == 'save') yourSaveAction(list.itemIndex);
$.list.sections[0].updateItemAt(list.itemIndex,row);
}
list = row = null;
});