Bug 59025 - On iOS, the topmost content of a ContentPage is hidden beneath secondary toolbar items
Summary: On iOS, the topmost content of a ContentPage is hidden beneath secondary tool...
Status: CONFIRMED
Alias: None
Product: Forms
Classification: Xamarin
Component: Forms ()
Version: 2.4.0
Hardware: PC Windows
: Normal normal
Target Milestone: ---
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2017-08-24 19:08 UTC by John Hardman
Modified: 2017-10-17 20:53 UTC (History)
3 users (show)

Tags: iOS, navigation, secondary, Toolbar, ToolbarItem, ac
Is this bug a regression?: ---
Last known good build:


Attachments
Screenshot showing problem (273.34 KB, image/png)
2017-08-24 19:08 UTC, John Hardman
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 59025 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
CONFIRMED

Description John Hardman 2017-08-24 19:08:12 UTC
Created attachment 24407 [details]
Screenshot showing problem

On iOS, when using a Secondary toolbar, the toolbar hides whatever is at the top of the ContentPage that the toolbar is associated with.

In the attached screenshot, it's just possible to see the bottom of "Placeholder for Entry" sticking out from under the bottom of the toolbar button "Button1" (at the top of the page).

The page & toolbar is created as follows (just add this to a ContentPage):

                this.Content = vslOuterPage; // Set to whatever the page content is

                this.ToolbarItems.Clear();

                ToolbarItem toolbarItem = new ToolbarItem
                {
                    Order = ToolbarItemOrder.Secondary,
                    Priority = 0,
                    Text = "Button1"
                };
                this.ToolbarItems.Add(toolbarItem);

The default page renderer should begin the page Content after the secondary toolbar, not underneath it.
Comment 1 Paul DiPietro [MSFT] 2017-08-29 22:44:30 UTC
Hi John -- two of us tried to reproduce this to no avail. Do you still experience it on 2.4.0-pre as well and if so, could you maybe give us more details about what device/iPad and version of iOS you're on? Perhaps there's a specific scenario that causes this that we're missing.
Comment 2 John Hardman 2017-09-07 13:46:26 UTC
Hi Paul,

After a serious debugging session, I've identified the extra scenario detail.

The problem occurs if the page is populated in OnAppearing() rather than in the page's constructor. So, any page that does async work in it's population (which is why OnAppearing would be used) and that uses a secondary toolbar is likely to hit this on iOS.
Comment 3 John Hardman 2017-09-07 17:21:46 UTC
Here's a repro-sample.
Just push a new instance of Bug59025ListViewUnderToolbarPageView onto the navigation stack, on iOS. The scrollable content goes behind the secondary toolbar.
Change USE_ON_APPEARING to false and run again. Now the scrollable content is no longer behind the secondary toolbar.
Both cases should have the scrollable content not behind the secondary toolbar.


using System;
using System.Collections.Generic;

using Xamarin.Forms;

namespace ViewsUsingXamarinForms
{
	public class Bug59025ListViewUnderToolbarPageView :
		ContentPage
	{
		private const bool USE_ON_APPEARING = true;

		public Bug59025ListViewUnderToolbarPageView()
		{
			if (!USE_ON_APPEARING)
				PopulatePage();
		}

		private bool _firstTime = true;
		protected override void OnAppearing()
		{
			base.OnAppearing();

			if (USE_ON_APPEARING)
			{
				if (_firstTime)
				{
					_firstTime = false;
					PopulatePage();
				}
			}
		}

		private void PopulatePage()
		{ 
			this.Content = new ScrollView
			{
				Content = new StackLayout
				{
					Children =
					{
						CreateGrid1(),
						CreateDivider(),
						CreateGrid2(true),
						CreateDivider(),
						CreateGrid3(true, false),
						CreateDivider(),
						new MyGrid
						{
							ColumnWidths = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star)
							},
							RowHeights = new List<GridLength>
							{
								GridLength.Star
							},
							MyGridChildren = new List<MyGridChild>
							{
								new MyGridChild(
									CreateDashboardButton(
										"Xx xXxxxX",
										null),
									0, 1, 0, 1),
								new MyGridChild(
									CreateDashboardButton(
										"XX XXxxxxxX",
										null),
									1, 2, 0, 1),
								new MyGridChild(
									CreateDashboardButton(
										"Xx XXxxX",
										null),
									2, 3, 0, 1),
							}
						},
						CreateDivider(),
						new MyGrid
						{
							ColumnWidths = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star)
							},
							RowHeights = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star)
							},
							MyGridChildren = new List<MyGridChild>
							{
								new MyGridChild(
									CreateDashboardButton(
										"Xx XXxxxX",
										null),
									0, 1, 0, 1),
								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null),
									1, 2, 0, 1),
								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null),
									2, 3, 0, 1),
								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null
									),
									0, 1, 1, 2),

								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null
									),
									1, 2, 1, 2),

								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null
									),
									2, 3, 1, 2),
								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null),
									0, 1, 2, 3),
								new MyGridChild(
									CreateDashboardButton(
										"XX XXxxX",
										null),
									1, 2, 2, 3),
								new MyGridChild(
									CreateDashboardButton(
										"XX",
										null),
									2, 3, 2, 3),
							}
						},
						CreateDivider(),
						new MyGrid
						{
							ColumnWidths = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Star),
								new GridLength(1, GridUnitType.Star),
							},
							RowHeights = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Auto),
							},
							MyGridChildren = new List<MyGridChild>
							{
								new MyGridChild(
									CreateDashboardButton(
										"Xxxxx",
										null
									),
									0, 1, 0, 1),
								new MyGridChild(
									CreateDashboardButton(
										"Xxxx",
										null
									),
									1, 2, 0, 1),
							}
						},
						CreateDivider(),
						new MyGrid
						{
							ColumnWidths = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Star),
							},
							RowHeights = new List<GridLength>
							{
								new GridLength(1, GridUnitType.Auto),
							},
							MyGridChildren = new List<MyGridChild>
							{
								new MyGridChild(
									CreateDashboardButton(
										"Xxxx Xx Xxxxxxxx",
										null
									),
									0, 1, 0, 1),
							}
						}
					}
				}
			};

			this.Title = "Xxxxxxxxxxx";

			ToolbarItems.Clear();

			ToolbarItems.Add(new ToolbarItem(
				"Xxxxxxx", null, () => { }, ToolbarItemOrder.Secondary, 0));
			ToolbarItems.Add(new ToolbarItem(
				"Settings", null, () => { }, ToolbarItemOrder.Secondary, 0));
		}

		private Grid CreateGrid2(bool useOneRowOrColumn)
		{
			Grid grid = new Grid();
			int numberOfColumns = GetNumberOfColumns(useOneRowOrColumn, this.Width, this.Height);
			int numberOfRows = GetNumberOfRows(useOneRowOrColumn, this.Width, this.Height);

			AddAutoRowsAndColumnsToGrid(grid, numberOfRows, numberOfColumns);
			FormatGrid(
				grid,
				CreateDashboardButton(
					"Xxxxxxxxxx",
					null
				),
				CreateDashboardButton(
					"Xxx Xxxx",
					null
				),
				CreateDashboardButton(
					"Xxx Xxxxx",
					null
				),
				CreateDashboardButton(
					"Xxx Xxxx",
					null
				),
				numberOfRows,
				numberOfColumns,
				true);

			return grid;
		}

		public Grid AddAutoRowsAndColumnsToGrid(
			Grid grid,
			int numberOfRows,
			int numberOfColumns)
		{
			for (int columnNumber = 0; columnNumber < numberOfColumns; ++columnNumber)
			{
				ColumnDefinition columnDefinition = new ColumnDefinition
				{
					Width = GridLength.Auto
				};

				grid.ColumnDefinitions.Add(columnDefinition);
			}

			for (int rowNumber = 0; rowNumber < numberOfRows; ++rowNumber)
			{
				RowDefinition rowDefinition = new RowDefinition
				{
					Height = GridLength.Auto
				};

				grid.RowDefinitions.Add(rowDefinition);
			}

			return grid;
		}

		private Grid CreateGrid3(bool useEisenhowerTerms, bool useOneRowOrColumn)
		{
			Grid grid = new Grid();
			int numberOfColumns = GetNumberOfColumns(useOneRowOrColumn, this.Width, this.Height);
			int numberOfRows = GetNumberOfRows(useOneRowOrColumn, this.Width, this.Height);

			AddAutoRowsAndColumnsToGrid(grid, numberOfRows, numberOfColumns);
			FormatGrid(
				grid,
				CreateDashboardButton(
					"Xxxxxx",
					"Xxxxxxxxx"
				),
				CreateDashboardButton(
					"Xxxxxx",
					"Xxx Xxxxxxxxx"
				),
				CreateDashboardButton(
					"Xxx Xxxxxx",
					"Xxxxxxxxx"
				),
				CreateDashboardButton(
					"Xxx Xxxxxx",
					"Xxx Xxxxxxxxx"
				),
				numberOfRows,
				numberOfColumns,
				true);

			return grid;
		}

		private int GetNumberOfColumns(bool useOneRowOrColumn, double width, double height)
		{
			if (!useOneRowOrColumn)
				return 2;
			else if (width < 0)
				return 1;
			else if (width >= height)
				return 4;
			else
				return 1;
		}

		private int GetNumberOfRows(bool useOneRowOrColumn, double width, double height)
		{
			if (!useOneRowOrColumn)
				return 2;
			else if (width < 0)
				return 4;
			else if (width < height)
				return 4;
			else
				return 1;
		}

		private void FormatGrid(
			Grid grid,
			View topLeft,
			View topRight,
			View bottomLeft,
			View bottomRight,
			int numberOfRows,
			int numberOfColumns,
			bool showDefaultFirst)
		{
			if (grid != null)
			{
				if ((numberOfRows == 1) && (numberOfColumns == 4))
				{
					grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
					grid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
					grid.ColumnDefinitions[2].Width = new GridLength(1, GridUnitType.Star);
					grid.ColumnDefinitions[3].Width = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);

					grid.Children.Add(
						showDefaultFirst ? topLeft : bottomRight, 0, 1, 0, 1);
					grid.Children.Add(
						showDefaultFirst ? topRight : bottomLeft, 1, 2, 0, 1);
					grid.Children.Add(
						showDefaultFirst ? bottomLeft : topRight, 2, 3, 0, 1);
					grid.Children.Add(
						showDefaultFirst ? bottomRight : topLeft, 3, 4, 0, 1);

					grid.ColumnSpacing = 2;
					grid.RowSpacing = 0;
				}
				else if ((numberOfRows == 4) && (numberOfColumns == 1))
				{
					grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[1].Height = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[2].Height = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[3].Height = new GridLength(1, GridUnitType.Star);

					grid.Children.Add(
						showDefaultFirst ? topLeft : bottomRight, 0, 1, 0, 1);
					grid.Children.Add(
						showDefaultFirst ? topRight : bottomLeft, 0, 1, 1, 2);
					grid.Children.Add(
						showDefaultFirst ? bottomLeft : topRight, 0, 1, 2, 3);
					grid.Children.Add(
						showDefaultFirst ? bottomRight : topLeft, 0, 1, 3, 4);

					grid.ColumnSpacing = 0;
					grid.RowSpacing = 2;
				}
				else if ((numberOfRows == 2) && (numberOfColumns == 2))
				{
					grid.ColumnDefinitions[0].Width = new GridLength(1, GridUnitType.Star);
					grid.ColumnDefinitions[1].Width = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);
					grid.RowDefinitions[1].Height = new GridLength(1, GridUnitType.Star);

					grid.Children.Add(
						showDefaultFirst ? topLeft : bottomRight, 0, 1, 0, 1);
					grid.Children.Add(
						showDefaultFirst ? topRight : bottomLeft, 1, 2, 0, 1);
					grid.Children.Add(
						showDefaultFirst ? bottomLeft : topRight, 0, 1, 1, 2);
					grid.Children.Add(
						showDefaultFirst ? bottomRight : topLeft, 1, 2, 1, 2);

					grid.ColumnSpacing = 2;
					grid.RowSpacing = 2;
				}
			}
		}

		private ContentView CreateDashboardButton(
			string cellTitleFirstLine,
			string cellTitleSecondLine)
		{
			return new ContentView
			{
				Padding = new Thickness(1, 0),
				Content = new ContentView
				{
					BackgroundColor = Color.AntiqueWhite,
					HorizontalOptions = LayoutOptions.FillAndExpand,
					VerticalOptions = LayoutOptions.FillAndExpand,
					Content = new StackLayout
					{
						VerticalOptions = LayoutOptions.CenterAndExpand,
						BackgroundColor = Color.Pink,
						Children =
						{
							new Label
							{
								Text = cellTitleFirstLine,
								BackgroundColor = Color.Yellow,
								TextColor = Color.Black,
							},
							new Label
							{
								Text = cellTitleSecondLine ?? string.Empty,
								BackgroundColor = Color.Yellow,
								TextColor = Color.Black,
							},
							new Label
							{
								BackgroundColor = Color.Yellow,
								TextColor = Color.Black,
								Text = "Count: 15",
							},
							new Label
							{
								BackgroundColor = Color.Aquamarine,
								IsVisible = true,
								TextColor = Color.Black,
								Text = "Xxx",
							}
						}
					},
				}
			};
		}

		private Grid CreateGrid1()
		{
			MyGrid grid1 = new MyGrid
			{
				ColumnWidths = new List<GridLength>
				{
					new GridLength(1, GridUnitType.Star),
					new GridLength(1, GridUnitType.Star),
					new GridLength(1, GridUnitType.Star)
				},
				RowHeights = new List<GridLength>
				{
					GridLength.Star
				},
				MyGridChildren = new List<MyGridChild>
				{
					new MyGridChild(
						CreateDashboardButton(
							"Xxx Xxxxxxxxxxx",
							null
							),
						0, 1, 0, 1),
					new MyGridChild(
						CreateDashboardButton(
							"Xxxxxxx",
							null
							),
						1, 2, 0, 1),
					new MyGridChild(
						CreateDashboardButton(
							"Xxxxx Xx",
							null
							)
						, 2, 3, 0, 1)
				}
			};

			return grid1;
		}

		private View CreateDivider()
		{
			return new ContentView
			{
				Content = new Label{ Text = "XXX", TextColor = Color.Black, BackgroundColor = Color.Aquamarine }
			};
		}

	}

	public class MyGridChild
	{
		public View View { get; }
		public int Left { get; }
		public int Right { get; }
		public int Top { get; }
		public int Bottom { get; }

		public MyGridChild(View view, int left, int right, int top, int bottom)
		{
			View = view;
			Left = left;
			Right = right;
			Top = top;
			Bottom = bottom;
		}

	} // public class MyGridChild

	public class MyGrid : Grid
	{
		public MyGrid()
		{
			// no-op    
		}

		public IList<GridLength> RowHeights
		{
			set
			{
				this.RowDefinitions = new RowDefinitionCollection();
				foreach (GridLength rowHeight in value)
				{
					this.RowDefinitions.Add(new RowDefinition
					{
						Height = rowHeight
					});
				}
			}
		}

		public IList<GridLength> ColumnWidths
		{
			set
			{
				this.ColumnDefinitions = new ColumnDefinitionCollection();
				foreach (GridLength columnWidth in value)
				{
					this.ColumnDefinitions.Add(new ColumnDefinition
					{
						Width = columnWidth
					});
				}
			}
		}

		public IList<MyGridChild> MyGridChildren
		{
			set
			{
#if DEBUG
				int numberOfColumns = ColumnDefinitions.Count;
				int numberOfRows = RowDefinitions.Count;
#endif
				foreach (MyGridChild child in value)
				{
#if DEBUG
					if ((child.Left > numberOfColumns)
					    || (child.Right > numberOfColumns)
					    || (child.Top > numberOfRows)
					    || (child.Bottom > numberOfRows))
					{
						throw new ArgumentOutOfRangeException();
					}
#endif
					Children.Add(child.View, child.Left, child.Right, child.Top, child.Bottom);
				}
			}
		}

	} // public class MyGrid : Grid
}

// eof
Comment 4 Paul DiPietro [MSFT] 2017-09-08 00:32:37 UTC
Thank you for this code, which does do the trick. I've checked against 2.4.0-pre2 for good measure.