UIScrollView 混合布局方法的问题
Trouble with UIScrollView mixed layouts approach
我正在尝试关注 UIScrollView And Autolayout Mixed approach。
我看过其他类似的问题but with no accepted answer。
我不确定这是我需要的。
请理解我既不使用故事板也不使用 XIB。
我的所有视图都是在 Xamarin.iOS 中使用 c# 以编程方式创建的。这与 objective 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;
}
}
滚动视图需要定义其框架和它需要定义其内容。如果内容约束得当,滚动是自动的。
我将逐步完成此操作 - 使用 Storyboard 只是为了让我们了解发生了什么。当我们通过代码完成时,绝对没有什么不同。
从一个视图控制器开始,添加一个滚动视图,并将其约束到所有四个边(我给它一个绿色背景以便我们可以看到它):
现在我们添加一个 "container" 视图(青色背景),并将所有 4 个边约束到滚动视图:
如果我们运行这个,我们看到:
那是因为 "container" 没有宽度或高度。它的 Leading / Trailing / Top / Bottom 约束定义了滚动视图的 content.
所以,让我们将它的等宽和等高约束到滚动视图:
运行它,我们看到这个:
很好,除了...它永远不会滚动,因为它与滚动视图的高度相同。
因此,我删除了等高 - 因为我希望容器的 内容 确定其高度。
我添加了一个 "CardView" 并将其顶部/前导/尾随约束设置为 "container" 的顶部/前导/尾随,具有 10 点 "padding" 和 100 的高度。我还给它一个底部约束到 "container" (+10) 的底部 - 这将 "pull the bottom of the container" 在 CardView 底部以下最多 10 点:
导致:
现在我添加第二个 CardView,将 Leading/Trailing 约束约束到容器的 Leading/Trailing,具有 10 点 "padding" 和 100 的高度。我将其顶部约束设置为 CardView 的底部-1(+10),我把它的底部约束在"container"(+10)的底部:
不幸的是,我们现在有一个来自 CardView-1 底部的 10 磅约束 和 有一个来自底部的 10 磅约束CardView-2 - 这当然行不通。
那么,让我们从 CardView-1
中删除 "Bottom to Superview" 约束
我们得到这个:
让我们对更多的卡片视图重复这些步骤,每次将前导/尾部限制为 "container",高度为 100,从上到下为 previous CardView。 .. 然后仅将 last CardView 的底部约束到 "container":
的底部
耶!我们可以滚动:
通过代码执行此操作只需记住每个 CardView 的顶部应限制在 previousCardView 的底部——除了对于 first 卡,您将其限制在 "container" 的顶部,而 last 卡,您将其限制在 "container".
全部通过自动布局完成。无需执行任何 "calculate height and set frame" 代码。
附带说明一下,您可以使用 StackView 并摆脱其中的大部分:
- 忘了"container"
- 向滚动视图添加堆栈视图
- 轴:垂直,间距:10
- 将堆栈视图的所有 4 个边限制为具有 10 磅填充的滚动视图
- 给堆栈视图一个等于滚动视图宽度 -20 的宽度约束(对于每一侧的 10 磅填充)
然后,只需将您的 CardView 添加为堆栈视图的 arrangedSubview。
然后....完成!
我正在尝试关注 UIScrollView And Autolayout Mixed approach。
我看过其他类似的问题but with no accepted answer。
我不确定这是我需要的。
请理解我既不使用故事板也不使用 XIB。 我的所有视图都是在 Xamarin.iOS 中使用 c# 以编程方式创建的。这与 objective 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;
}
}
滚动视图需要定义其框架和它需要定义其内容。如果内容约束得当,滚动是自动的。
我将逐步完成此操作 - 使用 Storyboard 只是为了让我们了解发生了什么。当我们通过代码完成时,绝对没有什么不同。
从一个视图控制器开始,添加一个滚动视图,并将其约束到所有四个边(我给它一个绿色背景以便我们可以看到它):
现在我们添加一个 "container" 视图(青色背景),并将所有 4 个边约束到滚动视图:
如果我们运行这个,我们看到:
那是因为 "container" 没有宽度或高度。它的 Leading / Trailing / Top / Bottom 约束定义了滚动视图的 content.
所以,让我们将它的等宽和等高约束到滚动视图:
运行它,我们看到这个:
很好,除了...它永远不会滚动,因为它与滚动视图的高度相同。
因此,我删除了等高 - 因为我希望容器的 内容 确定其高度。
我添加了一个 "CardView" 并将其顶部/前导/尾随约束设置为 "container" 的顶部/前导/尾随,具有 10 点 "padding" 和 100 的高度。我还给它一个底部约束到 "container" (+10) 的底部 - 这将 "pull the bottom of the container" 在 CardView 底部以下最多 10 点:
导致:
现在我添加第二个 CardView,将 Leading/Trailing 约束约束到容器的 Leading/Trailing,具有 10 点 "padding" 和 100 的高度。我将其顶部约束设置为 CardView 的底部-1(+10),我把它的底部约束在"container"(+10)的底部:
不幸的是,我们现在有一个来自 CardView-1 底部的 10 磅约束 和 有一个来自底部的 10 磅约束CardView-2 - 这当然行不通。
那么,让我们从 CardView-1
中删除 "Bottom to Superview" 约束我们得到这个:
让我们对更多的卡片视图重复这些步骤,每次将前导/尾部限制为 "container",高度为 100,从上到下为 previous CardView。 .. 然后仅将 last CardView 的底部约束到 "container":
的底部耶!我们可以滚动:
通过代码执行此操作只需记住每个 CardView 的顶部应限制在 previousCardView 的底部——除了对于 first 卡,您将其限制在 "container" 的顶部,而 last 卡,您将其限制在 "container".
全部通过自动布局完成。无需执行任何 "calculate height and set frame" 代码。
附带说明一下,您可以使用 StackView 并摆脱其中的大部分:
- 忘了"container"
- 向滚动视图添加堆栈视图
- 轴:垂直,间距:10
- 将堆栈视图的所有 4 个边限制为具有 10 磅填充的滚动视图
- 给堆栈视图一个等于滚动视图宽度 -20 的宽度约束(对于每一侧的 10 磅填充)
然后,只需将您的 CardView 添加为堆栈视图的 arrangedSubview。
然后....完成!