如何:在iOS中使用StackView View从XIB自我调整自定义视图 [英] How To: Self-Sizing Custom-View from XIB with StackView View in iOS

查看:1325
本文介绍了如何:在iOS中使用StackView View从XIB自我调整自定义视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近又开始使用Swift进入iOS开发阶段,现在我正在研究定制的UIControl。对于布局和灵活性,这个自定义视图是使用StackView构建的。

I've recently started diving into iOS development with Swift again and am now looking into a custom UIControl. For layout and flexibility this custom view is built using a StackView.

...基本上只是具有与StackView相同的功能,当我将它直接拖到故事板中时,我通常具有相同的功能 - 它调整大小以适应没有问题。这与自我调整tableview单元基本相同。

... is basically to simply have the same functionality with the StackView that I usually have when I drag it directly into the storyboard - where it resizes to fit no problem. This essentially the same as self-sizing tableview cells.

我可以将CustomView简化为简单示例:它是一个包含三个标签的StackView,对齐设置为中心分布 Equal Centering 。 StackView将固定到左,右和顶部布局指南(或尾随,前导和顶部)。我可以轻松地使用CustomView重新创建,从XIB加载转换为具有相同参数的CustomView - 我将两者都附加为屏幕截图。

I can reduce my CustomView to a simple example: It's a StackView with three labels in it, Alignment set to Center, Distribution Equal Centering. The StackView would be pinned to left, right and top layout guides (or trailing, leading and top). This I can easily re-create with a CustomView, loaded from XIB translate into a CustomView with the same parameters - I attached both as screenshot.

Simple StackView的屏幕截图

CustomView的屏幕截图

import UIKit

@IBDesignable
class CustomView: UIView {

    // loaded from NIB
    private weak var view: UIView!

    convenience init() {
        self.init(frame: CGRect())
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.loadNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.loadNib()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // we need to adjust the frame of the subview to no longer match the size used
        // in the XIB file BUT the actual frame we got assinged from the superview
        self.view.frame = bounds
    }

    private func loadNib ()  {
        self.view = Bundle (for: type(of: self)).loadNibNamed(
            "CustomView", owner: self, options: nil)! [0] as! UIView
        self.addSubview(self.view)
    }
}

你在这里看到的是我只是加载XIB文件,覆盖布局子视图以使框架适应实际视图,就是这样。

What you can see here is that I simply load the XIB file, override layout Subviews to adapt the frame to the actual view and that's it.

所以我的问题与通常如何接近有关。我的StackView中的标签具有内在的内容大小,即,通常StackView只是适应标签的高度而不抱怨 - 如果您在故事板中设计它。但是,如果您将所有内容放在XIB中并保持布局原样,那么如果视图固定在三个方向(如顶部,前导,尾随),则IB会抱怨未知高度。

So my questions are related to how this is usually approached. The labels in my StackView have an intrinsic content size, i.e., usually the StackView simply adapts to the height of the labels without complaining - IF you design it in the storyboard. It doesn't though, if you put all of it in a XIB and leave the layout as it is - the IB will complain about an unknown height if the view is pinned three ways as in the screenshot (top, leading, trailing).

我阅读了自动布局指南,并观看了WWDC视频AutoLayout之谜,但这个话题对我来说还是一个新鲜事,我认为我没有正确的做法。所以这就是我需要帮助的地方

I read the Auto-Layout Guide and watched the WWDC videos "Mysteries of AutoLayout" but the topic is still quite new to me and I don't think I got the right approach yet. So this is where I need help

我首先可以做和应该做的是设置 translatesAutoresizingMaskIntoConstraints false 这样的我没有获得自动生成的约束并定义自己的约束。所以我可以将 loadNib 更改为:

What I first could and should do is to set translatesAutoresizingMaskIntoConstraints false such that I don't get auto-generated constraints and define my own constraints. So I could change loadNib into this:

private func loadNib ()  {
    self.view = Bundle (for: type(of: self)).loadNibNamed(
        "CustomView", owner: self, options: nil)! [0] as! UIView
    self.view.translatesAutoresizingMaskIntoConstraints = false
    self.addSubview(self.view)

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .bottom
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .bottom
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self
        , attribute: .top
        , relatedBy: .equal
        , toItem: self.view
        , attribute: .top
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .leading
        , relatedBy: .equal
        , toItem: self
        , attribute: .leading
        , multiplier: 1.0
        , constant: 0))

    self.addConstraint(NSLayoutConstraint (
        item: self.view
        , attribute: .trailing
        , relatedBy: .equal
        , toItem: self
        , attribute: .trailing
        , multiplier: 1.0
        , constant: 0))
}

基本上我确实拉伸视图以适应完整在故事板中我的UIView的宽度并限制它在StackView的顶部和底部到顶部和底部。

Basically I did stretch the view to fit the full width of my UIView in the Storyboard and restrained it's top and bottom to top and bottom of the StackView.

我使用这个时遇到了很多问题,XCode 8崩溃了一个,所以我不太清楚该怎么做。

I had tons of problems with XCode 8 crashing on me when using this one so I'm not quite sure what to make of it.

我还可以通过检查所有的ArrangeSubviews <的高度来简单地覆盖 intrinsicContentSize / code>并取一个最高的那个,将我的视图高度设置为:

I could also simply override intrinsicContentSize by checking the height of all my arrangedSubviews and take the one that is the highest, set my view height to it:

override var intrinsicContentSize: CGSize {
    var size = CGSize.zero

    for (_ , aSubView) in (self.view as! UIStackView).arrangedSubviews.enumerated() {
        let subViewHeight = aSubView.intrinsicContentSize.height
        if  subViewHeight > size.height {
            size.height = aSubView.intrinsicContentSize.height
        }
    }

    // add some padding
    size.height += 0
    size.width = UIViewNoIntrinsicMetric
    return size
}

虽然这肯定会给我更大的灵活性我也必须使内在内容大小无效,检查动态类型以及我宁愿避免的许多其他东西。

While this surely gives me more flexibility I'd also have to invalidate the intrinsic content size, check the Dynamic Type and lots of other stuff I'd rather avoid.

或者有更好的方法,例如使用 UINib.instantiate ?我真的找不到最佳实践方法。

Or is there a better way, e.g. by using UINib.instantiate? I really couldn't find a best-practice way to do that.

真的,为什么?我只将StackView移动到CustomView中。那么为什么StackView现在不再适应自己,为什么我必须设置顶部和底部约束?

Really, why? I only moved the StackView into a CustomView. So why does the StackView stop adapting itself now, why do I have to set the top and bottom constraints?

迄今为止最有帮助的是关键短语 https://stackoverflow.com/a/24441368

What was helping the most so far is that key-phrase in https://stackoverflow.com/a/24441368 :


一般来说,自动布局是以自上而下的方式执行的。在其他
字样中,首先执行父视图布局,然后执行任何子
视图布局。

"In general, auto layout is performed in a top-down fashion. In other words, a parent view layout is performed first, and then any child view layouts are performed."

..但我真的不太确定。

.. but I'm really not so sure about it.

假设我禁用 translatesAutoresizingMaskIntoConstraints 然后我不能像IB中的tableview单元格那样设置约束?它总是转换为类似label.top = top的东西,因此标签将被拉伸而不是继承尺寸/高度的StackView。此外,我无法将StackView固定到IB的XIB文件的容器视图中。

Assuming I disable translatesAutoresizingMaskIntoConstraints then I can't set constraints like for tableview cells in IB? It'll always translate into something like label.top = top and thus the label would be stretched instead of the StackView inheriting the size/height. Also I can't really pin the StackView to it's container view in the IB for the XIB file.

希望有人可以帮助我。干杯!

Hope someone can help me out there. Cheers!

推荐答案

以下是您的问题的一些答案,但顺序不同:

Here are some answers to your questions, although in a different order:


(5)为什么我必须在代码中添加约束?

(5) And why do I have to add the constraints in code?

假设我禁用translatesAutoresizingMaskIntoConstraints然后我
不能像IB中的tableview单元那样设置约束吗?它总是
转换成类似label.top = top的东西,因此标签将被拉伸
而不是继承大小/高度的StackView。另外
我无法将StackView固定到它的IB容器视图中,只需
的XIB文件。

Assuming I disable translatesAutoresizingMaskIntoConstraints then I can't set constraints like for tableview cells in IB? It'll always translate into something like label.top = top and thus the label would be stretched instead of the StackView inheriting the size/height. Also I can't really pin the StackView to it's container view in the IB for the XIB file.

在此示例中,您必须在代码中添加约束,因为您从nib加载任意视图,然后将其添加到编码 CustomView 中。笔尖中的视图没有预先知道它将被置于什么上下文或层次结构中,因此不可能提前定义如何将自身约束到未知的未来超视图。对于故事板上的StackView或原型单元格内的情况,该上下文以及其父视图都是已知的。

You have to add the constraints in code, in this example, because you are loading an arbitrary view from a nib, and then adding it to a your coded CustomView. The view in the nib has no pre-existing knowledge of what context or hierarchy it will be placed into, so it can't possible defined ahead of time how to constrain itself to an unknown future superview. In the case of the StackView on the storyboard, or inside a prototype cell, that context is known as well as its parent view.

您可以考虑这个的一个不错的选择例如,如果您不想手动编写约束,则使您的CustomView子类UIStackView而不是UIView 。由于UIStackView会自动生成适当的约束,因此它将为您节省手动编码。有关示例代码,请参阅下一个答案。

One nice alternative you can consider for this case, if you don't want to manually write constraints, is to make your CustomView subclass UIStackView instead of UIView. Since UIStackView generates appropriate constraints automatically, it will save you the manual coding. See next answer for some sample code.


(3)我是否以建议的方式加载XIB?

(3) Am I even loading the XIB in the "proposed" way ?

或者有更好的方法,例如通过使用UINib.instantiate?我真的
找不到最好的做法。

Or is there a better way, e.g. by using UINib.instantiate? I really couldn't find a best-practice way to do that.

我不知道处理将笔尖加载到自定义视图中的标准或右边方法。我已经看到了各种各样有效的方法。但是,我会注意到您在示例代码中所做的工作超出了所需的工作量。这是一种实现相同结果的最小方法,但也使用UIStackView子类而不是UIView子类(如上所述):

I'm not aware of a "standard" or "right" way to handle loading a nib into a custom view. I've seen a variety of approaches that all work. However, I will note that you are doing more work than needed in your sample code. Here is a more minimal way of accomplishing the same outcome, but also using a UIStackView subclass instead of a UIView subclass (as mentioned above):

class CustomView: UIStackView {
    required init(coder: NSCoder) {
        super.init(coder: coder)
        distribution = .fill
        alignment = .fill
        if let nibView = Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)?.first as? UIView {
            addArrangedSubview(nibView)
        }
    }
}

请注意,因为CustomView现在是UIStackView的子类,所以它将自动创建任何子视图所需的约束。在这种情况下,因为分发 alignment 都设置为 .fill ,它会将子视图的边缘固定到它自己的边缘,如你所愿。

Note that because CustomView is now a subclass of UIStackView, it will create the constraints needed for any subviews automatically. In this case, because distribution and alignment are both set to .fill, it will pin the edges o the subview to its own edges, as you want.


(4)为什么我有首先要做到这一点?

(4) Why do I have to do this in the first place?

真的,为什么?我只将StackView移动到CustomView中。那么为什么
StackView现在停止适应自己,为什么我必须设置顶部
和底部约束?

Really, why? I only moved the StackView into a CustomView. So why does the StackView stop adapting itself now, why do I have to set the top and bottom constraints?

实际上,这里的关键问题是你的StackView的对齐属性 .center 。这就是说StackView中的标签将垂直居中,但它不会将它们限制在StackView的顶部或底部。这就像在视图内的子视图中添加centerY约束一样。您可以将外部视图设置为所需的任何高度,子视图将居中,但它不会推出外部视图的顶部和底部,因为中心约束仅限制中心,而不是顶部和底部边缘。

Actually, the key issue here is your StackView's alignment property of .center. That is saying that the labels in StackView will be centered vertically, but it does not constrain them to the top or the bottom of the StackView. It's just like adding a centerY constraint to a subview inside a view. You can set the outer view to any height you want, and the subview will be centered, but it does not "push out" the top and bottom of the outer view, because center constraints only constrain centers, not top and bottom edges.

在StackView的故事板版本中,StackView上方的视图层次结构是已知的,并且还有通过调整大小掩码自动生成约束的选项。无论哪种方式,UIKit都知道最终的StackView高度,并且标签将在该高度内垂直居中,但实际上不会影响它。 您需要将StackView的对齐方式设置为.fill,或者将标签的顶部和底部的其他约束添加到StackView的顶部和底部,以便自动布局以确定StackView应该有多高be。

In your storyboard version of the StackView, the view hierarchy above that StackView is known, and there are also options to generate constraints automatically from resizing masks. Either way, the final StackView heigh is known to UIKit and the labels will be centered vertically within that height, but will not actually effect it. You need to either set the alignment of your StackView to be .fill, or add additional constraints from the tops and bottoms of the labels to the top and bottom of the StackView in order for auto layout to determine how tall the StackView should be.


(2)或者我应该覆盖intrinsicContentSize?

(2) Or should I override intrinsicContentSize ?

这没有多大帮助,因为内部内容大小本身不会推出父视图的边缘,除非子视图的边缘也被约束到父视图的边缘视图。在任何情况下,只要您解决上面提到的 .center 对齐问题,您就会自动获得所需的所有正确的内在内容大小行为。

This wouldn't help much, because the intrinsic content size by itself won't push out the edges of a parent view unless the edges of the subview are also constrained to the edges of the parent view. In any event, you will get all the correct intrinsic content size behavior you need in this case automatically, so long as you address the .center alignment issue mentioned above.


(1)我应该设置约束并禁用translatesAutoresizingMaskIntoConstraints吗?

(1) Should I set constraints and disable translatesAutoresizingMaskIntoConstraints ?

这通常是一种有效的方法。如果你按照我上面描述的那样继承UIStackView,你也不需要这样做。

This would normally be a valid approach. If you subclass UIStackView as I describe above, though, you don't need to do either.

如果您:


  1. 创建您的CustomView作为UIStackView的子类,如上面的示例代码

  1. Create your CustomView as a subclass of UIStackView as in the sample code above

在自定义类的故事板上创建一个视图 CustomView 。如果你想让CustomView正确地自我调整大小,你需要赋予它约束,而不是使用调整大小掩码中自动生成的约束。

Create a view on your storyboard set to your custom class CustomView. If you want the CustomView to self-size correctly, you need to give it constraints and not use auto-generated constraints from the resizing mask.

更改StackView在您的笔尖中要么将对齐设置为 .fill ,要么在顶部和底部之间有其他约束至少一个标签(很可能是大中心标签)和堆栈视图的顶部和底部

Change the StackView in your nib to either have the alignment set to .fill or to have additional constraints between the top and bottom of at least one of the labels (most likely the large center one) and the top and bottom of the stack view

然后自动布局应该正常工作,您的自定义视图与StackView应按预期布局。

Then auto layout should work properly and your custom view with the StackView should layout as expected.

这篇关于如何:在iOS中使用StackView View从XIB自我调整自定义视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆