diff --git a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs index af934c52e..25be35bbd 100644 --- a/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs +++ b/Xamarin.Forms.Build.Tasks/DebugXamlCTask.cs @@ -113,8 +113,16 @@ namespace Xamarin.Forms.Build.Tasks var br2 = Instruction.Create(OpCodes.Ldarg_0); var ret = Instruction.Create(OpCodes.Ret); il.Emit(OpCodes.Ldarg_0); - var baseCtor = module.ImportReference(typeDef.BaseType.Resolve().GetConstructors().First(c => c.HasParameters == false)); - baseCtor = module.ImportReference(baseCtor.ResolveGenericParameters(typeDef.BaseType, module)); + MethodReference baseCtor; + if (typeDef.BaseType.Resolve().GetConstructors().FirstOrDefault(c => c.HasParameters && c.Parameters.Count == 1 && c.Parameters[0].Name == "useCompiledXaml") is MethodDefinition baseCtorDef) { + baseCtor = module.ImportReference(baseCtorDef); + baseCtor = module.ImportReference(baseCtor.ResolveGenericParameters(typeDef.BaseType, module)); + il.Emit(OpCodes.Ldarg_1); + } + else { + baseCtor = module.ImportReference(typeDef.BaseType.Resolve().GetConstructors().First(c => c.HasParameters == false)); + baseCtor = module.ImportReference(baseCtor.ResolveGenericParameters(typeDef.BaseType, module)); + } il.Emit(OpCodes.Callvirt, baseCtor); il.Emit(OpCodes.Nop); diff --git a/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs b/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs index edd435167..03b68a158 100644 --- a/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs +++ b/Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs @@ -10,10 +10,7 @@ namespace Xamarin.Forms.Build.Tasks { class SetNamescopesAndRegisterNamesVisitor : IXamlNodeVisitor { - public SetNamescopesAndRegisterNamesVisitor(ILContext context) - { - Context = context; - } + public SetNamescopesAndRegisterNamesVisitor(ILContext context) => Context = context; ILContext Context { get; } @@ -25,7 +22,7 @@ namespace Xamarin.Forms.Build.Tasks public bool IsResourceDictionary(ElementNode node) { - var parentVar = Context.Variables[(IElementNode)node]; + var parentVar = Context.Variables[node]; return parentVar.VariableType.FullName == "Xamarin.Forms.ResourceDictionary" || parentVar.VariableType.Resolve().BaseType?.FullName == "Xamarin.Forms.ResourceDictionary"; } @@ -64,7 +61,7 @@ namespace Xamarin.Forms.Build.Tasks public void Visit(RootNode node, INode parentNode) { - var namescopeVarDef = CreateNamescope(); + var namescopeVarDef = GetOrCreateNameScope(node); IList namesInNamescope = new List(); if (Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Body.Method.Module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject")))) SetNameScope(node, namescopeVarDef); @@ -77,33 +74,38 @@ namespace Xamarin.Forms.Build.Tasks } static bool IsDataTemplate(INode node, INode parentNode) - { - var parentElement = parentNode as IElementNode; - INode createContent; - if (parentElement != null && parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) && - createContent == node) - return true; - return false; - } + => parentNode is IElementNode parentElement && parentElement.Properties.TryGetValue(XmlName._CreateContent, out INode createContent) && createContent == node; - static bool IsStyle(INode node, INode parentNode) - { - var pnode = parentNode as ElementNode; - return pnode != null && pnode.XmlType.Name == "Style"; - } + static bool IsStyle(INode node, INode parentNode) => parentNode is ElementNode pnode && pnode.XmlType.Name == "Style"; - static bool IsVisualStateGroupList(ElementNode node) - { - return node != null && node.XmlType.Name == "VisualStateGroup" && node.Parent is IListNode; - } + static bool IsVisualStateGroupList(ElementNode node) => node != null && node.XmlType.Name == "VisualStateGroup" && node.Parent is IListNode; static bool IsXNameProperty(ValueNode node, INode parentNode) + => parentNode is IElementNode parentElement && parentElement.Properties.TryGetValue(XmlName.xName, out INode xNameNode) && xNameNode == node; + + VariableDefinition GetOrCreateNameScope(ElementNode node) { - var parentElement = parentNode as IElementNode; - INode xNameNode; - if (parentElement != null && parentElement.Properties.TryGetValue(XmlName.xName, out xNameNode) && xNameNode == node) - return true; - return false; + var module = Context.Body.Method.Module; + var vardef = new VariableDefinition(module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "NameScope"))); + Context.Body.Variables.Add(vardef); + var stloc = Instruction.Create(OpCodes.Stloc, vardef); + + if (Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Body.Method.Module.ImportReference(("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject")))) { + var namescoperef = ("Xamarin.Forms.Core", "Xamarin.Forms", "BindableObject"); + Context.IL.Append(Context.Variables[node].LoadAs(module.GetTypeDefinition(namescoperef), module)); + Context.IL.Emit(OpCodes.Call, module.ImportMethodReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "NameScope"), + methodName: "GetNameScope", + parameterTypes: new[] { namescoperef }, + isStatic: true)); + Context.IL.Emit(OpCodes.Dup); + Context.IL.Emit(OpCodes.Brtrue, stloc); + + Context.IL.Emit(OpCodes.Pop); + } + Context.IL.Emit(OpCodes.Newobj, module.ImportCtorReference(("Xamarin.Forms.Core", "Xamarin.Forms.Internals", "NameScope"), parameterTypes: null)); + + Context.IL.Append(stloc); + return vardef; } VariableDefinition CreateNamescope() diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2818.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2818.cs index 6934d2a88..08bc2ae80 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2818.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2818.cs @@ -1,21 +1,41 @@ using Xamarin.Forms.CustomAttributes; using Xamarin.Forms.Internals; +using System; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +#endif namespace Xamarin.Forms.Controls.Issues { [Preserve(AllMembers = true)] [Issue(IssueTracker.Github, 2818, "Right-to-Left MasterDetail in Xamarin.Forms Hamburger icon issue", PlatformAffected.Android)] - public class Issue2818 : MasterDetailPage + public class Issue2818 : TestMasterDetailPage { - public Issue2818() + + protected override void Init() { FlowDirection = FlowDirection.RightToLeft; - Master = new ContentPage { Title = "Master", BackgroundColor = Color.SkyBlue, - IconImageSource = "menuIcon" + IconImageSource = "menuIcon", + Content = new StackLayout() + { + Children = + { + new Button() + { + Text = "If you can see me the test has passed", + AutomationId = "CloseMasterView", + Command = new Command(() => IsPresented = false) + } + }, + AutomationId = "MasterLayout" + }, + Padding = new Thickness(0, 42, 0, 0) }; Detail = new NavigationPage(new ContentPage @@ -26,21 +46,92 @@ namespace Xamarin.Forms.Controls.Issues Children = { new Label { - Text = "The page must be with RightToLeft FlowDirection. Hamburger icon in main page must be going to right side." + Text = "The page must be with RightToLeft FlowDirection. Hamburger icon in main page must be going to right side. There should be visible text inside the Master View" }, new Button { Text = "Set RightToLeft", - Command = new Command(() => FlowDirection = FlowDirection.RightToLeft) + Command = new Command(() => FlowDirection = FlowDirection.RightToLeft), + AutomationId = "ShowRightToLeft" }, new Button { Text = "Set LeftToRight", - Command = new Command(() => FlowDirection = FlowDirection.LeftToRight) + Command = new Command(() => FlowDirection = FlowDirection.LeftToRight), + AutomationId = "ShowLeftToRight" + }, + new Button + { + Text = "Open Master View", + Command = new Command(() => IsPresented = true), + AutomationId = "OpenMasterView" + }, + new Label() + { + Text = Device.Idiom.ToString(), + AutomationId = "Idiom" } } } }); } + +#if UITEST + [Test] + public void MasterViewMovesAndContentIsVisible() + { + var idiom = RunningApp.WaitForElement("Idiom"); + + // This behavior is currently broken on a phone device Issue 7270 + if (idiom[0].ReadText() != "Tablet") + return; + + RunningApp.Tap("OpenMasterView"); + RunningApp.Tap("CloseMasterView"); + RunningApp.SetOrientationLandscape(); + RunningApp.Tap("OpenMasterView"); + var positionStart = RunningApp.WaitForElement("CloseMasterView"); + RunningApp.Tap("ShowLeftToRight"); + + var results = RunningApp.QueryUntilPresent(() => + { + var secondPosition = RunningApp.Query("CloseMasterView"); + + if (secondPosition.Length == 0) + return null; + + if (secondPosition[0].Rect.X < positionStart[0].Rect.X) + return secondPosition; + + return null; + }); + + Assert.IsNotNull(results, "Master View Did not change flow direction correctly"); + Assert.AreEqual(1, results.Length, "Master View Did not change flow direction correctly"); + + } + +#if __IOS__ + [Test] + public void MasterViewSizeDoesntChangeAfterBackground() + { + var idiom = RunningApp.WaitForElement("Idiom"); + // This behavior is currently broken on a phone device Issue 7270 + if (idiom[0].ReadText() != "Tablet") + return; + + RunningApp.SetOrientationLandscape(); + RunningApp.Tap("CloseMasterView"); + RunningApp.Tap("ShowLeftToRight"); + var windowSize = RunningApp.WaitForElement("MasterLayout")[0]; + RunningApp.SendAppToBackground(TimeSpan.FromSeconds(5)); + var newWindowSize = RunningApp.WaitForElement("MasterLayout")[0]; + Assert.AreEqual(newWindowSize.Rect.Width, windowSize.Rect.Width); + Assert.AreEqual(newWindowSize.Rect.Height, windowSize.Rect.Height); + + } +#endif + +#endif } } diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs index 6e458d2b8..81c34e3e6 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/ScreenshotConditionalApp.cs @@ -4,6 +4,10 @@ using System.IO; using Xamarin.UITest; using Xamarin.UITest.Queries; +#if __IOS__ +using Xamarin.UITest.iOS; +#endif + namespace Xamarin.Forms.Controls { /// @@ -445,6 +449,16 @@ namespace Xamarin.Forms.Controls { get { return _app.TestServer; } } + +#if __IOS__ + public void SendAppToBackground(TimeSpan timeSpan) + { + if (_app is iOSApp app) + { + app.SendAppToBackground(timeSpan); + } + } +#endif } } #endif \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs index ee2a11b94..efa03f5a1 100644 --- a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGallery.cs @@ -21,6 +21,7 @@ GalleryBuilder.NavButton("Header/Footer (Template)", () => new HeaderFooterTemplate(), Navigation), GalleryBuilder.NavButton("Header/Footer (Grid)", () => new HeaderFooterGrid(), Navigation), GalleryBuilder.NavButton("Footer Only (String)", () => new FooterOnlyString(), Navigation), + GalleryBuilder.NavButton("Header/Footer (Grid Horizontal)", () => new HeaderFooterGridHorizontal(), Navigation), } } }; diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml new file mode 100644 index 000000000..babedd222 --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs new file mode 100644 index 000000000..fbe2478ff --- /dev/null +++ b/Xamarin.Forms.Controls/GalleryPages/CollectionViewGalleries/HeaderFooterGalleries/HeaderFooterGridHorizontal.xaml.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Xamarin.Forms.Controls.GalleryPages.CollectionViewGalleries.HeaderFooterGalleries +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class HeaderFooterGridHorizontal : ContentPage + { + readonly DemoFilteredItemSource _demoFilteredItemSource = new DemoFilteredItemSource(10); + + public HeaderFooterGridHorizontal() + { + InitializeComponent(); + + CollectionView.ItemTemplate = ExampleTemplates.PhotoTemplate(); + CollectionView.ItemsSource = _demoFilteredItemSource.Items; + } + + void Handle_Clicked(object sender, System.EventArgs e) + { + if (sender is VisualElement ve && ve.Parent is StackLayout sl) + sl.Children.Add(new Label() { Text = "Grow" }); + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj index 5f13a8028..56181469f 100644 --- a/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj +++ b/Xamarin.Forms.Controls/Xamarin.Forms.Controls.csproj @@ -80,6 +80,9 @@ MSBuild:UpdateDesignTimeXaml + + MSBuild:UpdateDesignTimeXaml + MSBuild:UpdateDesignTimeXaml diff --git a/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs b/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs index 1808c3a49..de1a29f14 100644 --- a/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs +++ b/Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs @@ -5,6 +5,9 @@ using Xamarin.UITest; using Xamarin.UITest.Queries; using System.Text.RegularExpressions; using System.Threading; +#if __IOS__ +using Xamarin.UITest.iOS; +#endif namespace Xamarin.UITest { @@ -28,6 +31,17 @@ namespace Xamarin.UITest return results; } + +#if __IOS__ + public static void SendAppToBackground(this IApp app, TimeSpan timeSpan) + { + if(app is Xamarin.Forms.Controls.ScreenshotConditionalApp sca) + { + sca.SendAppToBackground(timeSpan); + Thread.Sleep(timeSpan.Add(TimeSpan.FromSeconds(2))); + } + } +#endif } } diff --git a/Xamarin.Forms.Core/Internals/NameScope.cs b/Xamarin.Forms.Core/Internals/NameScope.cs index 9419bf02b..bf20c1111 100644 --- a/Xamarin.Forms.Core/Internals/NameScope.cs +++ b/Xamarin.Forms.Core/Internals/NameScope.cs @@ -23,10 +23,7 @@ namespace Xamarin.Forms.Internals _names[name] = scopedElement; } - public static INameScope GetNameScope(BindableObject bindable) - { - return (INameScope)bindable.GetValue(NameScopeProperty); - } + public static INameScope GetNameScope(BindableObject bindable) => (INameScope)bindable.GetValue(NameScopeProperty); public static void SetNameScope(BindableObject bindable, INameScope value) { diff --git a/Xamarin.Forms.Platform.Android/AndroidTicker.cs b/Xamarin.Forms.Platform.Android/AndroidTicker.cs index 6640d934b..6b2c38971 100644 --- a/Xamarin.Forms.Platform.Android/AndroidTicker.cs +++ b/Xamarin.Forms.Platform.Android/AndroidTicker.cs @@ -43,7 +43,7 @@ namespace Xamarin.Forms.Platform.Android // If power saver is active, then animations will not run _energySaveModeDisabled = !powerSaveOn; - + // Notify the ticker that this value has changed, so it can manage animations in progress OnSystemEnabledChanged(); } @@ -56,8 +56,20 @@ namespace Xamarin.Forms.Platform.Android return false; } - var scale = global::Android.Provider.Settings.Global.GetFloat(resolver, global::Android.Provider.Settings.Global.AnimatorDurationScale, 0); - return scale > 0; + float animationScale; + + if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr1) + { + animationScale = global::Android.Provider.Settings.Global.GetFloat(resolver, global::Android.Provider.Settings.Global.AnimatorDurationScale, 1); + } + else + { +#pragma warning disable 0618 + animationScale = global::Android.Provider.Settings.System.GetFloat(resolver, global::Android.Provider.Settings.System.AnimatorDurationScale, 1); +#pragma warning restore 0618 + } + + return animationScale > 0; } public void Dispose() @@ -95,4 +107,4 @@ namespace Xamarin.Forms.Platform.Android SendSignals(); } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs index 310ef84ed..4d23ff744 100644 --- a/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs +++ b/Xamarin.Forms.Platform.iOS/CollectionView/ItemsViewController.cs @@ -45,7 +45,7 @@ namespace Xamarin.Forms.Platform.iOS // If we're updating from a previous layout, we should keep any settings for the SelectableItemsViewController around var selectableItemsViewController = Delegator?.SelectableItemsViewController; Delegator = new UICollectionViewDelegator(ItemsViewLayout, this); - + CollectionView.Delegate = Delegator; if (CollectionView.CollectionViewLayout != ItemsViewLayout) @@ -54,9 +54,9 @@ namespace Xamarin.Forms.Platform.iOS // Make sure the new layout is sized properly ItemsViewLayout.ConstrainTo(CollectionView.Bounds.Size); - + CollectionView.SetCollectionViewLayout(ItemsViewLayout, false); - + // Reload the data so the currently visible cells get laid out according to the new layout CollectionView.ReloadData(); } @@ -154,8 +154,19 @@ namespace Xamarin.Forms.Platform.iOS // This update is only relevant if you have a footer view because it's used to place the footer view // based on the ContentSize so we just update the positions if the ContentSize has changed - if(_footerUIView != null && _footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height) - UpdateHeaderFooterPosition(); + if (_footerUIView != null) + { + if (IsHorizontal) + { + if (_footerUIView.Frame.X != ItemsViewLayout.CollectionViewContentSize.Width) + UpdateHeaderFooterPosition(); + } + else + { + if (_footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height) + UpdateHeaderFooterPosition(); + } + } } protected virtual IItemsViewSource CreateItemsViewSource() @@ -172,7 +183,7 @@ namespace Xamarin.Forms.Platform.iOS public override nint NumberOfSections(UICollectionView collectionView) { - CheckForEmptySource(); + CheckForEmptySource(); return ItemsSource.GroupCount; } @@ -317,26 +328,53 @@ namespace Xamarin.Forms.Platform.iOS CollectionView.RegisterClassForCell(typeof(VerticalTemplatedCell), VerticalTemplatedCell.ReuseId); } + bool IsHorizontal => (ItemsView?.ItemsLayout as ItemsLayout)?.Orientation == ItemsLayoutOrientation.Horizontal; + void UpdateHeaderFooterPosition() { - var currentInset = CollectionView.ContentInset; - - nfloat headerHeight = _headerUIView?.Frame.Height ?? 0f; - nfloat footerHeight = _footerUIView?.Frame.Height ?? 0f; - - if(_headerUIView != null && _headerUIView.Frame.Y != headerHeight) - _headerUIView.Frame = new CoreGraphics.CGRect(0, -headerHeight, CollectionView.Frame.Width, headerHeight); - - if (_footerUIView != null && (_footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height)) - _footerUIView.Frame = new CoreGraphics.CGRect(0, ItemsViewLayout.CollectionViewContentSize.Height, CollectionView.Frame.Width, footerHeight); - - if (CollectionView.ContentInset.Top != headerHeight || CollectionView.ContentInset.Bottom != footerHeight) + if (IsHorizontal) { - CollectionView.ContentInset = new UIEdgeInsets(headerHeight, 0, footerHeight, 0); + var currentInset = CollectionView.ContentInset; - // if the header grows it will scroll off the screen because if you change the content inset iOS adjusts the content offset so the list doesn't move - // this changes the offset of the list by how much ever the header size has changed - CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X, CollectionView.ContentOffset.Y + (currentInset.Top - CollectionView.ContentInset.Top)); + nfloat headerWidth = _headerUIView?.Frame.Width ?? 0f; + nfloat footerWidth = _footerUIView?.Frame.Width ?? 0f; + + if (_headerUIView != null && _headerUIView.Frame.X != headerWidth) + _headerUIView.Frame = new CoreGraphics.CGRect(-headerWidth, 0, headerWidth, CollectionView.Frame.Height); + + if (_footerUIView != null && (_footerUIView.Frame.X != ItemsViewLayout.CollectionViewContentSize.Width)) + _footerUIView.Frame = new CoreGraphics.CGRect(ItemsViewLayout.CollectionViewContentSize.Width, 0, footerWidth, CollectionView.Frame.Height); + + if (CollectionView.ContentInset.Left != headerWidth || CollectionView.ContentInset.Right != footerWidth) + { + CollectionView.ContentInset = new UIEdgeInsets(0, headerWidth, 0, footerWidth); + + // if the header grows it will scroll off the screen because if you change the content inset iOS adjusts the content offset so the list doesn't move + // this changes the offset of the list by however much the header size has changed + CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X + (currentInset.Left - CollectionView.ContentInset.Left), CollectionView.ContentOffset.Y); + } + } + else + { + var currentInset = CollectionView.ContentInset; + + nfloat headerHeight = _headerUIView?.Frame.Height ?? 0f; + nfloat footerHeight = _footerUIView?.Frame.Height ?? 0f; + + if (_headerUIView != null && _headerUIView.Frame.Y != headerHeight) + _headerUIView.Frame = new CoreGraphics.CGRect(0, -headerHeight, CollectionView.Frame.Width, headerHeight); + + if (_footerUIView != null && (_footerUIView.Frame.Y != ItemsViewLayout.CollectionViewContentSize.Height)) + _footerUIView.Frame = new CoreGraphics.CGRect(0, ItemsViewLayout.CollectionViewContentSize.Height, CollectionView.Frame.Width, footerHeight); + + if (CollectionView.ContentInset.Top != headerHeight || CollectionView.ContentInset.Bottom != footerHeight) + { + CollectionView.ContentInset = new UIEdgeInsets(headerHeight, 0, footerHeight, 0); + + // if the header grows it will scroll off the screen because if you change the content inset iOS adjusts the content offset so the list doesn't move + // this changes the offset of the list by however much the header size has changed + CollectionView.ContentOffset = new CoreGraphics.CGPoint(CollectionView.ContentOffset.X, CollectionView.ContentOffset.Y + (currentInset.Top - CollectionView.ContentInset.Top)); + } } } @@ -379,9 +417,7 @@ namespace Xamarin.Forms.Platform.iOS if (formsElement != null) { - var request = formsElement.Measure(CollectionView.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins); - Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(0, -request.Request.Height, CollectionView.Frame.Width, request.Request.Height)); - + RemeasureLayout(formsElement); formsElement.MeasureInvalidated += OnFormsElementMeasureInvalidated; } else if (uiView != null) @@ -390,13 +426,25 @@ namespace Xamarin.Forms.Platform.iOS } } - void OnFormsElementMeasureInvalidated(object sender, EventArgs e) + void RemeasureLayout(VisualElement formsElement) { - if(sender is VisualElement formsElement) + if (IsHorizontal) + { + var request = formsElement.Measure(double.PositiveInfinity, CollectionView.Frame.Height, MeasureFlags.IncludeMargins); + Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(-request.Request.Width, 0, request.Request.Width, CollectionView.Frame.Height)); + } + else { var request = formsElement.Measure(CollectionView.Frame.Width, double.PositiveInfinity, MeasureFlags.IncludeMargins); Xamarin.Forms.Layout.LayoutChildIntoBoundingRegion(formsElement, new Rectangle(0, -request.Request.Height, CollectionView.Frame.Width, request.Request.Height)); + } + } + void OnFormsElementMeasureInvalidated(object sender, EventArgs e) + { + if (sender is VisualElement formsElement) + { + RemeasureLayout(formsElement); UpdateHeaderFooterPosition(); } } @@ -488,4 +536,4 @@ namespace Xamarin.Forms.Platform.iOS return (new UILabel { Text = $"{view}" }, null); } } -} \ No newline at end of file +} diff --git a/Xamarin.Forms.Platform.iOS/Extensions/FlowDirectionExtensions.cs b/Xamarin.Forms.Platform.iOS/Extensions/FlowDirectionExtensions.cs index ae62ee54b..48c6772b5 100644 --- a/Xamarin.Forms.Platform.iOS/Extensions/FlowDirectionExtensions.cs +++ b/Xamarin.Forms.Platform.iOS/Extensions/FlowDirectionExtensions.cs @@ -18,16 +18,25 @@ namespace Xamarin.Forms.Platform.iOS } } - internal static void UpdateFlowDirection(this UIView view, IVisualElementController controller) + internal static bool UpdateFlowDirection(this UIView view, IVisualElementController controller) { if (controller == null || view == null || !Forms.IsiOS9OrNewer) - return; + return false; + UISemanticContentAttribute updateValue = view.SemanticContentAttribute; if (controller.EffectiveFlowDirection.IsRightToLeft()) - view.SemanticContentAttribute = UISemanticContentAttribute.ForceRightToLeft; + updateValue = UISemanticContentAttribute.ForceRightToLeft; else if (controller.EffectiveFlowDirection.IsLeftToRight()) - view.SemanticContentAttribute = UISemanticContentAttribute.ForceLeftToRight; + updateValue = UISemanticContentAttribute.ForceLeftToRight; + + if(updateValue != view.SemanticContentAttribute) + { + view.SemanticContentAttribute = updateValue; + return true; + } + + return false; } internal static void UpdateTextAlignment(this UITextField control, IVisualElementController controller) diff --git a/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs b/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs index 93662a07b..8d3598e55 100644 --- a/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs +++ b/Xamarin.Forms.Platform.iOS/Renderers/TabletMasterDetailRenderer.cs @@ -195,7 +195,11 @@ namespace Xamarin.Forms.Platform.iOS if (layoutMaster) { var masterBounds = _masterController.View.Frame; - _masterWidth = (nfloat)Math.Max(_masterWidth, masterBounds.Width); + + if (Forms.IsiOS13OrNewer) + _masterWidth = masterBounds.Width; + else + _masterWidth = (nfloat)Math.Max(_masterWidth, masterBounds.Width); if (!masterBounds.IsEmpty) MasterDetailPage.MasterBounds = new Rectangle(0, 0, _masterWidth, masterBounds.Height); @@ -385,7 +389,12 @@ namespace Xamarin.Forms.Platform.iOS void UpdateFlowDirection() { - NativeView.UpdateFlowDirection(Element); + if(NativeView.UpdateFlowDirection(Element) && Forms.IsiOS13OrNewer && NativeView.Superview != null) + { + var view = NativeView.Superview; + NativeView.RemoveFromSuperview(); + view.AddSubview(NativeView); + } } class InnerDelegate : UISplitViewControllerDelegate diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7097.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7097.xaml new file mode 100644 index 000000000..3a3e14ca9 --- /dev/null +++ b/Xamarin.Forms.Xaml.UnitTests/Issues/Gh7097.xaml @@ -0,0 +1,34 @@ + + + + + + + Vertical + + + + + + +