In my activity class i use both custom keyboard and android soft text keyboard. Android text soft keyboard resizes activity layout. If I open custom keyboard while soft keyboard is opened, the last one hides and layout expands back. But I open custom keyboard right after call
InputMethodManager imm = (InputMethodManager)context.GetSystemService(Context.InputMethodService);
imm.HideSoftInputFromWindow(view.WindowToken, 0);
Here view is view with custom keyboard.
And I face the problem when custom keyboard draws twice:
When android soft keyboard is hidden, but layout is not expanded back yet. In that case custom keyboard appears at the top half of the screen.
After layout is expanded back. In that case custom keyboard appears on the bottom half of the screen.
What i want to do is somehow avoid two keyboards simultaneous appearance.
In activity code i use only SoftInput.StateAlwaysHidden WindowSoftInputMode. SoftInput.AdjustPan is not convenient because in that case some views can be hidden by android keyboard.
After hours of internet search the answer has been found. Pspdfkit has great post.
And with small investigation it has been rewritten on C# in Oncreate method:
private View decorView;
private int lastVisibleDecorViewHeight = 0;
decorView = Window.DecorView;
decorView.ViewTreeObserver.GlobalLayout += (sender, args) =>
{
Rect windowVisibleDisplayFrame = new Rect();
decorView.GetWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
int visibleDecorViewHeight = windowVisibleDisplayFrame.Height();
if (lastVisibleDecorViewHeight != 0)
{
if (lastVisibleDecorViewHeight > visibleDecorViewHeight)
{
OnSoftKeyboardShown();
}
else if (lastVisibleDecorViewHeight < visibleDecorViewHeight)
{
OnSoftKeyboardHidden();
if (!isAndroidSoftKeyboardShown && customKeyboardRequested)
{
Keyboard.RequestCustomKeyboard(requestedCustomKeyboardType);
customKeyboardRequested = false;
}
}
}
lastVisibleDecorViewHeight = visibleDecorViewHeight;
};
Hope this will help someone with similar problems.
Related
So I have read mutliple articles regarding this issue, but none worked for my case.
What happens:
When you toggle the keyboard by clicking an entry, on Android the whole layout is shifted up by as much as the keyboard is big. iOS simply renderes the keyboard on top. This is ofc terrible, and especially for the chat application I am building right now completely hiding the entry editor field where the user types on. Inacceptable.
There are some solutions (allthoug I really wonder why such a basic thing isnt included into xamarin.ios already)
1.) Putting your layout into a scrollview.
This works. Simply wrap everything into a scrollview, and the keyboard will push everything up. Great, right?
No. In some instances you cannot wrap things into a scrollview: My chat is one example. Since the chat view is a scrollview itself, the outter layers cannot be a scrollview. I mean, they can: but then you have two scrollviews on top of each other leading to scroll issues and both interfering with one another. ALSO: values like height="180" dont work inside a scrollview anymore because the height isnt a fixed value.
2) Using a plugin
There are many nuget plugins that should work but with the newest iOS they just dont anymore. Some still do, but on few occasions (when the enter button is pressed to disable keyboard) the layout doesnt scroll back down well enough leaving a blank space. So these do not work at all or well enough.
3) Adding a layout that is inflated when the keyboard is triggered
This is what I did as a workaround (that isnt good either):
At the bottom of my layout where my entry field for the chat is I added this layout:
<Grid Grid.Column="1" Grid.Row="2" x:Name="keyboardLayout" IsVisible="false" >
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<BoxView BackgroundColor="Transparent"/>
</Grid>
It is a fixed layout with a height of 300. Now I can listen to keyboard change events:
if (Device.RuntimePlatform == Device.iOS)
{
// Android does it well by itself, iOS is special again
var keyboardService = Xamarin.Forms.DependencyService.Get<IKeyboardService>();
keyboardService.KeyboardIsHidden += delegate
{
keyboardLayout.IsVisible = false;
};
keyboardService.KeyboardIsShown += delegate
{
keyboardLayout.IsVisible = true;
};
}
With a complicated interface (that I am posting if someone wants it), I can listen to change keyboard events. If the keyboard is visible, I simply update the UI with the layout.
This works, but the fixed size of 300 is an issue.
To this day I still dont really know how fixed values in XAML work (input wanted...!), for smaller margins they seem to be equal on every phone, but for higher values (> 50) they differ too much.
So my solution is just about good enough for older iPhones (6, 7). But leaves a bit of an empty space between the keyboard and the entry filed on newer iPhones with longer screens (11, 12).
In summary: no solution is ideal.
What we need
Either an important xamarin update facing this issue (which wont happen anytime soon), or someone who knows how to get the height of the keyboard in pixels, translate that into XAML values, and fill them in in regards to the phone used. Then my solution (number 3) would work always, everywhere (still a workaround, but bulletproof).
Is there anybody out there, who knows how to
a.) get the height of the shown keyboard in pixels
and (and most important)
b.) konws how to translate pixels into Height="xxx"
Thank you for comming to my ted talk ;)
You can create class that extend grid in shared code firstly.
public class KeyboardView: Grid{}
Then create a custom renderer to do the resize control.
[assembly: ExportRenderer(typeof(KeyboardView), typeof(KeyboardViewRenderer))]
namespace KeyboardSample.iOS.Renderers
{
public class KeyboardViewRenderer : ViewRenderer
{
NSObject _keyboardShowObserver;
NSObject _keyboardHideObserver;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
RegisterForKeyboardNotifications();
}
if (e.OldElement != null)
{
UnregisterForKeyboardNotifications();
}
}
void RegisterForKeyboardNotifications()
{
if (_keyboardShowObserver == null)
_keyboardShowObserver = UIKeyboard.Notifications.ObserveWillShow(OnKeyboardShow);
if (_keyboardHideObserver == null)
_keyboardHideObserver = UIKeyboard.Notifications.ObserveWillHide(OnKeyboardHide);
}
void OnKeyboardShow(object sender, UIKeyboardEventArgs args)
{
NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
CGSize keyboardSize = result.RectangleFValue.Size;
if (Element != null)
{
Element.Margin = new Thickness(0, 0, 0, keyboardSize.Height); //push the entry up to keyboard height when keyboard is activated
}
}
void OnKeyboardHide(object sender, UIKeyboardEventArgs args)
{
if (Element != null)
{
Element.Margin = new Thickness(0); //set the margins to zero when keyboard is dismissed
}
}
void UnregisterForKeyboardNotifications()
{
if (_keyboardShowObserver != null)
{
_keyboardShowObserver.Dispose();
_keyboardShowObserver = null;
}
if (_keyboardHideObserver != null)
{
_keyboardHideObserver.Dispose();
_keyboardHideObserver = null;
}
}
}
}
Finally, adding content inside KeyboardView.
You can take a look this thread:
adjust and move the content of a page up slightly when the keyboard appears in an Entry control Xamarin Forms
Install Xamarin.IQKeyboardManager nuget package in Xamarin.Forms iOS project only.
Add below code in AppDelegate.cs before Forms.init()
IQKeyboardManager.SharedManager.Enable = true;
IQKeyboardManager.SharedManager.KeyboardDistanceFromTextField = 20;
When you click on entry, it will shift UI up as you mentioned in your question for Android.
I'm using FPPopover to present a pop over view for my iPhone app. I'm having an issue, however, where when I present it, it will only allow me to present it so low or it will jump to the top. And this is well before it gets cut off anyway.
For example:
[self.speedOptionsPopover presentPopoverFromPoint:CGPointMake(0, 235)];
Works fine, but if I put it a 255 instead of 235 (as it's a good 40px from the bottom) it jumps back up to the top.
Does anyone have any experience with this or how I could fix it?
Also, bonus points if you can explain why the content for the popover always starts like 50px from the top, when I want it to start up higher. How can I change this also?
More code from the creation:
- (void)speedOptionsTapped:(UIBarButtonItem *)sender {
// Set the delegate in the controller that acts as the popover's view to be self so that the controls on the popover can manipulate the WPM and number of words shown
self.speedOptionsController.delegate = self;
self.speedOptionsPopover.arrowDirection = FPPopoverNoArrow;
self.speedOptionsPopover.border = NO;
self.speedOptionsPopover.contentSize = CGSizeMake(320, 190);
[self.speedOptionsPopover presentPopoverFromPoint:CGPointMake(0, 235)];
}
Try replacing this part of the code in FPPopoverController.m:
//ok, will be vertical
if(ht == best_h || self.arrowDirection == FPPopoverArrowDirectionDown)
with this code:
//ok, will be vertical
if (self.arrowDirection == FPPopoverNoArrow)
{
r.origin.x = p.x;
r.origin.y = p.y;
}
else if(ht == best_h || self.arrowDirection == FPPopoverArrowDirectionDown)
The reason you might be having this issue is that the macro FPPopoverArrowDirectionIsVertical considers a popover with no arrows as having a vertical arrow. So, the result is that is tries to best position your popover as close as possible to the view that triggered the popover.
If you replace the code as indicated above, you'll be creating a special case for popovers with no arrows and asking that the original points be respected without repositioning.
I am attempting to implement a chat view in Windows Phone 8. When a user taps my TextBox at the bottom of my View, the view shifts vertically as the keyboard appears, but an additional amount of padding appears at the bottom of the view. I have seen this happen in other apps as well.
Here is my app:
Here is an equivalent app (Whatsapp) that has clearly solved the problem.
Anyone have any ideas on how to correct this issue in a way that won't break my view? My attempts to manually modify padding when Focused/Unfocused have not been successful.
Good news! I have managed to figure out a fix for this. The below code stops the page from being moved up at all and then adds a margin to the bottom of the text box to place it above the keyboard. The value below of 417 seems to work well for me but you can change this to whatever you like. Using this method also stops other content being pushed off screen like the conversation as it will be fully scrollable while the keyboard is active.
private void TextBox_GotFocus_1(object sender, RoutedEventArgs e)
{
var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
rootFrame.RenderTransform = new CompositeTransform() { TranslateY = +0 };
TextInput2.Margin = new Thickness(12, 0, 12, 417);
}
private void TextBox_LostFocus_1(object sender, RoutedEventArgs e)
{
var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
rootFrame.RenderTransform = new CompositeTransform() { TranslateY = +0 };
TextInput2.Margin = new Thickness(12, 0, 12, 12);
}
You can always try to give bottom margin with negative value. example give -40px and see.
If you're using Grid, set Height to "Auto" where the TextBox is.
Set InputScope="Default".
I have an asp.net web site I am building to be supported on ipad. When I focus on an input element and the keyboard pops up, the position fixed header div(which normally scrolls along with the page) will pop up the page a distance equivalent to the amount the keyboard takes up and freeze there for the duration of the input process. Once the keyboard is dropped back down, the div snaps back into place and behaves normally again. I am testing on iOS5 so position: fixed should be supported.
Is this a known issue? Has someone come across this and dealt with it before? I can't seem to find anything on this.
Fixed positioning is broken on iOS5/iOS6/iOS7.
Edit 3: See link to a working fix near end of this answer for iOS8.
Position:fixed is broken when either:
a) the page is zoomed
or
b) the keyboard shows on the iPad/iPhone (due to an input getting focus).
You can view the bugs yourself in jsbin.com/icibaz/3 by opening the link and zooming, or giving the input focus. You can edit the edit the html yourself.
Notes about bugs (a) and (b):
A fixed div with top: 0px; left: 0px; will show in the wrong position (above or below the top of the screen) when an input gets focus and the keyboard shows.
The problem seems to have something to do with the auto-centering of the input on the screen (changing window.pageYOffset).
It appears to be a calculation fault, and not a redraw fault: if you force the top: to change (e.g. switching between 0px and 1px) on the onScroll event, you can see the fixed div move by a pixel, but it remains in the wrong place.
One solution I used previously is to hide the fixed div when an input gets focus - see the other Answer I wrote.
The fixed div seems to becomes stuck at the same absolute position on the page it was at at the time when the keyboard opened.
So perhaps change the div to absolute positioning when an input has focus? Edit 3: see comment at bottom using this solution. Or perhaps save the pageXOffset/pageYOffset values before the keyboard is opened, and in an onScroll event calculate the difference between those values and the current pageXOffset/pageYOffset values (current once the keyboard is opened), and offset the fixed div by that difference.
There appears to be a different problem with fixed positioning if the page is zoomed - try it here (Also good information here about Android support for fixed in comments).
Edit 1: To reproduce use jsbin (not jsfiddle) and use the fullscreen view of jsbin (not the edit page). Avoid jsfiddle (and edit view of jsbin) because they put the code inside an iframe which causes interference with fixed positioning and pageYOffset.
Edit 2: iOS 6 and iOS 7 Mobile Safari position:fixed; still has the same issues - presumably they are by design!.
Edit 3: A working solution for (b) is when the input get focus, change the header to absolute positioning and then set the header top on the page scroll event for example. This solution:
Uses fixed positioning when input not focused (using window.onscroll has terrible jitter).
Don't allow pinch-zoom (avoid bug (a) above).
Uses absolute positioning and window.pageYOffset once an input gets focus (so header is correctly positioned).
If scrolled while input has focus, set style.top to equal pageYOffset (header will jitter somewhat due to onscroll event delay even on iOS8).
If using UIWebView within an App on iOS8, or using <=iOS7, if scrolling when input has focus, header will be super jittery because onscroll is not fired till scroll finishes.
Go back to fixed position header once input loses focus (Example uses input.onblur, but probably tider to use
document.body.onfocus).
Beware usability fail that if header too large, the input can be occluded/covered.
I couldn't get to work for a footer due to bugs in iOS page/viewport height when the keyboard is showing.
Edit example using http://jsbin.com/xujofoze/4/edit and view using http://output.jsbin.com/xujofoze/4/quiet
For my needs, I found it easier to use an absolute positioned header, hide it before scroll and show it when finish scroll (I need the same code to support iOS4 and Android).
For my purposes, I hide the header on a touchstart event, and show it again on touchend or scroll event (plus some timers to improve responsiveness/reduce flickering). It flashes, but is the best compromise I could find. One can detect the start of scrolling using the touchmove event (jQuery does this), but I found touchmove didn't work as well for me because:
regularly the iPad fails to do a repaint before scrolling (i.e. the absolute header remains stuck - even though the top was changed before scrolling started).
when an input element gets focus, the iPad auto-centres the element, but the scrollstart event doesn't get fired (because no touchmove if just clicking an input).
Implementing a fixed header on iOS5 could be improved by using a hybrid approach of fixed and absolute positioning:
used fixed positioning for iOS5 until an input gets focus.
when an input gets focus (keyboard showing), change to the iOS4 absolute positioning code.
when the keyboard is closed, change back to fixed positioning.
Code to detect when keyboard is closed (e.g. using keyboard hide key) is to register the DOMFocusOut event on the document element and do something like the following code. The timeout is needed because the DOMFocusOut event can fire between when one element gets the focus and another loses it.
function document_DOMFocusOut() {
clearTimeout(touchBlurTimer);
touchBlurTimer = setTimeout(function() {
if (document.activeElement == document.body) {
handleKeyboardHide();
}
}.bind(this), 400);
}
My fixed header code is something like:
{
setup: function() {
observe(window, 'scroll', this, 'onWinScroll');
observe(document, 'touchstart', this, 'onTouchStart');
observe(document, 'touchend', this, 'onTouchEnd');
if (isMobile) {
observe(document, 'DOMFocusOut', this, 'docBlurTouch');
} else if (isIE) {
// see http://ajaxian.com/archives/fixing-loss-of-focus-on-ie for code to go into this.docBlurIe()
observe(document, 'focusout', this, 'docBlurIe');
} else {
observe(isFirefox ? document : window, 'blur', this, 'docBlur');
}
},
onWinScroll: function() {
clearTimeout(this.scrollTimer);
this.scrolling = false;
this.rehomeAll();
},
rehomeAll: function() {
if ((isIOS5 && this.scrolling) || isIOS4 || isAndroid) {
this.useAbsolutePositioning();
} else {
this.useFixedPositioning();
}
},
// Important side effect that this event registered on document on iOs. Without it event.touches.length is incorrect for any elements in the document using the touchstart event!!!
onTouchStart: function(event) {
clearTimeout(this.scrollTimer);
if (!this.scrolling && event.touches.length == 1) {
this.scrolling = true;
this.touchStartTime = inputOrOtherKeyboardShowingElement(event.target) ? 0 : (new Date).getTime();
// Needs to be in touchStart so happens before iPad automatic scrolling to input, also not reliable using touchMove (although jQuery touch uses touchMove to unreliably detect scrolling).
this.rehomeAll();
}
},
onTouchEnd: function(event) {
clearTimeout(this.scrollTimer);
if (this.scrolling && !event.touches.length) {
var touchedDuration = (new Date).getTime() - this.touchStartTime;
// Need delay so iPad can scroll to the input before we reshow the header.
var showQuick = this.touchStartTime && touchedDuration < 400;
this.scrollTimer = setTimeout(function() {
if (this.scrolling) {
this.scrolling = false;
this.rehomeAll();
}
}.bind(this), showQuick ? 0 : 400);
}
},
// ... more code
}
jQuery mobile supports scrollstart and scrollstop events:
var supportTouch = $.support.touch,
scrollEvent = "touchmove scroll",
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
function triggerCustomEvent( obj, eventType, event ) {
var originalType = event.type;
event.type = eventType;
$.event.handle.call( obj, event );
event.type = originalType;
}
// also handles scrollstop
$.event.special.scrollstart = {
enabled: true,
setup: function() {
var thisObject = this,
$this = $( thisObject ),
scrolling,
timer;
function trigger( event, state ) {
scrolling = state;
triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
}
// iPhone triggers scroll after a small delay; use touchmove instead
$this.bind( scrollEvent, function( event ) {
if ( !$.event.special.scrollstart.enabled ) {
return;
}
if ( !scrolling ) {
trigger( event, true );
}
clearTimeout( timer );
timer = setTimeout(function() {
trigger( event, false );
}, 50 );
});
}
};
This is somewhat still a problem in iOS13 (when a long text gets deleted in the 'textarea' field, fixed header jumps to the start of that 'textarea' field, obstructing the view), therefore, I thought I share my quick fix:
Since my footer is rather large, I went about without any JS and just adding a greater z-index to the footer than what the fixed header has. Out of sight, out of mind.
Recently, I have been working on a project where the interface should work for desktop and tablets (in particular the iPad).
One issue I am coming across is with a Dojo dialog on the iPad when text entry is taking place.
Basically here is what happens:
Load Dojo interface with buttons on iPad - OK
Press button (touch) to show dialog (90% height and width) - OK
Click on text box (touch) like DateTextBox or TimeTextBox - OK, the virtual keyboard is opened
Click the date or time I want in the UI (touch) - OK, but I can't see all of the options since it is longer than the screen size...
Try to scroll down (swipe up with two fingers or click 'next' in the keyboard) - not OK and the dialog repositions itself to have it's top at the top of the viewport area.
Basically, the issue is that the dialog keeps trying to reposition itself.
Am I able to stop dialog resizing and positioning if I catch the window onResize events?
Does anyone else have this issue with the iPad and Dojo dialogs?
Also, I found this StackOverflow topic on detecting the virtual keyboard, but it wasn't much help in this case...
iPad Web App: Detect Virtual Keyboard Using JavaScript in Safari?
Thanks!
I just came across the same issue yesterday and found a hack,
which is not an elegant solution.
If you want to stop the dijit.Dialog from repositioning you can:
1) Set the property ._relativePosition of a dijit.Dialog object
(in this case it's "pop") after calling the method pop.show():
pop.show();
pop._relativePosition = new Object(); //create empty object
Next steps would probably be:
Check browser type&OS: dojo or even better BrowserDetect
Check when the virtual keyboard is activated and disable repositioning
Extend dijit.Dialog with custom class (handle all of the exceptions)
As suggested another way to do this is to override the _position function by extending the object (or maybe relative position, or other method). Here is my solution which only allows the dialog to be positioned in the middle of the screen once. There are probably better ways to change this by playing with the hide and show events but this suits my needs.
dojo.provide("inno.BigDialog");
dojo.require("dijit.Dialog");
dojo.declare("inno.BigDialog",dijit.Dialog,{
draggable:false,
firstPositioned : false,
_position : function() {
if (!dojo.hasClass(dojo.body(), "dojoMove") && !this.firstPositioned) {
this.firstPositioned = true;
var _8 = this.domNode, _9 = dijit.getViewport(), p = this._relativePosition, bb = p ? null
: dojo._getBorderBox(_8), l = Math
.floor(_9.l
+ (p ? p.x : (_9.w - bb.w) / 2)), t = Math
.floor(_9.t
+ (p ? p.y : (_9.h - bb.h) / 2));
if (t < 0) // Fix going off screen
t = 0;
dojo.style(_8, {
left : l + "px",
top : t + "px"
});
}
}
});
You can override the _position function and call the _position function of the superclass only once. (See http://dojotoolkit.org/reference-guide/dojo/declare.html#calling-superclass-methods)
if (!dojo._hasResource["scorll.asset.Dialog"]) {
dojo._hasResource["scorll.asset.Dialog"] = true;
dojo.provide("scorll.asset.Dialog");
dojo.require("dijit.Dialog");
dojo.declare("scorll.asset.Dialog", [dijit.Dialog], {
_isPositioned: false,
_position: function () {
if(this._isPositioned == false) {
// Calls the superclass method
this.inherited(arguments);
this._isPositioned = true;
}
}
})
}