FlyoutNavigation - Views using NSLayoutConstraints Fail to Properly Draw View on First Use - uiview

Cross-posted here and on github, https://github.com/Clancey/FlyoutNavigation/issues/29.
I have an odd behavior I'm trying to track down, and I'm not sure if it is FlyoutNavigation or something else I'm doing. Maybe someone can take a quick look who understands things a bit better than I do.
Example Project - https://github.com/benhysell/FlyoutNavigationWithNSLayoutConstraintsError
Goal - Use https://gist.github.com/praeclarum/5175100, A C# syntax for NSLayoutConstraints, described in this blog post, http://praeclarum.org/post/45690317491/easy-layout-a-dsl-for-nslayoutconstraint, with FlyoutNavigation.
Issue - On the first use of a view that incorporates NSLayoutConstraints the view doesn't respect the constraints or background color, both odd. On subsequent 'selections' of the view from the menu of FlyoutNavigation the view will properly draw.
Setup - Working in Xamarin Beta Channel against iPhone Simulator 6.1 and the latest released Xcode.
Steps to Reproduce
1. The easiest way to show this is to open the sample project that comes with FlytoutNavigation and modify this project using the steps below. I included in this post a link to the example project I modified to show the error.
Add the gist, https://gist.github.com/praeclarum/5175100, to a new class, call it layout.
Add a new UIViewController and the following to ViewDidLoad(), note this was modified from the Xamarin 'Hello World' sample app one can create in VS2012
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.Frame = UIScreen.MainScreen.Bounds;
View.BackgroundColor = UIColor.Red;
button = UIButton.FromType(UIButtonType.RoundedRect);
button.SetTitle("Click me", UIControlState.Normal);
button.TouchUpInside += (object sender, EventArgs e) =>
{
button.SetTitle(String.Format("clicked {0} times", numClicks++), UIControlState.Normal);
};
View.AddSubview(button);
const int ButtonWidth = 75;
const int HPadding = 22;
const int VPadding = 44;
View.ConstrainLayout(() =>
button.Frame.Width == ButtonWidth &&
button.Frame.Left == View.Frame.Left + HPadding &&
button.Frame.Top == View.Frame.Top + VPadding);
}
In the MainController.cs replace
navigation.ViewControllers = Array.ConvertAll (Tasks, title =>
new UINavigationController (new TaskPageController (navigation, title))
);
with
navigation.ViewControllers = Array.ConvertAll(Tasks, title =>
new UINavigationController(new MyViewController(navigation))
);
I'm saying 'make every view a view that implements NSLayoutConstraints'.
Run Application, first view returns with:
Select the same item from the FlyoutNavigation menu and it will then properly draw.
I've traced through FlyoutNavigationController.cs a couple of times and it appears on the second time selecting the item from FlyoutNavigation, on line 238:
this.View.AddSubview (mainView);
ViewControllers[0].ChildViewControllers[0].View.Frame {{X=160,Y=208,Width=0,Height=0}} System.Drawing.RectangleF
This is the incorrect size for the view, however after I step over line 238:
ViewControllers[0].ChildViewControllers[0].View.Frame {{X=0,Y=0,Width=320,Height=416}} System.Drawing.RectangleF
The position is fixed, and the view will draw correctly.
Summary - I've tried using the gist with the NSLayoutConstraints by itself in a single page window application without issue, and I'm thinking since it does eventually draw properly after a second invokation of FlyoutNavigation I'm thinking there is a 'something' I'm missing with the FlyoutNavigation, or setting incorrectly that I can't put my finger on.

Related

Xamarin.Forms IOS softkeyboard hides layout

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.

Unable to scroll Horizontally in Appium using UiScrollable and TouchActions

I am trying to scroll till the Gift Card option on Make My Trip Home Page and then Click it. So far I have tried below two approaches without success. I am also attaching the screenshot of the App Home Page for clear understanding.
Approach 1 : Using AndroidUIAutomator to scroll to particular element.
driver.findElement(MobileBy.AndroidUIAutomator("new UiScrollable(new UiSelector()"
+ ".resourceId(\"com.makemytrip:id/rvHomePageIcon\"))"
+ ".scrollIntoView(new UiSelector().textMatches(\"Gift Cards\")"
+ ".instance(0));"));
Result : This does not scroll but clicks on Homestays option on the app.
Approach 2:
WebElement eleOne = driver.findElement(By.xpath("//*[#text='Flights']"));
WebElement eleTwo = driver.findElement(By.xpath("//*[#text='Gift Cards']"));
TouchAction t = new TouchAction(driver);
t.longPress(longPressOptions().withElement(element(eleOne))
.withDuration(ofSeconds(8))).moveTo(element(eleTwo))
.release().perform();
Result : This throws No Such Element Found exception as eleTwo is currently not in frame. I tried to tweak this approach and enter eleTwo as an element which is visible on screen just to see if the scrolling works and it did work.
But Somehow I am not sure on how to handle it for elements which are not visible on screen.
I would like to scroll the top options list and then click on GiftCard which is the last option on top widget menu.
I am using AppiumDriver with Java-Client 7.3.0.
You can try this, With uiAutomator2 (set scrollable as true):
public void scrollByID(String Id, int index) {
try {
driver.findElement(MobileBy.AndroidUIAutomator("new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().resourceId(\""+Id+"\").instance("+index+"));"));
} catch (Exception e) {
e.printStackTrace();
}
}
You can scroll Horizontal and vertical based on screen size with Touch Action. Here is sample code.
public void scrollHorizontally() {
int y = driver.manage().window().getSize().height / 2;
int start_x = (int) (driver.manage().window().getSize().width * 0.2);
int end_x = (int) (driver.manage().window().getSize().width * 0.8);
TouchAction dragNDrop = new TouchAction(driver)
.press(PointOption.point(start_x, y)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(500)))
.moveTo(PointOption.point(end_x, y))
.release();
dragNDrop.perform();
}
I have written one detailed answer to scroll with different approaches. You can check here:
How to reach the end of a scroll bar in appium?

Horizontally centering a popup window in Vaadin

I have added a popup window to my main UI as follows:
Window component = new Window();
UI.getCurrent().addWindow(component);
Now, I want my popup to be centered horizontally and e.g. 40 pixels from the top of the screen. As far as I can see Vaadin has 4 methods for positioning my window.
component.center()
component.setPosition(x, y)
component.setPositionX(x)
component.setPositionY(y)
None of these are really what I want. I was hoping at first that setPositionY might help me. This does allow me to get the right distance from the top, but the x-position is now set to 0, where I wanted it to be centered.
The setPosition might have helped if I was able to calculate what the x-position should be, but this would require me to know the width of the component in pixels, but component.getWidth just tells me 100%.
Next I tried to use CSS styling on the component, writing and explicit css rule and adding it to the component with addStyleName. It seems though that Vaadin overrides whatever I wrote in my css with its own defaults...
Any ideas how to get my Window component positioned correctly?
I used the methods getBrowserWindowWidth() and getBrowserWindowHeight() from the com.vaadin.server.Page class for this.
I centered my "log" window horizontally in the lower part of the browser window with
myWindow.setHeight("30%");
myWindow.setWidth("96%");
myWindow.setPosition(
(int) (Page.getCurrent().getBrowserWindowWidth() * 0.02),
(int) (Page.getCurrent().getBrowserWindowHeight() * 0.65)
);
Solution 1: Use SizeReporter
Indeed, setPositionY() will reset the window's centered property to false. As the width of your pop-up and that of your browser window are not know before they appear on the screen, the only way I know to get those values is to use the SizeReporter add-on. Its use is quite straightforward:
public class MyUI extends UI {
private Window popUp;
private SizeReporter popUpSizeReporter;
private SizeReporter windowSizeReporter;
#Override
protected void init(VaadinRequest request) {
Button button = new Button("Content button");
VerticalLayout layout = new VerticalLayout(button);
layout.setMargin(true);
popUp = new Window("Pop-up", layout);
popUp.setPositionY(40);
addWindow(popUp);
popUpSizeReporter = new SizeReporter(popUp);
popUpSizeReporter.addResizeListenerOnce(this::centerPopUp);
windowSizeReporter = new SizeReporter(this);
windowSizeReporter.addResizeListenerOnce(this::centerPopUp);
}
private void centerPopUp(ComponentResizeEvent event) {
int popUpWidth = popUpSizeReporter.getWidth();
int windowWidth = windowSizeReporter.getWidth();
if (popUpWidth == -1 || windowWidth == -1) {
return;
}
popUp.setPositionX((windowWidth - popUpWidth) / 2);
}
}
This piece of code will be okay as long as you don't resize the pop-up. If you do, it will not be automatically recentered. If you replace addResizeListenerOnce() by addResizeListener() then it will automatically recenter the pop-up but you'll get some "UI glitches" as the add-on sends resize events almost continually while you're resizing your pop-up...
You could try to do it using CSS, but I personally avoid CSS as much as I can with Vaadin :).
You'll need to recompile the widgetset after you've added the add-on as a dependency.
Solution 2: Use com.vaadin.ui.JavaScript
I won't vouch for the portability of this solution but I guess it will work on most modern browsers.
public class MyUI extends UI {
private Window popUp;
#Override
protected void init(VaadinRequest request) {
Button button = new Button("Content button");
VerticalLayout layout = new VerticalLayout(button);
layout.setMargin(true);
popUp = new Window("Pop-up", layout);
popUp.setPositionY(40);
popUp.addStyleName("window-center");
addWindow(popUp);
// Add a JS function that can be called from the client.
JavaScript.getCurrent().addFunction("centerWindow", args -> {
popUp.setPositionX((int) ((args.getNumber(1) - args.getNumber(0)) / 2));
});
// Execute the function now. In real code you might want to execute the function just after the window is displayed, probably in your enter() method.
JavaScript.getCurrent().execute("centerWindow(document.getElementsByClassName('window-center')[0].offsetWidth, window.innerWidth)");
}
}

How do I remove bottom padding that appears below a TextBox in Windows Phone when tapped?

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".

IOS: Creating full screen 'overlay' UIView (Monotouch)

I have a view within a navigationcontroller, and would like to create a 'full (application) screen' overlay that says a couple of things about what the user should do. I've seen this done with a bit of transparency on other apps and it works well. I'm trying the below, but the content ends up beneath my navcontroller headers. Investigations talk about converting the rectangle to a 'window level' location etc, but still it is underneath.
Any alternatives to my feeble attempt below? Or a link to a sample would be great too.
Thanks!
HelpOverlayUIView _overlayView;
public void PresentHelpOverlay()
{
RectangleF windowFullFrame = new RectangleF(0,0,UIScreen.MainScreen.Bounds.Width,UIScreen.MainScreen.Bounds.Height);
// Conversion?
//[aView convertPoint:localPosition toView:nil];
RectangleF overlayFrame = View.ConvertRectFromView(windowFullFrame ,null);
// Clear old one if it exists.
if (_overlayView != null) _overlayView = null;
// Instantiate popup view and add to (in this case the 2nd tier view). This is in an abstract class, if in a concrete one it'd normally just be View.Add
_overlayView = new HelpOverlayUIView(this, overlayFrame);
// tried th, didn't change it.
// this.WantsFullScreenLayout = true;
// _childView.BringSubviewToFront(_overlayView);
_overlayView.Alpha = 0;
_childView.Add (_overlayView);
// Setup event to close without doing anything.
_overlayView.CloseView += (sender, e) => {
Console.WriteLine ("close called");
// animate modal popup's departure
UIView.Animate (
0.5f,
() => {
// Anim Code
_overlayView.Alpha = 0;
},
null
);
};
// animate modal popup's arrival
UIView.Animate (
0.5f,
() => {
// Anim Code
_overlayView.Alpha = 1;
},
null
);
}
(The view is just a UIView with an overload to the Draw() method that places text etc into the view)
You could add the _overlayView to something higher up in the view hierarchy such as the NavigationViewController. If your NavigationViewController is set to be your RootViewController (well, regardless of what is really) you could try this.
((AppDelegate)UIApplication.SharedApplication.Delegate).Window.RootViewController.Add(_overlayView);
You can also bring a subview to the front like this, but this is only to the front of that view, not to the front of everything (which isn't really what you wanted unless _childView was your NavigationViewController).
_childView.BringSubviewToFront(_overlayView);

Resources