1

I'm trying to create a ViewController in Xamarin.iOS to show a modal page that scales height as needed, and when not enough height is available it will scroll the content.

Anyone suggestions how to set the UIScrollView to do this?

Currently I can show

  • a height scaled View that is Y centered (showSmallDesign = true)
  • and I can show a View that uses max height that can scroll (showSmallDesign = false)

But I can’t combine those two behaviours for some reason; creating a scroll view that expands height until parent height and enabling the scroll. I have tried a lot of different constraints but it wont work.

This is my UIController (copy paste and it should work)

public class DialogViewModelBaseController : UIViewController
{
    private readonly bool showSmallDesign = false;

    public override void ViewDidLoad()
    {
        ///
        /// Create Views
        /// 
        
        var brownOuterView = new UIView {BackgroundColor = UIColor.Brown};
        brownOuterView.Layer.CornerRadius = 20;
        View.Add(brownOuterView);

        var greenScrollView = showSmallDesign
            ? new UIView { BackgroundColor = UIColor.Green }
            : new UIScrollView() { BackgroundColor = UIColor.Green };
        brownOuterView.Add(greenScrollView);

        var cyanInnerView = new UIView {BackgroundColor = UIColor.Cyan};
        greenScrollView.Add(cyanInnerView);

        var redLabel = new UILabel
        {
            LineBreakMode = UILineBreakMode.WordWrap,
            Lines = 0,
            BackgroundColor = UIColor.Red
        };
        redLabel.Text = showSmallDesign
            ? "Lorem ipsum"
            : "Lorem ipsum dolor sit amet consectetur adipiscing elit. Aenean id lorem at tellus euismod gravida. Morbi scelerisque molestie nulla, pulvinar congue neque viverra ac. Donec euismod bibendum lectus eget ultrices. Ut eu vehicula lectus. In est orci, feugiat a sollicitudin eu, posuere ac sem. Quisque eu egestas lectus. Aliquam rhoncus dictum nisl a ullamcorper. Donec eget fermentum purus. Proin accumsan aliquam lacus ut convallis. Etiam tincidunt vitae nunc non sodales. Cras vitae nunc mattis, ultricies turpis et, laoreet nulla. Duis blandit sit amet libero ac cursus. Praesent ultricies erat laoreet turpis placerat scelerisque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id lorem at tellus euismod gravida. Morbi scelerisque molestie nulla, pulvinar congue neque viverra ac. Donec euismod bibendum lectus eget ultrices. Ut eu vehicula lectus. In est orci, feugiat a sollicitudin eu, posuere ac sem. Quisque eu egestas lectus. Aliquam rhoncus dictum nisl a ullamcorper. Donec eget fermentum purus. Proin accumsan aliquam lacus ut convallis. Etiam tincidunt vitae nunc non sodales. Cras vitae nunc mattis, ultricies turpis et, laoreet nulla. Duis blandit sit amet libero ac cursus. Praesent ultricies erat laoreet turpis placerat scelerisque. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id lorem at tellus euismod gravida. Morbi scelerisque molestie nulla, pulvinar congue neque viverra ac. Donec euismod bibendum lectus eget ultrices. Ut eu vehicula lectus. In est orci, feugiat a sollicitudin eu, posuere ac sem. Quisque eu egestas lectus. Aliquam rhoncus dictum nisl a ullamcorper. Donec eget fermentum purus. Proin accumsan aliquam lacus ut convallis. Etiam tincidunt vitae nunc non sodales. Cras vitae nunc mattis, ultricies turpis et, laoreet nulla. Duis blandit sit amet libero ac cursus. Praesent ultricies erat laoreet turpis placerat scelerisque. XXXX";
        cyanInnerView.Add(redLabel);

        var blueButton = new UIButton {BackgroundColor = UIColor.Blue};
        blueButton.SetTitle(" OK ", UIControlState.Normal);
        blueButton.TouchUpInside += (sender, args) =>
        {
            DismissViewController(true, null);
        };
        cyanInnerView.Add(blueButton);

        var outerMargin = 10;
        var innerMargin = 16;


        ///
        /// Layout Views
        ///
        
        View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        View.AddConstraints(
            brownOuterView.AtRightOfSafeArea(View, outerMargin),
            brownOuterView.AtLeftOfSafeArea(View, outerMargin)
            );

        if (showSmallDesign)
        {
            View.AddConstraints(
                // works for none scroll subview (small resizable height)
                brownOuterView.WithSameCenterY(View),
                brownOuterView.Height().LessThanOrEqualTo().HeightOf(View.SafeAreaLayoutGuide).Minus(2 * outerMargin) // this resizes height to max View size
            );
        }
        else
        {
            View.AddConstraints(
                // works for scroll subview (maximum height)
                brownOuterView.AtTopOfSafeArea(View, outerMargin),
                brownOuterView.AtBottomOfSafeArea(View, outerMargin)
            );
        }

        brownOuterView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        brownOuterView.AddConstraints(
            greenScrollView.AtRightOf(brownOuterView, outerMargin),
            greenScrollView.AtLeftOf(brownOuterView, outerMargin),
            greenScrollView.AtTopOf(brownOuterView, outerMargin),
            greenScrollView.AtBottomOf(brownOuterView, outerMargin)
            );

        greenScrollView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        greenScrollView.AddConstraints(
            cyanInnerView.AtTopOf(greenScrollView, innerMargin),
            cyanInnerView.AtLeftOf(greenScrollView, innerMargin),
            cyanInnerView.Width().EqualTo().WidthOf(greenScrollView).Minus(2 * innerMargin),
            cyanInnerView.Bottom().EqualTo().BottomOf(greenScrollView).Minus((nfloat)(innerMargin)) // constraint on last item to let scroll work
        );

        cyanInnerView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        cyanInnerView.AddConstraints(
            redLabel.WithSameWidth(cyanInnerView),
            redLabel.AtTopOf(cyanInnerView),
            blueButton.Below(redLabel, 20),
            blueButton.Bottom().EqualTo().BottomOf(cyanInnerView).Minus((nfloat)(20)) // constraint on last item to let scroll work
        );
    }

}

To show the Controller as Modal, just use something like:

    var dialogVC = new DialogViewModelBaseController()
    {
        ModalPresentationStyle = (DeviceInfo.IsPhone) ? UIModalPresentationStyle.OverCurrentContext : UIModalPresentationStyle.FormSheet,
        ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
    };

    var vc = CrossCurrentViewController.GetTopViewController();
    vc.PresentViewController(dialogVC, true, null);
Toine db
  • 709
  • 7
  • 25
  • You could create the UIScrollView directly . And set the `ContentSize` in different case, which up to its content . Check https://learn.microsoft.com/en-us/dotnet/api/uikit.uiscrollview.contentsize?view=xamarin-ios-sdk-12 . – Lucas Zhang Apr 07 '21 at 05:21
  • @LucasZhang-MSFT that would indeed be a workable approach, if I knew the size of the content ;-) This purpose of this UIViewController with scroll is to show different contents, some large and some very small. When checking the content in ViewDidLoad it always has 0 height, probably because it never has been drawn or measured yet. – Toine db Apr 07 '21 at 07:03

1 Answers1

0

I found a solution, a modal page that stretches for all content and when max screensize has been reached it will scroll.

The trick was resetting the HeightConstraint after 'ViewDidLayoutSubviews', because the child views then measured their height. And to catch device rotations I also reset the height after 'UIDeviceOrientationDidChangeNotification'.

Here it is, free to use:

public class DialogViewModelBaseController : UIViewController
{
    private readonly nfloat minimumHeight = 100;
    private readonly UIColor backgroundColor = UIColor.White;

    private readonly Margins outerMargin = new Margins(10, 10, 10, 10);
    private readonly Margins innerMargin = new Margins(16, 16, 16, 16);

    private UIView outerShellView;
    private UIView contentView;

    private FluentLayout viewHeightContraint;
    private NSObject deviceRotateNotificationToken;

    public override void ViewWillAppear(bool animated)
    {
        base.ViewWillAppear(animated);

        View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();

        deviceRotateNotificationToken = Foundation.NSNotificationCenter.DefaultCenter
            .AddObserver(new NSString("UIDeviceOrientationDidChangeNotification"), OnDeviceRotated);
    }

    public override void ViewWillDisappear(bool animated)
    {
        base.ViewWillDisappear(animated);
        deviceRotateNotificationToken?.Dispose();
    }

    public override void ViewDidLoad()
    {
        ///
        /// Create Views
        ///
        View.BackgroundColor = UIColor.Clear;
        outerShellView = new UIView { BackgroundColor = backgroundColor };
        outerShellView.Layer.CornerRadius = 20;
        outerShellView.ClipsToBounds = true;
        View.Add(outerShellView);

        var greenScrollView = new UIScrollView();
        outerShellView.Add(greenScrollView);

        contentView = new UIView();
        greenScrollView.Add(contentView);

        var redLabel = new UILabel
        {
            LineBreakMode = UILineBreakMode.WordWrap,
            Lines = 0
        };

        //redLabel.Text ="Lorem ipsum dolor sit amet";
        redLabel.Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae rutrum nulla. Etiam ultricies suscipit augue, non molestie lorem pellentesque sed. Vestibulum viverra pellentesque pharetra. Vivamus ut tincidunt lectus, eu maximus purus. Sed malesuada dignissim augue, sodales finibus ligula molestie at. Duis vitae felis vitae quam luctus pretium. Phasellus pulvinar ligula sit amet arcu porttitor facilisis. Vivamus a pellentesque urna. Sed elit elit, cursus vitae pharetra at, rutrum et lectus. Vivamus at dignissim lacus. Fusce viverra ultricies velit eu pretium. Donec tincidunt lacinia eros, nec accumsan libero viverra sed. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae rutrum nulla. Etiam ultricies suscipit augue, non molestie lorem pellentesque sed. Vestibulum viverra pellentesque pharetra. Vivamus ut tincidunt lectus, eu maximus purus. Sed malesuada dignissim augue, sodales finibus ligula molestie at. Duis vitae felis vitae quam luctus pretium. Phasellus pulvinar ligula sit amet arcu porttitor facilisis. Vivamus a pellentesque urna. Sed elit elit, cursus vitae pharetra at, rutrum et lectus. Vivamus at dignissim lacus. Fusce viverra ultricies velit eu pretium. Donec tincidunt lacinia eros, nec accumsan libero viverra sed. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae rutrum nulla. Etiam ultricies suscipit augue, non molestie lorem pellentesque sed. Vestibulum viverra pellentesque pharetra. Vivamus ut tincidunt lectus, eu maximus purus. Sed malesuada dignissim augue, sodales finibus ligula molestie at. Duis vitae felis vitae quam luctus pretium. Phasellus pulvinar ligula sit amet arcu porttitor facilisis. Vivamus a pellentesque urna. Sed elit elit, cursus vitae pharetra at, rutrum et lectus. Vivamus at dignissim lacus. Fusce viverra ultricies velit eu pretium. Donec tincidunt lacinia eros, nec accumsan libero viverra sed XXXX";
        contentView.Add(redLabel);

        var blueButton = new UIButton { BackgroundColor = UIColor.Gray };
        blueButton.SetTitle(" Close ", UIControlState.Normal);
        blueButton.TouchUpInside += (sender, args) =>
        {
            DismissViewController(true, null);
        };
        contentView.Add(blueButton);

        ///
        /// Layout Views
        ///

        View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        viewHeightContraint = outerShellView.Height().EqualTo(minimumHeight);

        View.AddConstraints(
            outerShellView.AtRightOfSafeArea(View, outerMargin.Right),
            outerShellView.AtLeftOfSafeArea(View, outerMargin.Left),
            outerShellView.WithSameCenterY(View),
            viewHeightContraint
            );

        outerShellView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        outerShellView.AddConstraints(
            greenScrollView.AtRightOf(outerShellView),
            greenScrollView.AtLeftOf(outerShellView),
            greenScrollView.AtTopOf(outerShellView),
            greenScrollView.AtBottomOf(outerShellView)
            );

        greenScrollView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        greenScrollView.AddConstraints(
            contentView.AtTopOf(greenScrollView, innerMargin.Top),
            contentView.AtLeftOf(greenScrollView, innerMargin.Left),
            contentView.Width().EqualTo().WidthOf(greenScrollView).Minus(innerMargin.Left + innerMargin.Right),
            contentView.Bottom().EqualTo().BottomOf(greenScrollView)//.Minus(innerMargin.Bottom) // constraint on last item to let scroll work
        );

        contentView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
        contentView.AddConstraints(
            redLabel.WithSameWidth(contentView),
            redLabel.AtTopOf(contentView),
            blueButton.Below(redLabel, 20),
            blueButton.Bottom().EqualTo().BottomOf(contentView) // constraint on last item to let scroll work
        );
    }

    public override void ViewDidLayoutSubviews()
    {
        base.ViewDidLayoutSubviews();

        SetViewHeight();
    }

    void OnDeviceRotated(NSNotification notification)
    {
        // Make sure View is resized after rotation, during ViewDidLayoutSubviews SubViews height probably isn't adjusted yet
        SetViewHeight();
    }

    private void SetViewHeight()
    {
        var contentHeight = contentView.Frame.Size.Height;
        if (contentHeight <= 0)
        {
            return;
        }

        var safeAreaHeight = View.SafeAreaLayoutGuide.LayoutFrame.Height;

        var contentHeightWithMargins = contentHeight
                                       + outerMargin.Top + outerMargin.Bottom
                                       + innerMargin.Top + innerMargin.Bottom;
        if (contentHeightWithMargins > safeAreaHeight)
        {
            contentHeightWithMargins = safeAreaHeight;
        }

        if (contentHeightWithMargins < minimumHeight)
        {
            contentHeightWithMargins = minimumHeight;
        }

        if (viewHeightContraint.Constant == contentHeightWithMargins)
        {
            return;
        }

        View.RemoveConstraints(viewHeightContraint);
        viewHeightContraint = outerShellView.Height().EqualTo(contentHeightWithMargins);
        View.AddConstraints(viewHeightContraint);
    }

}

TIP: just put everything in 'contentView' and don't forget to set constraint of last item in contentView to the bottom of the contentView.

Toine db
  • 709
  • 7
  • 25