如何使用 FluentLayout 为扩展高度设置 UIScrollView 约束?

How to set UIScrollView constraints for expanding height using FluentLayout?

我正在尝试在 Xamarin.iOS 中创建一个 ViewController 来显示一个根据需要缩放高度的模态页面,当没有足够的高度可用时它会滚动内容。

有人建议如何设置 UIScrollView 来执行此操作吗?

目前我可以显示

但出于某种原因我无法将这两种行为结合起来;创建一个滚动视图,将高度扩展到父高度并启用滚动。我已经尝试了很多不同的约束,但它不会起作用。

这是我的 UIController(复制粘贴它应该可以工作)

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
        );
    }

}

要将控制器显示为模态,只需使用如下内容:

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

    var vc = CrossCurrentViewController.GetTopViewController();
    vc.PresentViewController(dialogVC, true, null);

我找到了一个解决方案,一个可拉伸所有内容的模态页面,当达到最大屏幕尺寸时它会滚动。

诀窍是在 'ViewDidLayoutSubviews' 之后重置 HeightConstraint,因为子视图随后测量了它们的高度。 为了捕捉设备旋转,我还在 'UIDeviceOrientationDidChangeNotification'.

之后重置了高度

在这里,免费使用:

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);
    }

}

提示:只需将所有内容放入 'contentView' 并且不要忘记将 contentView 中最后一项的约束设置为 contentView 的底部。