Keyboard overlaps Editor in Xamarin.Forms on iOS - ios

When I focus that Editor, a keyboard overlaps Editor and Button. I know, there is a solution: putting elements inside ScrollView, but this solution makes huge problems with scrolling ListView inside ScrollView. What should I do, to make it work properly?
Layout:
red - ScrollView,
green - ListView,
blue - StackLayout with Editor and Button.
Here is the video how this works:
https://streamable.com/wvd3h
Solution #2 is to use Xamarin.Forms.Plugins - KeyboardOverlap, but this plugin has a problem with tab bar on TabbedPage

Rather than using a scrollview, you could create a custom iOS renderer that shifts your view up (using the margin) when the keyboard is active.
An example of this can be seen here:
https://github.com/rdelrosario/ChatUIXForms/blob/master/ChatUIXForms.iOS/Renderers/ChatEntryRenderer.cs
Relevant code:
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;
}
}
On Android, it's likely not an issue, but if it is, you could try
android:Application.WindowSoftInputModeAdjust="Resize"

Related

Shadow in UIView cannot overlay Entry or Editor or Picker in Xamarin Forms

I added an RoutingEffect in Xamarin Form project and PlatformEffect in my Xamarin.iOS project. It will add effect to Stacklayout. The Stacklayout in this demo is a custom navigation bar. The below of navigation bar is a scrollview has many cells (label, entry, picker).
I implemented in Android is OK.
But in iOS has problem: Shadow effect cannot overlays some controls, such as: Entry, Editor, Picker. Could you share me how to fix it?
This is code in Xamarin.iOS project.
public class DropShadowEffect : PlatformEffect
{
protected override void OnAttached()
{
try
{
var effect = (myDemo.UIControls.DropShadowEffect)Element.Effects.FirstOrDefault(e => e is myDemo.UIControls.DropShadowEffect);
if (effect != null)
{
Container.Layer.CornerRadius = effect.Radius;
Container.Layer.ShadowColor = UIColor.Red.CGColor;// effect.Color.ToCGColor();
Container.Layer.ShadowOffset = new CGSize(effect.DistanceX, effect.DistanceY);
Container.Layer.ShadowOpacity = 0.8f;
Container.Layer.ShadowRadius = 2f;
Container.Layer.ShouldRasterize = true;
Container.Layer.MasksToBounds = false;
}
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: {0}", ex.Message);
}
}
*Shadow effect overly Label is OK
*Shadow effect cannot overlay either Picker or Entry
Cause:
Actually, such as Label will still overlay the shadow.But it doesn't seem obvious.If you set the background of label (such as red ),you will see the overlay.
Solution:
You can set the BackgroundColor of the Picker and Entry in the custom renderer to let the alpha as 0.
For example in EntryRenderer
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BackgroundColor = new UIColor(1,1,1,0);//The last parameter sets the alpha of backgound as transparent
Control.Layer.MasksToBounds = true;
Control.Layer.CornerRadius = xxx; //set the rounded corner
Control.Layer.BorderColor = UIColor.xxx.CGColor;
Control.Layer.BorderWidth = xxx;
}
}

Xamarin.form Move up view when keyboard appear

I'm trying to build a Chat app UI, the idea of the Layout was pretty simple:
When the input bar is focused, keyboard show up and "push" up the chat bar, as it's a grid, the ListView will resize to fit the screen:
I update the input bar's margin to "push" it up:
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
}
And this is the result:
https://drive.google.com/file/d/1S9yQ6ks15BRH3hH0j_M8awpDJFRFitUi/view?usp=sharing
The view did push up and the ListView also resized as expected, however there are two issues that I had no idea how to solve it:
How can I retain the ListView scroll position after resize?
Lack of animation to push up the view
I have search over the web, tried IQKeyboardManager and KeyboardOverLap, The push up animation is nice and smooth, however strange things happened:
https://drive.google.com/file/d/1Zm0lMKB3wq07ve67wlcvLuNM_6Waad7R/view?usp=sharing
Instead of resizing the ListView, this approach Push the entire ListView up, that I cannot see the first few items, of course the scroll bar can be scroll out of screen
Extra strange spaces at the bottom of the ListView
Any help will be appreciated, thank you!
Solution:
void OnKeyboardShow(object sender, UIKeyboardEventArgs args)
{
NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
CGSize keyboardSize = result.RectangleFValue.Size;
if (Control != null)
{
int bottomMargin = 0;
var sa = UIApplication.SharedApplication.KeyWindow.SafeAreaInsets;
bottomMargin = (int)sa.Bottom;
CGPoint offset = Control.ContentOffset;
var difference = keyboardSize.Height - bottomMargin;
if (Control.ContentSize.Height > Control.Frame.Height)
{
offset.Y += difference;
Control.SetContentOffset(offset, true);
}
else if (Control.ContentSize.Height + keyboardSize.Height > Control.Frame.Height)
{
offset.Y += Control.ContentSize.Height + keyboardSize.Height - Control.Frame.Height - bottomMargin;
Control.SetContentOffset(offset, true);
}
Control.ContentInset = new UIEdgeInsets(0, 0, difference, 0);
Control.ScrollIndicatorInsets = Control.ContentInset;
}
}
void OnKeyboardHide(object sender, UIKeyboardEventArgs args)
{
if (Control != null)
{
Control.ContentInset = new UIEdgeInsets(0, 0, 0, 0);
Control.ScrollIndicatorInsets = new UIEdgeInsets(0, 0, 0, 0);
}
}
Solution:
Refer the following code
in iOS Custom Renderer
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
Control.KeyboardDismissMode = UIScrollViewKeyboardDismissMode.OnDrag;
NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector("KeyBoardWillShow:"), new NSString("UIKeyboardWillShowNotification"), null);
NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector("KeyBoardWillHide:"), new NSString("UIKeyboardWillHideNotification"), null);
}
}
[Export("KeyBoardWillShow:")]
void KeyBoardWillShow(NSNotification note)
{
NSValue keyboardRect = (NSValue)note.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
Control.ContentInset = new UIEdgeInsets(0,0, keyboardRect.RectangleFValue.Size.Height,0);
}
[Export("KeyBoardWillHide:")]
void KeyBoardWillHide(NSNotification note)
{
Control.ContentInset = UIEdgeInsets.Zero;
}

IOS Tabs Title without icons white space on the top

I a have page with tabs, in the tabs i only want text, in android the text is centered both vertically and horizontally, without any white spaces, but in the ios version there is a white space on the top of the text, because there isnt icons.
How can i remove the white space on the top?
You can use a custom renderer for that:
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabBarRenderer))]
namespace MyProject.iOS.Renderers
{
public class CustomTabBarRenderer : TabbedRenderer
{
public override void ViewWillAppear(bool animated)
{
if (TabBar?.Items == null)
return;
// Go through our elements and change them
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
UpdateTabBarItem(TabBar.Items[i]);
}
base.ViewWillAppear(animated);
}
private void UpdateTabBarItem(UITabBarItem item)
{
if (item == null)
return;
// Set the font for the title.
item.SetTitleTextAttributes(new UITextAttributes() { Font = UIFont.FromName("Your-Font", 10) }, UIControlState.Normal);
item.SetTitleTextAttributes(new UITextAttributes() { Font = UIFont.FromName("Your-Font", 10) }, UIControlState.Selected);
// Moves the titles up just a bit.
item.TitlePositionAdjustment = new UIOffset(0, -2);
}
}
}
The TitlePositionAdjustment is what you're looking for. You can also use this to change the font size if needed using the SetTitleTextAttributes method.

JavaFXPorts - Problems with ScrollBar on Mobile Devices

I'm currently developing a mobile application with JavaFX, using GluonHQ and JavaFXPorts. One of my screens contains a listview as you can see from the screenshot below, which was taken from my iPhone 6.
I have noticed the following problems with the scrollbar in mobile devices:
The first time i touch the screen the scroll bar appears a bit off place and then moves to the correct right position. This just happens quickly only the first time. (Screenshot)
I noticed that the scrollbar appears every time i touch the screen and not only when I touch and drag. On native iOS applications the scrollbar appears only when you touch and drag. If you keep your finger on screen and then remove it the scrollbar does not appear.
The scrollbar always takes some time to disappear when I remove my finger from the screen, whilst in native apps it disappears instantly.
Can anyone help me on fixing these issues. How can you define the time the scrollbar appears before it hides again?
You can experience this situation by just creating a ListView and load it with some items.
UPDATE
Thanks to the answer of Jose Pereda below, I have managed to overcome all three problems described above. Here is the code I used to reach the desired results. Watch this short video to get a quick idea of how the new scrolling bar appears and behaves. Again, Jose, you are the boss! Please go ahead with any comments for improvement.
public class ScrollBarView {
public static void changeView(ListView<?> listView) {
listView.skinProperty().addListener(new ChangeListener<Object>() {
private StackPane thumb;
private ScrollBar scrollBar;
boolean touchReleased = true, inertia = false;
#Override
public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
scrollBar = (ScrollBar) listView.lookup(".scroll-bar");
// "hide" thumb as soon as the scroll ends
listView.setOnScrollFinished(e -> {
if (thumb != null) {
touchReleased = true;
playAnimation();
} // if
});
// Fix for 1. When user touches first time, the bar is set invisible so that user cannot see it is
// placed in the wrong position.
listView.setOnTouchPressed(e -> {
if (thumb == null) {
thumb = (StackPane) scrollBar.lookup(".thumb");
thumb.setOpacity(0);
initHideBarAnimation();
} // if
});
// Try to play animation whenever an inertia scroll takes place
listView.addEventFilter(ScrollEvent.SCROLL, e -> {
inertia = e.isInertia();
playAnimation();
});
// As soon as the scrolling starts the thumb become visible.
listView.setOnScrollStarted(e -> {
sbTouchTimeline.stop();
thumb.setOpacity(1);
touchReleased = false;
});
} // changed
private Timeline sbTouchTimeline;
private KeyFrame sbTouchKF1, sbTouchKF2;
// Initialize the animation that hides the thumb when no scrolling takes place.
private void initHideBarAnimation() {
if (sbTouchTimeline == null) {
sbTouchTimeline = new Timeline();
sbTouchKF1 = new KeyFrame(Duration.millis(50), new KeyValue(thumb.opacityProperty(), 1));
sbTouchKF2 = new KeyFrame(Duration.millis(200), (e) -> inertia = false, new KeyValue(thumb.opacityProperty(), 0));
sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
} // if
} // initHideBarAnimation
// Play animation whenever touch is released, and when an inertia scroll is running but thumb reached its bounds.
private void playAnimation() {
if(touchReleased)
if(!inertia || (scrollBar.getValue() != 0.0 && scrollBar.getValue() != 1))
sbTouchTimeline.playFromStart();
} // playAnimation()
});
} // changeView
} // ScrollBarView
As mentioned in the comments, the first issue is known, and for now it hasn't been fixed. The problem seems to be related to the initial width of the scrollbar (20 pixels as in desktop), and then is set to 8 pixels (as in touch enabled devices), and moved to its final position with this visible shift of 12 pixels to the right.
As for the second and third issues, if you don't want to patch and build the JDK yourself, it is possible to override the default behavior, as the ScrollBar control is part of the VirtualFlow control of a ListView, and both can be found on runtime via lookups.
Once you have the control, you can play with its visibility according to your needs. The only problem with this property is that it is already bound and constantly called from the layoutChildren method.
This is quite a hacky solution, but it works for both 2) and 3):
public class BasicView extends View {
private final ListView<String> listView;
private ScrollBar scrollbar;
private StackPane thumb;
public BasicView(String name) {
super(name);
listView = new ListView<>();
// add your items
final InvalidationListener skinListener = new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
if (listView.getSkin() != null) {
listView.skinProperty().removeListener(this);
scrollbar = (ScrollBar) listView.lookup(".scroll-bar");
listView.setOnScrollFinished(e -> {
if (thumb != null) {
// "hide" thumb as soon as scroll/drag ends
thumb.setStyle("-fx-background-color: transparent;");
}
});
listView.setOnScrollStarted(e -> {
if (thumb == null) {
thumb = (StackPane) scrollbar.lookup(".thumb");
}
if (thumb != null) {
// "show" thumb again only when scroll/drag starts
thumb.setStyle("-fx-background-color: #898989;");
}
});
}
}
};
listView.skinProperty().addListener(skinListener);
setCenter(listView);
}
}

How to hide a UIView and remove the "empty" space? -iOS/Monotouch

I need to be able to hide controls on a page that uses constraints and remove the empty space that Hidden=true leaves. It needs to be similar to how the web handles visibility. If it's invisible, it doesn't take up space.
Does anyone know of a clean way to accomplish this?
Please let me know if you need more details.
Thx
Example:
UIButton | UIButton | UIButton
"empty space for hidden UIButton"
UIButton
That should really be rendered like this:
UIButton | UIButton | UIButton
UIButton
Edit: I'm using Xamarin Studio and VS2012 for development.
Since original question is related to Xamarin, I provide complete C# solution.
First, create height constraint for your view and give it an identifier in Xcode Interface Builder:
Then in controller override ViewDidAppear() method and wrap view with HidingViewHolder:
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
applePaymentViewHolder = new HidingViewHolder(ApplePaymentFormView, "ApplePaymentFormViewHeightConstraint");
}
It is important to create HidingViewHolder when view was laid out, so it has real height assigned.
To hide or show view you can use corresponding methods:
applePaymentViewHolder.HideView();
applePaymentViewHolder.ShowView();
HidingViewHolder source:
using System;
using System.Linq;
using UIKit;
/// <summary>
/// Helps to hide UIView and remove blank space occupied by invisible view
/// </summary>
public class HidingViewHolder
{
private readonly UIView view;
private readonly NSLayoutConstraint heightConstraint;
private nfloat viewHeight;
public HidingViewHolder(UIView view, string heightConstraintId)
{
this.view = view;
this.heightConstraint = view
.GetConstraintsAffectingLayout(UILayoutConstraintAxis.Vertical)
.SingleOrDefault(x => heightConstraintId == x.GetIdentifier());
this.viewHeight = heightConstraint != null ? heightConstraint.Constant : 0;
}
public void ShowView()
{
if (!view.Hidden)
{
return;
}
if (heightConstraint != null)
{
heightConstraint.Active = true;
heightConstraint.Constant = viewHeight;
}
view.Hidden = false;
}
public void HideView()
{
if (view.Hidden)
{
return;
}
if (heightConstraint != null)
{
viewHeight = heightConstraint.Constant;
heightConstraint.Active = true;
heightConstraint.Constant = 0;
}
view.Hidden = true;
}
}
In storyboard wire your constrains first. Then try this
self.viewToHideHeight.constant = 0;
self.lowerButtonHeightFromTop.constant = self.viewToHideHeightFromTop.constant + self.viewToHideHeight.constant;
[UIView animateWithDuration:0.5f animations:^{
self.viewToHide.alpha = 0.0f;
[self.view layoutIfNeeded];
}];

Resources