Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Implement ScrollView Orientation #13657

Merged
merged 9 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<views:BasePage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Pages.ScrollViewPages.ScrollViewOrientationPage"
xmlns:views="clr-namespace:Maui.Controls.Sample.Pages.Base"
Title="ScrollView">
<views:BasePage.Content>
<Grid Padding="12" ColumnSpacing="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Text="Change Orientation:"
Style="{StaticResource Headline}" VerticalOptions="Center" />

<Picker x:Name="Orientation" Grid.Column="1" SelectedIndex="0" SelectedIndexChanged="OrientationSelectedIndexChanged" VerticalOptions="Start">
<Picker.Items>
<x:String>Vertical</x:String>
<x:String>Horizontal</x:String>
<x:String>Both</x:String>
<x:String>Neither</x:String>
</Picker.Items>
</Picker>
<ScrollView x:Name="ScrollViewer" Orientation="Horizontal" Grid.Row="1" Grid.ColumnSpan="2">
<Image Source="dotnet_bot.png" HeightRequest="1000" WidthRequest="1000" Aspect="AspectFit" />
</ScrollView>
</Grid>
</views:BasePage.Content>
</views:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;

namespace Maui.Controls.Sample.Pages.ScrollViewPages
{
public partial class ScrollViewOrientationPage
{
public ScrollViewOrientationPage()
{
InitializeComponent();
}

public void OrientationSelectedIndexChanged(object sender, EventArgs args)
{
ScrollViewer.Orientation = (ScrollOrientation)Orientation.SelectedIndex;
}

protected override void OnAppearing()
{
Orientation.SelectedIndex = 0;
base.OnAppearing();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new ScrollViewTemplatePageModel{ Spacing = 200, ScrollViewPadding = new Thickness(25),
ContentBackground = Colors.LightBlue, VerticalAlignment = LayoutOptions.Fill }),

new SectionModel(typeof(ScrollViewOrientationPage), "Orientation",
"Lock the orientation of your ScrollView",
new ScrollViewTemplatePageModel{ VerticalAlignment = LayoutOptions.Fill }),


};
}
Expand Down
53 changes: 39 additions & 14 deletions src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ public static void MapContent(IScrollViewHandler handler, IScrollView scrollView
// We don't actually have this mapped because we don't need it, but we can't remove it because it's public
public static void MapContentSize(IScrollViewHandler handler, IScrollView scrollView)
{
handler.PlatformView.UpdateContentSize(scrollView.ContentSize);
handler.PlatformView?.UpdateContentSize(scrollView.ContentSize);
}

public static void MapIsEnabled(IScrollViewHandler handler, IScrollView scrollView)
{
handler.PlatformView.UpdateIsEnabled(scrollView);
handler.PlatformView?.UpdateIsEnabled(scrollView);
}

public static void MapHorizontalScrollBarVisibility(IScrollViewHandler handler, IScrollView scrollView)
Expand All @@ -78,7 +78,10 @@ public static void MapVerticalScrollBarVisibility(IScrollViewHandler handler, IS

public static void MapOrientation(IScrollViewHandler handler, IScrollView scrollView)
{
// Nothing to do here for now, but we might need to make adjustments for FlowDirection when the orientation is set to Horizontal
if (GetContentView(handler.PlatformView) is ContentView currentContentContainer)
{
currentContentContainer.SetNeedsLayout();
}
}

public static void MapRequestScrollTo(IScrollViewHandler handler, IScrollView scrollView, object? args)
Expand Down Expand Up @@ -155,35 +158,34 @@ static void InsertContentView(UIScrollView platformScrollView, IScrollView scrol
Tag = ContentPanelTag
};

contentContainer.CrossPlatformArrange = ArrangeScrollViewContent(scrollView.CrossPlatformArrange, contentContainer, platformScrollView);
contentContainer.CrossPlatformArrange = ArrangeScrollViewContent(scrollView.CrossPlatformArrange, contentContainer, platformScrollView, scrollView);

platformScrollView.ClearSubviews();
contentContainer.AddSubview(platformContent);
platformScrollView.AddSubview(contentContainer);
}

static Func<Rect, Size> ArrangeScrollViewContent(Func<Rect, Size> internalArrange, ContentView container, UIScrollView platformScrollView)
static Func<Rect, Size> ArrangeScrollViewContent(Func<Rect, Size> internalArrange, ContentView container, UIScrollView platformScrollView, IScrollView scrollView)
{
return (rect) =>
{
if (container.Superview is UIScrollView scrollView)
if (container.Superview is UIScrollView uiScrollView)
{
// Ensure the container is at least the size of the UIScrollView itself, so that the
// cross-platform layout logic makes sense and the contents don't arrange outside the
// container. (Everything will look correct if they do, but hit testing won't work properly.)

var scrollViewBounds = scrollView.Bounds;
var scrollViewBounds = uiScrollView.Bounds;
var containerBounds = container.Bounds;

container.Bounds = new CGRect(0, 0,
Math.Max(containerBounds.Width, scrollViewBounds.Width),
Math.Max(containerBounds.Height, scrollViewBounds.Height));
container.Center = new CGPoint(container.Bounds.GetMidX(), container.Bounds.GetMidY());
}

var contentSize = internalArrange(rect);
platformScrollView.ContentSize = contentSize;
return contentSize;
var size = SetContentSizeForOrientation(platformScrollView, scrollView.Orientation, contentSize);
return size;
};
}

Expand Down Expand Up @@ -241,13 +243,13 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra
widthConstraint = AccountForPadding(widthConstraint, padding.HorizontalThickness);
heightConstraint = AccountForPadding(heightConstraint, padding.VerticalThickness);

var size = virtualView.CrossPlatformMeasure(widthConstraint, heightConstraint);
var crossPlatformSize = virtualView.CrossPlatformMeasure(widthConstraint, heightConstraint);

// Add the padding back in for the final size
size.Width += padding.HorizontalThickness;
size.Height += padding.VerticalThickness;
crossPlatformSize.Width += padding.HorizontalThickness;
crossPlatformSize.Height += padding.VerticalThickness;

platformView.ContentSize = size;
var size = SetContentSizeForOrientation(platformView, virtualView.Orientation, crossPlatformSize);

var finalWidth = ViewHandlerExtensions.ResolveConstraints(size.Width, virtualView.Width, virtualView.MinimumWidth, virtualView.MaximumWidth);
var finalHeight = ViewHandlerExtensions.ResolveConstraints(size.Height, virtualView.Height, virtualView.MinimumHeight, virtualView.MaximumHeight);
Expand Down Expand Up @@ -286,5 +288,28 @@ static double AccountForPadding(double constraint, double padding)
// Remove the padding from the constraint, but don't allow it to go negative
return Math.Max(0, constraint - padding);
}

static Size SetContentSizeForOrientation(UIScrollView platformScrollView, ScrollOrientation orientation, Size crossPlatformSize)
{
CGRect scrollViewBounds = platformScrollView.Bounds;
var contentSize = AccountForOrientation(crossPlatformSize, scrollViewBounds.Width, scrollViewBounds.Height, orientation);
platformScrollView.ContentSize = contentSize;
return contentSize;
}

internal static Size AccountForOrientation(Size size, double widthConstraint, double heightConstraint, ScrollOrientation orientation)
{
if (orientation is ScrollOrientation.Vertical or ScrollOrientation.Neither)
{
size.Width = widthConstraint;
}

if (orientation is ScrollOrientation.Horizontal or ScrollOrientation.Neither)
{
size.Height = heightConstraint;
}

return size;
}
}
}
2 changes: 1 addition & 1 deletion src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ static Microsoft.Maui.Platform.WebViewExtensions.UpdateUserAgent(this WebKit.WKW
Microsoft.Maui.WeakEventManager.HandleEvent(object? sender, object? args, string! eventName) -> void
static Microsoft.Maui.SizeRequest.operator !=(Microsoft.Maui.SizeRequest left, Microsoft.Maui.SizeRequest right) -> bool
static Microsoft.Maui.SizeRequest.operator ==(Microsoft.Maui.SizeRequest left, Microsoft.Maui.SizeRequest right) -> bool
virtual Microsoft.Maui.MauiUIApplicationDelegate.PerformFetch(UIKit.UIApplication! application, System.Action<UIKit.UIBackgroundFetchResult>! completionHandler) -> void
virtual Microsoft.Maui.MauiUIApplicationDelegate.PerformFetch(UIKit.UIApplication! application, System.Action<UIKit.UIBackgroundFetchResult>! completionHandler) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ Microsoft.Maui.WeakEventManager.HandleEvent(object? sender, object? args, string
static Microsoft.Maui.SizeRequest.operator !=(Microsoft.Maui.SizeRequest left, Microsoft.Maui.SizeRequest right) -> bool
static Microsoft.Maui.SizeRequest.operator ==(Microsoft.Maui.SizeRequest left, Microsoft.Maui.SizeRequest right) -> bool
Microsoft.Maui.LifecycleEvents.iOSLifecycle.PerformFetch
virtual Microsoft.Maui.MauiUIApplicationDelegate.PerformFetch(UIKit.UIApplication! application, System.Action<UIKit.UIBackgroundFetchResult>! completionHandler) -> void
virtual Microsoft.Maui.MauiUIApplicationDelegate.PerformFetch(UIKit.UIApplication! application, System.Action<UIKit.UIBackgroundFetchResult>! completionHandler) -> void