【问题标题】:Trouble with UIScrollView mixed layouts approachUIScrollView 混合布局方法的问题
【发布时间】:2020-04-30 16:40:56
【问题描述】:

我正在尝试关注UIScrollView And Autolayout Mixed approach

我见过其他类似的问题but with no accepted answer

我不确定这是不是我需要的。

请理解,我既不使用 Storyboard,也不使用 XIB。 我的所有视图都是在 Xamarin.iOS 中使用 c# 以编程方式创建的。这与目标 C 代码非常相似。

像许多其他人一样,我找不到让我的滚动视图真正滚动的方法。

所以在我的主视图控制器中,我在 ViewDidLoad() 中有以下内容:

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

        _scrollView = new UIScrollView
        {
            ShowsHorizontalScrollIndicator = false,
            TranslatesAutoresizingMaskIntoConstraints = false,
            AlwaysBounceVertical = true,
            Bounces = true
        };

        _refreshControl = new UIRefreshControl { TranslatesAutoresizingMaskIntoConstraints = false };
        _refreshControl.Enabled = true;
        _refreshControl.ValueChanged -= RefreshControl_ValueChanged;
        _refreshControl.ValueChanged += RefreshControl_ValueChanged;
        if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
        {
            _scrollView.RefreshControl = _refreshControl;
        }
        else
        {
            _scrollView.AddSubview(_refreshControl);
        }

        this.View.AddSubview(_scrollView);

        _scrollViewContainer = new UIView { TranslatesAutoresizingMaskIntoConstraints = true };
        _scrollView.AddSubview(_scrollViewContainer);

        _scrollView.Anchor(top: this.View.SaferAreaLayoutGuide().TopAnchor, leading: this.View.LeadingAnchor, bottom: this.View.SaferAreaLayoutGuide().BottomAnchor, trailing: this.View.TrailingAnchor);
    }

没什么特别的,我创建了我的 UIScrollView,将它添加为主视图的子视图。创建一个 UIRefreshControl 并将其添加到滚动视图中。创建一个普通的 UIView 并将其添加到 UIScrollView 以放置我所有的子视图。

因为我使用的是 AutoLayout,所以 ScrollView 锚定在主视图的约束内。

现在稍后在 ViewWillAppear() 中,我正在创建添加到普通 UIView 的卡片视图。这些卡片视图使用自动布局来堆叠在一起,第一张卡片被锚定到容器视图的顶部,前导和尾随锚点。

这些卡片中的所有内容都使用自动布局,基本上可以堆叠所有内容。

        public UIView BuildQOLCard()
        {
            CardView qolCard = new CardView();
            _scrollViewContainer.AddSubview(qolCard);
            qolCard.TranslatesAutoresizingMaskIntoConstraints = false;
            qolCard.CornerRadius = 5f;
            qolCard.ShadowOffsetHeight = 0;
            qolCard.ShadowOffsetWidth = 0;
            qolCard.BackgroundColor = UIColor.White;
...
           qolCard.Anchor(leading: _scrollViewContainer.LeadingAnchor, trailing: _scrollViewContainer.TrailingAnchor, top: _scrollViewContainer.TopAnchor, padding: new UIEdgeInsets(10f, 10f, 10f, 10f));

            return qolCard;
        }

当然,容器视图不会神奇地知道如何调整自己的大小。所以我必须给它一个尺寸,否则我什么都看不到。

所以我想了一个这样的助手:

public void SizeScrollViewContentSize()
{
    nfloat scrollViewHeight = 0.0f;
    foreach (UIView view in this._scrollViewContainer.Subviews)
    {
        scrollViewHeight += view.Frame.Size.Height;
    }

    this._scrollViewContainer.Frame = new CGRect(0, 0, this._scrollView.Frame.Width, scrollViewHeight);
}

但我似乎无法弄清楚何时调用它,因为子视图在这里但它们的大小是未知的。我尝试从 ViewDidLayoutSubviews() 调用它。

任何帮助表示赞赏。


编辑:应用已接受答案的建议,导致启用滚动,但滚动视图总是弹回顶部。

这是现在的代码:

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

    _scrollView = new UIScrollView
    {
        ShowsHorizontalScrollIndicator = false,
        TranslatesAutoresizingMaskIntoConstraints = false
    };

    if (!UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
    {
        _scrollView.AlwaysBounceVertical = true;
        _scrollView.Bounces = true;
    }

    _refreshControl = new UIRefreshControl { TranslatesAutoresizingMaskIntoConstraints = false };
    _refreshControl.Enabled = true;
    _refreshControl.ValueChanged -= RefreshControl_ValueChanged;
    _refreshControl.ValueChanged += RefreshControl_ValueChanged;
    if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
    {
        _scrollView.RefreshControl = _refreshControl;
    }
    else
    {
        _scrollView.AddSubview(_refreshControl);
    }

    this.View.AddSubview(_scrollView);

    _scrollViewContainer = new UIView { TranslatesAutoresizingMaskIntoConstraints = false };
    _scrollView.AddSubview(_scrollViewContainer);

    _scrollView.Anchor(top: this.View.SaferAreaLayoutGuide().TopAnchor, leading: this.View.LeadingAnchor, bottom: this.View.SaferAreaLayoutGuide().BottomAnchor, trailing: this.View.TrailingAnchor);

    // Only define the width of the container
    _scrollViewContainer.TopAnchor.ConstraintEqualTo(_scrollView.TopAnchor).Active = true;
    _scrollViewContainer.LeadingAnchor.ConstraintEqualTo(_scrollView.LeadingAnchor).Active = true;
    _scrollViewContainer.WidthAnchor.ConstraintEqualTo(_scrollView.WidthAnchor).Active = true;
}

卡一:

            CardView qolCard = new CardView();
            _scrollViewContainer.AddSubview(qolCard);
            qolCard.TranslatesAutoresizingMaskIntoConstraints = false;
            qolCard.CornerRadius = 5f;
            qolCard.ShadowOffsetHeight = 0;
            qolCard.ShadowOffsetWidth = 0;
            qolCard.BackgroundColor = UIColor.White;
...
           qolCard.Anchor(leading: _scrollViewContainer.LeadingAnchor, trailing: _scrollViewContainer.TrailingAnchor, top: _scrollViewContainer.TopAnchor, padding: new UIEdgeInsets(10f, 10f, 10f, 10f));

卡片 2:

               CardView goalsCard = new CardView();
                _scrollViewContainer.AddSubview(goalsCard);
                goalsCard.TranslatesAutoresizingMaskIntoConstraints = false;
                goalsCard.CornerRadius = 5f;
                goalsCard.ShadowOffsetHeight = 0;
                goalsCard.ShadowOffsetWidth = 0;
                goalsCard.BackgroundColor = UIColor.White;
...
               // Top should be constrained to the previous CardView's Bottom
                goalsCard.Anchor(leading: _scrollViewContainer.LeadingAnchor, trailing: _scrollViewContainer.TrailingAnchor, top: qolCard.BottomAnchor,padding: new UIEdgeInsets(10f, 10f, 10f, 10f));

最终锚点:

           // constraint the last card bottom to the bottom of the scrollview container
            goalsCard.Anchor(bottom: context._scrollViewContainer.BottomAnchor);

锚助手:

   internal static void Anchor(this UIView uIView, NSLayoutYAxisAnchor top = null, NSLayoutXAxisAnchor leading = null, NSLayoutYAxisAnchor bottom = null, NSLayoutXAxisAnchor trailing = null, UIEdgeInsets padding = default, CGSize size = default)
    {
        uIView.TranslatesAutoresizingMaskIntoConstraints = false;
        if (top != null)
        {
            uIView.TopAnchor.ConstraintEqualTo(top, padding.Top).Active = true;
        }

        if (leading != null)
        {
            uIView.LeadingAnchor.ConstraintEqualTo(leading, padding.Left).Active = true;
        }

        if (bottom != null)
        {
            uIView.BottomAnchor.ConstraintEqualTo(bottom, -padding.Bottom).Active = true;
        }

        if (trailing != null)
        {
            uIView.TrailingAnchor.ConstraintEqualTo(trailing, -padding.Right).Active = true;
        }

        if (size.Width != 0)
        {
            uIView.WidthAnchor.ConstraintEqualTo(size.Width).Active = true;
        }

        if (size.Height != 0)
        {
            uIView.HeightAnchor.ConstraintEqualTo(size.Height).Active = true;
        }
    }

【问题讨论】:

    标签: c# ios xamarin.ios ios-autolayout


    【解决方案1】:

    滚动视图需要定义其框架并且它需要定义其内容。如果内容被适当限制,滚动是自动的。

    我将逐步完成此操作 - 使用 Storyboard 只是为了让我们了解正在发生的事情。当我们通过代码执行时,绝对没有什么不同。

    从一个 View Controller 开始,添加一个滚动视图,并将其约束到所有四个边(我给它一个绿色背景,以便我们可以看到它):

    现在我们添加一个“容器”视图(青色背景),并将所有 4 个面都约束到滚动视图:

    如果我们运行它,我们会看到:

    那是因为“容器”没有宽度或高度。它的Leading / Trailing / Top / Bottom 约束定义了滚动视图的content

    所以,让我们将其 Equal Width 和 Equal Height 约束到滚动视图:

    运行它,我们会看到:

    这很好,除了...它永远不会滚动,因为它与滚动视图的高度相同。

    所以,我删除了 Equal Height - 因为我希望容器的 content 来确定它的高度。

    我添加了一个“CardView”并将其顶部/前导/尾随约束设置为“容器”的顶部/前导/尾随,具有 10 点“填充”和 100 的高度。我还给它一个底部约束“容器”的底部 (+10) - 将“将容器的底部”拉到 CardView 底部以下 10 分:

    导致:

    现在我添加第二个 CardView,将前导/尾随约束约束到容器的前导/尾随,具有 10 点“填充”和 100 的高度。我将其顶部约束设置为 CardView-1 的底部(+ 10),我将它的底部限制在“容器”的底部(+10):

    不幸的是,我们现在有一个来自 CardView-1 底部的 10 磅约束一个来自 CardView-2 底部的 10 磅约束 - 当然,不起作用。

    所以,让我们从 CardView-1 中删除“Bottom to Superview”约束

    我们得到了这个:

    让我们重复这些步骤以获得更多卡片视图,每次都将前导/尾随限制为“容器”,高度为 100,previous CardView 的从上到下...然后仅限制last CardView 底部到“容器”底部:

    和耶!我们可以滚动:

    通过代码执行此操作只需记住每个 CardView 的 Top 都应限制在 previous CardView 的底部 -- 除了 first 卡,您将其限制在“容器”的顶部和 last 卡,您将其限制在“容器”的底部。

    全部通过自动布局完成。无需做任何“计算高度和设置框架”代码。


    附带说明,您可以使用 StackView 并摆脱其中的大部分内容:

    • 忘记“容器”
    • 向滚动视图添加堆栈视图
      • 轴:垂直,间距:10
    • 使用 10 点填充将堆栈视图的所有 4 面限制为滚动视图
    • 为堆栈视图提供一个宽度约束,等于滚动视图宽度 -20(对于每侧 10 点的填充)

    然后,只需将您的 CardViews 添加为堆栈视图的排列子视图。

    然后....完成了!

    【讨论】:

    • 感谢您的精彩解释。
    • 只是一个快速更新,让您知道它会滚动,但它总是会弹回顶部。可能是什么问题?
    • @OlivierMATROT - 你必须向我展示你正在使用的代码才能找出原因。
    • @OlivierMATROT - 可能是几件事......你每次拉刷新时都添加一张新的“卡片”吗?如果是这样,当您向新添加的卡片添加底部锚点时,您是否删除了前一张卡片的底部锚点?此外,当刷新控件结束刷新时,它会将内容滚动回顶部......我不使用 Xamarin / C#,所以我不能给你一个完整的例子(我可以给你一个 Swift 或 Objective -C)
    • 当我拉动刷新时,我从 scrollViewContainer 中删除所有子视图(卡片)并将它们与更新的数据一起添加回来。问题是我什至不能在像 iPhone 4s 这样的小屏幕上滚动到第二张卡片的底部。一旦我释放向下滚动的手势,它就会弹回顶部。刷新 pat 的拉动对我来说似乎没问题。这真的是关于能够向上滚动的问题。希望对您有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2012-09-16
    • 2018-12-01
    • 1970-01-01
    • 2016-06-12
    • 2014-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多