DismissSoftInput() for search-bar for Nativescript-Vue - nativescript

Explaining the problem:
The search-bar has no way of dismissing an open keyboard. This makes
the search-bar a quite unusable as the normal user pattern is that the
user search for something and then press the item and gets navigated
there. On Android (at least on >= 5.x), the open keyboard will
continue to stay open, even on the new page.
Referring to the question on Github, anyone how to do that for Nativescript-Vue and not for Nativescript with Typescript
Updated:
Added Playground: https://play.nativescript.org/?template=play-vue&id=hrrcW9
If I minimize the app, then click on it again, the keyboard pops open again.

As you could already see in the linked issue, the feature request is closed as completed. dismissSoftInput() is a method on SearchBar now that hides the keyboard.
If you have issues still, please share your code.
Update:
It's the default behaviour of Android to focus first focusable element on fragment / activity. Adding event listeners / timeouts to remove focus from each screen might be annoying, I would prefer using a auto focus view as first element (which will not have any impact on the screen design) of my layout, that would prevent auto focusing on my text fields / search bar.
import { View } from "tns-core-modules/ui/core/view";
export class AutoFocusView extends View {
createNativeView() {
if (typeof android !== "undefined") {
const linearLayout = new android.widget.LinearLayout(this._context);
linearLayout.setFocusableInTouchMode(true);
linearLayout.setFocusable(true);
return linearLayout;
}
return super.createNativeView();
}
onLoaded() {
super.onLoaded();
if (typeof android !== 'undefined') {
this.requestFocus();
}
}
requestFocus() {
if (typeof android !== "undefined") {
const nativeViewProtected = this.nativeViewProtected;
nativeViewProtected.requestFocus();
}
}
}
Playground Sample

Related

How to suspend UI / App Shell updates when manually removing and adding element to App Shell in Xamarin.Forms?

My Question:
In Xamarin.Forms 4.2+, can I suspend the App Shell in any way while I am manipulating it? Or can I suspend the whole UI layouting and rending for an instance?
My Situation:
I am creating an App with Xamarin.Forms where I use the new Shell Navigation. Cause I change the Flyout Menu during app runtime, I want to add and remove some of the FlyoutItem by code.
As an example, I have a LoginPage which I want to replace by a UserProfilePage in the App Menu (Flyout Menu). I always have an AppInfoPage in the menu.
Whenever I remove a FlyoutItem, Shell wants to display the next item. So when I remove the LoginPage, Shell displays AppInfoPage or at least calls the constructor and executes the overload of OnAppearing on the AppInfoPage. OnAppearing then does a lot of things to prepare the App info, which is not needed now cause the page will be OnDisappearing just a few ticks later.
Most UI frameworks have some function like this to avoid unneeded UI layouting and rendering. I tried setting IsVisible = false, IsBusy = true and calling BatchBegin(), but none of them helped me.
Code Example:
Check this simplified example, see the TODOs.
private static void SyncAppShell()
{
try {
// TODO Here I want to disable the automatic "navigation on menu modification"
Current.Items.Add(CreateFlyoutItem($"{flyoutIdPrefix}-{nameof(LoginPage)}",
resources[nameof(AppStrings.LoginPage)],
NavigationConstants.LoginPage,
new LoginPage()));
Current.Items.Add(CreateFlyoutItem($"{flyoutIdPrefix}-{nameof(AppInfo)}",
resources[nameof(AppStrings.AppInfo)],
NavigationConstants.AppInfo,
new AppInfo()));
Current.Items.Remove(_staticReferenceToLogoutPage);
}
finally
{
// TODO Here I want to enable the automatic "navigation on menu modification"
}
}
private static FlyoutItem CreateFlyoutItem(string id, string title, string route, ContentPage page, bool isEnabled = true)
{
var flyoutItem = new FlyoutItem { Title = title, StyleId = id, IsEnabled = isEnabled };
flyoutItem.Items.Add(new ShellContent { Route = route, Content = page });
return flyoutItem;
}

How to detect if user requested a tab change on TabbedPage in Xamarin.Forms

I have a Xamarin.Forms application which uses a TabbedPage, let's call it T, T consists of 3 ContentPage children A, B and C. Since the usere has the possibility to edit some data on tab B, I want to notify user before leaving tab in order to allow him to cancel the navigation change and save changes first or to discard changes and leave. So far I have managed to override OnBackButtonPressed() method and the navigation bar back button (which would exit TabbedPage). However I quickly noticed that I am still loosing changes when switching between tabs. I would like to override the click on new tab, so I could first present user with the leaving dialog and the skip the change or continue with it. What would be the best way to do this? I am currently working only on Android platform, so solutions on the platform level are also acceptible.
Thank you for your suggestions and feedback :)
I do not think there is an easy way to do this ,
you can use OnDissappearing and OnAppearing for the pages, that is as easy as it gets .
However I think you are using the wrong design.
Having tabs are ment to make it easier to navigate between pages, if you are going to notify the user when changing the tabs then it would be annoying . If I were you i would save the data for each page locally. so when you get back to the page you will have the data anyway.
So in the end I followed the advice of Ahmad and implemented the persisting of data on individual tabs so they are not lost when tabs are switched. (I no longer refresh input fields from data from model when OnAppearing is called).
But in order to know if there are some unsaved changes on my ChildB page, I had to implement the following procedures:
I created the method HandleExit on my ChildB page, which checks for unsaved changes in fields (at least one value in input fields is different from the ones in stored model) and the either prompts the user that there are unsaved changes (if there are some) or pops the navigation stack if there are no changes.
private async Task HandleExit()
{
if(HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if(!action)
{
return;
}
}
await Navigation.PopAsync();
}
Since there are two ways on how user can return from Tabbed page (pressing the back button on device or pressing the back button in navigation bar, I had to:
A: override the back button method on my ChildB page, so it calls the HandleExit method. But since Navigation.PopAsync() needs to be called on UI thread, I had to explicitly execute the method on UI thread as written below:
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B: Since there is no way to intercept the navigation bar back button on the ContentPage, I had to intercept the event on the platform level (Android) and then pass the event to the ContentPage if necessary via MessagingCenter. So first we need to intercept the event, when navigation bar button is pressed in one of the child pages and send the event via MessagingCenter. We can do that but adding the following method in our MainActivity.cs class:
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332)
{
// retrieve the current xamarin forms page instance
var currentpage = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault();
var name = currentpage.GetType().Name;
if(name == "ChildA" || name == "ChildB" || name == "ChildC")
{
MessagingCenter.Send("1", "NavigationBack");
return false;
}
}
return base.OnOptionsItemSelected(item);
}
Now whenever we will press the navigation bar back button in one of the child pages (ChildA, ChildB, ChildC) nothing will happen. But the button will work as before on the rest of the pages. For the second part of solution we need to handle the message from MessagingCenter, so we need to subscribe to it in our ChildB page. We can subsribe to the message topic in OnAppearing method as follows:
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
Be careful to unsubscribe to the topic in OnDisappearing() otherwise strange things could happen, since there will be references left to your ContentPage even if you pop it from your navigation stack.
Now that we have handled both requests for back navigation in our ChildB page, we also need to handle them in all of remaining child pages (ChildA, ChildC), so they will know if there are unsaved changes in ChildB page, even if it is currently not selected. So the solution is again compraised of handling the device back button, and navigation bar back button, but first we heed a way to check if ChildB has unsaved changes when we are on one of the remaining pages, so we again write HandleExit method but this time it is as follows:
private async Task HandleExit()
{
var root = (TabbedPage)this.Parent;
var editPage = root.Children.Where(x => x.GetType() == typeof(ChildB)).FirstOrDefault();
if(editPage != null)
{
var casted = editPage as ChildB;
if (casted.HasUnsavedChanges())
{
var action = await DisplayAlert("Alert", "There are unsaved changes, do you want to discard them?", "Discard changes", "Cancel");
if (!action)
{
return;
}
}
}
await Navigation.PopAsync();
}
The only thing that remains now is to handle both navigation back events inside remaing child pages. The code for them is the same as in the actual ChildB page.
A: Handling the device back button.
protected override bool OnBackButtonPressed()
{
Device.BeginInvokeOnMainThread(new Action(async () =>
{
await HandleExit();
}));
return true;
}
B: Subscribing to topic from MessagingCenter
MessagingCenter.Subscribe<string>(this, "NavigationBack", async (arg) => {
await HandleExit();
});
If everthing has been done correctly, we should now be prompted with a dialog on any of the child pages if there are unsaved changes on the ChildB page. I hope this will help somebody in the future :)

Navigation Drawer back button Xamarin

I am using this binding for this awesome Material Drawer Library by MikePenz.
I have implemented the Navigation Drawer with this library and I have also managed to change the hamburger menu to the back arrow when I go level deep. Now I have some problems to get the back arrow to work correctly. When I click on the back arrow, rather than going back to the previous page, it opens up the navigation drawer.
After looking into the original library, I have identified, the following code is responsible to manage the back arrow button. I would appreciate , if someone can help me a bit to write this listener code in C#.
.withOnDrawerNavigationListener(new Drawer.OnDrawerNavigationListener() {
#Override
public boolean onNavigationClickListener(View clickedView) {
//this method is only called if the Arrow icon is shown. The hamburger is automatically managed by the MaterialDrawer
//if the back arrow is shown. close the activity
AdvancedActivity.this.finish();
//return true if we have consumed the event
return true;
}
})
Here is the binding libray that I use : MaterialDrawer-Xamarin
And this is the link to the original Library : MaterialDrawer
Try something like this:
var result = new DrawerBuilder()
.WithActivity(this)
.AddDrawerItems(
//Add some items here
new DividerDrawerItem()
)
.WithOnDrawerNavigationListener(this);
and implement Drawer.IOnDrawerNavigationListener in your activity like this:
public bool OnNavigationClickListener(View clickedView)
{
this.Finish();
return true;
}

Show modal page on only a portion of the screen

I am developing an iPad app using Xamarin.Forms.
I would like my settingspage to be modal so it lay over the previous page like a popup.
I have been trying all solutions I could find and most of them seems to recommend me to call this from a button:
await Navigation.PushModalAsync(ModalSettingsPage);
What happens when I use it is that my settingspage comes in from below as a modal page but not as a popup, it covers the entire screen.
This is my current code:
//Setup button (action bar)
ToolbarItems.Add(new ToolbarItem
{
// Text = "Setup",
Icon = "settings1.png",
Order = ToolbarItemOrder.Default,
Command = new Command(() => Navigation.PushModalAsync(new ModalSettingsPage())) //Action to perfome on click , open modal view
});
Also, does anyone now if there is any good way to positions the ToolbarItems? I have two items and would like each one to be positioned at each side, but by default they are both positioned to the right.
With the evolution of Forms (currently 4.5.0), it has now become simple to push a modalpage which is not fullscreen. Use the following in your code-behind of your xaml contentpage:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace YourAppWithAModalPage
{
public class ModalPage : ContentPage
{
public ModalPage()
{
// iOS Solution for a ModalPage (popup) which is not fullscreen
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FormSheet);
}
}
}
There is nothing pre-made in Forms to give you a popup like I think you want. Instead, you would have to create this or use a third-party solution. For example, here is a "popup" implementation that might work for you.

In NativeScript on Android, how do I prevent a SearchBar from gaining focus on page load?

I have a SeachBar inside a ScrollView. In iOS all is good. On Android the ScrollView automatically scrolls to the SearchBar, adds focus to it and displays the soft keyboard. I can hide the softkeyboard by adding android:windowSoftInputMode="stateHidden" as an activity in the AndroidManifest.xml file but I can't work out how to prevent the focus (and hence the auto scroll). Any help would be much appreciated.
Using Angular2:
app/my.component.html
<StackLayout (loaded)="onSearchLayoutLoaded($event)">
<SearchBar hint="search here" (loaded)="onSearchBarLoaded($event)">
</SearchBar>
</StackLayout>
app/my.component.ts
onSearchLayoutLoaded(event) {
if (event.object.android) {
event.object.android.setFocusableInTouchMode(true);
}
}
onSearchBarLoaded(event) {
if (event.object.android) {
event.object.android.clearFocus();
}
}
This eliminates unnecessarily having to use template reference variables.
For Android you need to do a couple of things. If you were using a native Android layout, lets say LinerLayout you would set android:focusableInTouchMode="true" and that should work for most use cases. So with NativeScript you're going to use the associated method on the parent of your SearchBar and then call `clearFocus() on the searchbar.
Example
function removeSearchFocus() {
// get the parent of the searchbar
var parent = page.getViewById('parentLayout');
var searchBar = page.getViewById('mySearchBar');
if (parent.android) {
parent.android.setFocusableInTouchMode(true);
parent.android.setFocusable(true);
searchBar.android.clearFocus();
}
}
Then attach this function to one of the page navigation event, or dump the important pieces into a current page event you might already have in your app. Just assign the parent an ID and the SearchBar so you can get it by the ID and this should work.
How about adding a endEditing to the page loaded or loading event?
searchBar = page.getViewById('your-searchbar');
searchBar.ios.endEditing(true);
or set focus to another element, where you want the focus to be, e.g.
somethingElse = page.getViewById('your-top-bar');
somethingElse.focus();

Resources