I have this code for custom keyboard.
Its xamarin.forms for android.
I want the keyboard to be presented from bottom of the page and to raise the Entry if needed.
the result is that the keyboard is presented on top of the page and covers the entry if the entry is on the top.
~
public class CustomEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
Android.InputMethodServices.Keyboard numericKeyboard = new Android.InputMethodServices.Keyboard(Control.Context, Resource.Xml.keyboard2);
CustomKeyboardView numericKeyboardView = new CustomKeyboardView(Control.Context, null);
numericKeyboardView.Id = Control.Id;
numericKeyboardView.Keyboard = numericKeyboard;
numericKeyboardView.Visibility = ViewStates.Gone;
numericKeyboardView.PreviewEnabled = false;
/////////////////////////////////////////////////
// THIS IS THE LAYOUT CREATION
////////////////////////////////////////////////
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent); // maybe WrapContent on all
lp.Gravity = GravityFlags.Bottom;
lp.BottomMargin = 0;
/////////////////////////////////////////////////
Activity activity = this.Context as Activity;
activity.AddContentView(numericKeyboardView, lp);
Control.Touch += (sender, ex2) =>
{
if (numericKeyboardView.Visibility == ViewStates.Gone)
{
//Xamarin.Forms.Animation animation = Android.Views.Animations.AnimationUtils.LoadAnimation(
Android.Views.Animations.Animation animation = Android.Views.Animations.AnimationUtils.LoadAnimation(
this.Context,
Resource.Animation.slide_in_bottom
);
numericKeyboardView.ShowWithAnimation(animation);
numericKeyboardView.Visibility = ViewStates.Visible;
}
ex2.Handled = true;
};
}
}
~
I want the keyboard to be presented from bottom of the page and to raise the Entry if needed.
You need to wrap your numericKeyboardView with a RelativeLayout and set the Rule of AlignParentBottom.
In OnElementChanged method, add the following codes:
//Create RelativeLayout for Keyboard
Android.Widget.RelativeLayout rl = new Android.Widget.RelativeLayout(this.Context);
//Create the LayoutParams for Keyboard
Android.Widget.RelativeLayout.LayoutParams rlp = new Android.Widget.RelativeLayout.LayoutParams(LayoutParams.FillParent, LayoutParams.WrapContent);
//set the AlignParentBottom rule
rlp.AddRule(LayoutRules.AlignParentBottom);
//set the LayoutParams to Keyboard
numericKeyboardView.LayoutParameters = rlp;
//add the keyboard to RelativeLayout
rl.AddView(numericKeyboardView);
//get current activity
Activity activity = this.Context as Activity;
//Add the Layout View to activity
activity.AddContentView(rl,rlp);
Related
I'm using a Xamarin.Forms grid application to show a couple of html elements as WebViews in the cells of the grid CardGrid:
private async void CreateCardView()
{
CardGrid.Children.Clear();
// idx over all count elements of html snippets
for (idx = 0; idx < count; idx++)
{
string html = AuthoringCard(idx);
RenderingCard(html, idx);
}
}
AuthoringCard() creates the html code snippet.
RenderingCard() creates the WebView inside the grid cell.
private void RenderingCard(string htmlCard, int index)
{
int CardWidth = 300;
int CardHeight = 150;
int CardNoHorizontally = 3;
WebView uiCard = new WebView();
uiCard.HeightRequest = CardHeight - 5;
uiCard.WidthRequest= CardWidth - 5;
uiCard.VerticalOptions = LayoutOptions.Start;
uiCard.HorizontalOptions = LayoutOptions.Center;
uiCard.Margin = new Thickness(0);
uiCard.AutomationId = index.ToString();
uiCard.Focused += Card_Tapped;
uiCard.InputTransparent = false;
var htmlSource1 = new HtmlWebViewSource
{
Html = htmlCard,
};
uiCard.Source = htmlSource1;
CardGrid.Children.Add(uiCard);
int row = (int)Math.Floor((double)(index / CardNoHorizontally));
int column = index - (row * CardNoHorizontally);
Grid.SetRow(uiCard, row);
Grid.SetColumn(uiCard, column);
}
I want to catch the Focused event, when the user it tapping on the card (WebView) and using the AutomationId to get the index of the card (html code snippet):
private void Card_Tapped(object sender, EventArgs e)
{
WebView card = (WebView)sender;
int index = Convert.ToInt16(card.AutomationId));
}
This works fine with Android. Under iOS the event is never raised. Any idea for a solution?
Cause:
the property Focus in Forms correspond method that we called becomeFirstResponder in native iOS platform.Unfortunately,UIWebView and WKwebview do not support the method becomeFirstResponder.This method is only available in some 'input-controls' Such as UITextField and UITextView(Entry in Forms).So even if you set the event on a webview.It will not work in iOS.
Workaround:
You can add a TapGestureRecognizer on the webview.And you have to implement it by using CustomRenderer.Because it will create conflict if you add TapGestureRecognizer in forms.
Refer to the following code.
in Forms
public MainPage()
{
if (Device.RuntimePlatform == "iOS")
{
MessagingCenter.Subscribe<Object,string>(this,"webview_click", (sender,arg)=> {
// int index = Convert.ToInt16(arg));
});
}
}
in iOS
using System;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using xxx;
using xxx.iOS;
using ObjCRuntime;
[assembly:ExportRenderer(typeof(WebView),typeof(MyWebViewRenderer))]
namespace xxx.iOS
{
public class MyWebViewRenderer : WebViewRenderer, IUIGestureRecognizerDelegate
{
public bool isFirstLoad = true;
public MyWebViewRenderer()
{
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e != null)
{
UITapGestureRecognizer tap = new UITapGestureRecognizer(this, new Selector("Tap_Handle:"));
tap.WeakDelegate = this;
this.AddGestureRecognizer(tap);
}
}
[Export("gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:")]
public bool ShouldRecognizeSimultaneously(UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer)
{
return true;
}
[Export("Tap_Handle:")]
void Tap_Handle(UITapGestureRecognizer tap)
{
if(isFirstLoad)
{
isFirstLoad = false;
MessagingCenter.Send<Object,string>(this, "webview_click",Element.AutomationId);
}
}
}
}
I have a UIPageViewController in which I've implemented a caching mechanism. Practically I have a pool of view controllers that I try to reuse as much as possible when the next or previous view controller is requested in the UIPageViewControllerDataSource. So when the data source requests the previous or the next page I first check if that page has already been shown and return the appropriate view controller.
It all works fine with the default swipe gesture, and the internal method to change page. Now I need to disable the swipe gesture, so I'm not setting the data source, and instead, I have two buttons to go to the previous and next page, and using setViewControllers(_:direction:animated:completion:) to change page programmatically. It works fine when going always in the same direction, but as soon as I change it, and so I'm using a cached view controller, it shows a blank page.
The code is exactly the same as before, if not for setting the view controller programmatically. I've tried both with or without animation and the result is the same and if I remove caching it also works well. My question is, am I forgetting something? Is the UIPageViewController doing something internally that I'm not doing when the data source is assigned?
EDIT
Here's a simplified version of the code. It's a Xamarin Project, but I suppose it's understandable
public class MyPageViewController : AbstractPageViewController
{
const int CacheCapacity = 5;
public int initialItemId { get; set; }
public List<int> ItemIds { get; set; }
readonly List<ChildViewController> viewControllerCache = new List<ChildViewController>(CacheCapacity + 1);
public override void ViewDidLoad()
{
base.ViewDidLoad();
DataSource = null;
var vc = GetDocumentViewController(initialItemId);
SetViewControllers(new[] { vc }, UIPageViewControllerNavigationDirection.Forward, false, null);
var nextButtonItem = new UIBarButtonItem
{
Enabled = true
};
var previousButtonItem = new UIBarButtonItem
{
Enabled = true
};
nextButtonItem.Clicked += NextDocumentButton_Clicked;
previousButtonItem.Clicked += PreviousDocumentButton_Clicked;
var rightButtons = new UIBarButtonItem[2];
rightButtons[0] = nextButtonItem;
rightButtons[1] = previousButtonItem;
NavigationItem.SetRightBarButtonItems(rightButtons, false);
}
private void PreviousDocumentButton_Clicked(object sender, EventArgs e)
{
var referenceVc = (ChildViewController)ViewControllers.FirstOrDefault();
var referenceId = referenceVc.ItemId;
var index = ItemIds.FindIndex(dp => dp.Id == referenceId);
if (index < 1)
return;
var previousDocumentId = ItemIds[index - 1];
var vc = GetDocumentViewController(previousDocumentId);
SetViewControllers(new[] { vc }, UIPageViewControllerNavigationDirection.Reverse, false, null);
}
private void NextDocumentButton_Clicked(object sender, EventArgs e)
{
var referenceVc = (ChildViewController)ViewControllers.FirstOrDefault();
var referenceId = referenceVc.ItemId;
var index = ItemIds.FindIndex(dp => dp.Id == referenceId);
if (index < 0 || index >= ItemIds.Count)
return;
var nextDocumentId = ItemIds[index + 1];
var vc = GetDocumentViewController(nextDocumentId);
SetViewControllers(new[] { vc }, UIPageViewControllerNavigationDirection.Forward, false, null);
}
ChildViewController GetDocumentViewController(int ItemId)
{
var cachedViewController = viewControllerCache.FirstOrDefault(dvc => dvc.ItemId == ItemId);
if (cachedViewController != null)
return cachedViewController;
var vc = new ChildViewController();
vc.SetData(itemId);
viewControllerCache.Add(vc);
if (viewControllerCache.Count > CacheCapacity)
{
viewControllerCache[0].RecycleIfNeeded();
viewControllerCache.RemoveAt(0);
}
return vc;
}
}
It turned out that it was due to a recycling mechanism that was implemented in my code.
In particular, there's a forced recycling of the content of the view controller on ViewDidDisappear. I've disabled it in this case and then it works as it should. This also means that when the UIPageViewController has a data source, and is managing the child view controller directly, ViewDidDisappear is not called when changing page through swiping.
I want to add a pinch event to my webview and did so according to the Xamarin docs:
PinchGestureRecognizer pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += ClearCache;
webview.GestureRecognizers.Add(pinch);
However, the event isn't firing. Is there something else I need to do to make it work?
Here's a messier scattergun approach with all solutions that I've come across that's also not working:
public class SettingsPage : ContentPage {
ICommand tapCommand;
public SettingsPage() {
var url = Path.Combine(DependencyService.Get<IBaseUrl>().Get(), "index.html");
WebView webview = new WebView() {
Source = new UrlWebViewSource { Url = url },
WidthRequest = 1000,
HeightRequest = 1000
};
//webview.Navigating += ClearCache; ********THIS WORKS*********
Padding = Device.OnPlatform<Thickness>(
new Thickness(1, 20, 1, 1),
new Thickness(1),
new Thickness(1)
);
PinchGestureRecognizer pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += ClearCache;
webview.GestureRecognizers.Add(pinch);
tapCommand = new Command(OnTapped);
TapGestureRecognizer tap = new TapGestureRecognizer { NumberOfTapsRequired = 3 };
tap.Tapped += ClearCache;
tap.SetBinding(TapGestureRecognizer.CommandProperty, "TapCommand");
tap.Command = tapCommand;
webview.GestureRecognizers.Add(tap);
StackLayout stackLayout = new StackLayout { Spacing = 0 };
stackLayout.Children.Add(webview);
stackLayout.Children.Add(btnClearCache);
View parent = (View)webview.ParentView;
parent.GestureRecognizers.Add(tap);
parent.GestureRecognizers.Add(pinch);
Content = webview;
}
private void OnTapped(object obj) {
var service = DependencyService.Get<IPlatformService>();
service.ClearWebViewCache();
}
private void ClearCache(object sender, EventArgs e) {
var service = DependencyService.Get<IPlatformService>();
service.ClearWebViewCache();
}
public ICommand TapCommand {
get { return tapCommand; }
}
he WebView is quite nasty when it comes to gestures.
You can use MR.Gestures to handle the Down and Up gestures on iOS, Android and WinPhone 8.0 Silverlight. For Win Phone 8.1 / WinRT I didn't find a way yet.
If you only need iOS and Android, then all the gestures work on the WebView.
More info in the compatibility table on mrgestures.com.
In a view controller I have a 2 text boxes (UITextField) and a submit button. The text boxes pop up the ASCII keyboard. The Submit button takes the values from the text boxes and does something with them.
When the keyboard is open, how do I kill it once the submit button is pressed
The keyboard has a Next button, how do I get it to go to the next field.
Using Xamarin Studio 4.0.12
Thank you!
You need to do what incmiko suggested. Here's the code in C#
Part 1.
txtUsername.ShouldReturn = TextFieldShouldReturn;
txtPassword.ShouldReturn = TextFieldShouldReturn;
create a function in your view
private bool TextFieldShouldReturn(UITextField tf)
{
//change the code below as per your validation
if (tf == _txtUsername)
{
_txtPassword.BecomeFirstResponder();
return true;
}
if(tf == _txtPassword)
{
// validate field inputs as per your requirement
tf.ResignFirstResponder();
return true;
}
return true;
}
Just have a try with this ,
It's just a sample,
NSObject keyboardShowObserver;
NSObject keyboardHideObserver;
public override void ViewWillAppear(bool animated) {
base.ViewWillAppear(animated);
keyboardShowObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, (notification) => {
NSValue nsKeyboardBounds = (NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.BoundsUserInfoKey);
RectangleF keyboardBounds = nsKeyboardBounds.RectangleFValue;
float height = View.Bounds.Height - keyboardBounds.Height;
if (NavigationController != null && NavigationController.TabBarController != null && NavigationController.TabBarController.TabBar != null) {
// Re-add tab bar height since it is hidden under keyboard but still excluded from View.Bounds.Height.
height += NavigationController.TabBarController.TabBar.Frame.Height;
}
someScrollView.Frame = new RectangleF(someScrollView.Frame.Location, new SizeF(View.Bounds.Width, height));
});
keyboardHideObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, (notification) => {
UIApplication.EnsureUIThread();
someScrollView.Frame = new RectangleF(someScrollView.Frame.Location, View.Bounds.Size);
});
}
public override void ViewDidDisappear(bool animated) {
base.ViewDidDisappear(animated);
if (keyboardShowObserver != null) {
NSNotificationCenter.DefaultCenter.RemoveObserver(keyboardShowObserver);
}
if (keyboardHideObserver != null) {
NSNotificationCenter.DefaultCenter.RemoveObserver(keyboardHideObserver);
}
}
I am using PaneManagerDemo project code (this project is present in sample demo projects of BB) for developing tab.
I am facing one problem when I select tab it got focus but when I select data below that tab the focus goes to that data.
I want when user select data under tab, it must focus on that data as well as the tab so that user can understand selected data is under which tab.
I am not getting it.
Please tell me.
Here is code for the reference.
public class PaneManagerDemo extends UiApplication
{
public static void main(String[] args)
{
UiApplication app = new PaneManagerDemo();
app.enterEventDispatcher();
}
public PaneManagerDemo()
{
invokeLater(new Runnable()
{
public void run()
{
int headerType = 0;
// Display a dialog for user to select header type
OptionDialog dialog = new OptionDialog();
int result = dialog.doModal();
if(result == Dialog.OK)
{
headerType = dialog.getHeaderType();
}
else if(result == Dialog.CANCEL)
{
System.exit(0);
}
//PaneScreen screen = new PaneScreen(headerType);
PaneScreen screen = new PaneScreen(headerType);
pushScreen(screen);
}
});
}
/**
* A dialog popup used to choose a header type
*/
private static class OptionDialog extends Dialog
{
public static final int SCROLL_HEADER_TYPE = 0;
public static final int TAB_HEADER_TYPE = 1;
private ObjectChoiceField _choiceField;
/**
* Create a new HeaderDialog object
*/
public OptionDialog()
{
super(Dialog.D_OK_CANCEL, "Choose Header Type", Dialog.OK, null, Dialog.GLOBAL_STATUS);
_choiceField = new ObjectChoiceField("", new String[]{"Scrollable", "Tab"}, 0);
add(_choiceField);
_choiceField.setFocus();
}
/**
* Returns an integer representing the header type
*
* #return SCROLL_HEADER_TYPE if scrollable header selected, TAB_HEADER_TYPE if tab header selected
*/
public int getHeaderType()
{
return _choiceField.getSelectedIndex();
}
}
/**
* Main screen for the application. Displays three panes
* switchable via horizontal scroll field or tabs, depending
* on user selection.
*/
private final static class PaneScreen extends MainScreen
{
/**
* Creates a new PaneScreen object
* #param headerType The header type for the PaneManager, scrollable or tab style
*/
public PaneScreen(int headerType)
{
super(Field.FOCUSABLE);
// Instantiate the model for the pane manager and enable looping
PaneManagerModel model = new PaneManagerModel();
model.enableLooping(true);
// Create a pane
VerticalFieldManager vfm = new VerticalFieldManager();
vfm.add(new LabelField("Data 1"));
XYEdges edgesOne = new XYEdges(1, 1, 1, 1);
vfm.setBorder(BorderFactory.createRoundedBorder(edgesOne));
Pane pane = new Pane(new LabelField("Pane 1", Field.FOCUSABLE | Field.FIELD_HCENTER), vfm);
// Add the pane to the model
model.addPane(pane);
// Create a second pane
vfm = new VerticalFieldManager();
for(int i = 0; i < 30; i++)
{
vfm.add(new LabelField("Data " + i, Field.FOCUSABLE));
}
LabelField iconTextLabelField = new LabelField("Pane 2");
model.addPane(new Pane(iconTextLabelField, vfm));
// Create a third pane
vfm = new VerticalFieldManager();
ButtonField button = new ButtonField("Button", ButtonField.CONSUME_CLICK | ButtonField.NEVER_DIRTY);
button.setChangeListener( new FieldChangeListener()
{
public void fieldChanged(Field field, int context)
{
Dialog.inform("Button activated.");
}
});
vfm.add(button);
model.addPane(new Pane(new LabelField("Pane 3"), vfm));
// Choose which pane the model is displaying
model.setCurrentlySelectedIndex(1);
// Create the header and initialize the model and visual properties
TitleView header = null;
PaneManagerController controller = null;
if(headerType == OptionDialog.SCROLL_HEADER_TYPE)
{
header = new HorizontalScrollableTitleView(Field.FOCUSABLE);
controller = new HorizontalScrollableController();
}
else if(headerType == OptionDialog.TAB_HEADER_TYPE)
{
header = new HorizontalTabTitleView(Field.FOCUSABLE);
((HorizontalTabTitleView)header).setNumberOfDisplayedTabs(model.numberOfPanes());
controller = new HorizontalTabController();
}
else
{
throw new IllegalStateException("Header type is not valid.");
}
header.setModel(model);
XYEdges edgesFour = new XYEdges(4, 4, 4, 4);
header.setBorder(BorderFactory.createRoundedBorder(edgesFour));
// Set arrow images
Bitmap leftArrow = Bitmap.getBitmapResource("leftArrow.png");
Bitmap rightArrow = Bitmap.getBitmapResource("rightArrow.png");
if(leftArrow != null)
{
header.setLeftArrow(leftArrow);
}
if(rightArrow != null)
{
header.setRightArrow(rightArrow);
}
// Create the PaneView object, which will display the panes and is
// controlled by the model.
PaneView paneView = new PaneView(Field.FOCUSABLE);
paneView.setBorder(BorderFactory.createSimpleBorder(edgesOne));
paneView.setModel(model);
// Initialize the PaneManagerView
PaneManagerView view = new PaneManagerView(Field.FOCUSABLE, header, paneView);
view.setModel(model);
view.setBorder(BorderFactory.createRoundedBorder(edgesFour));
model.setView(view);
// Initialize the Controller
controller.setModel(model);
controller.setView(view);
model.setController(controller);
view.setController(controller);
add(view);
}
}
}
Only one field in a screen can hold focus at any one time, so you will need to indicate which tab contains the focused field by other means. e.g. by painting the selected tab a different colour.