以编程方式添加具有约束的多个子视图会引发异常

Posted

技术标签:

【中文标题】以编程方式添加具有约束的多个子视图会引发异常【英文标题】:Programmatically adding multiple subviews with constraints throws exception 【发布时间】:2016-10-16 06:34:18 【问题描述】:

环境

Xcode-8 Swift-3 ios-10

意图

日耳曼的意图是将三个子视图添加到主视图中,以便它们从屏幕顶部开始按顺序相邻显示。最终意图还有更多,但这是我目前卡住的地方.

工作代码

如果我只是添加一个子视图,它可以使用以下代码(略有删节,添加行号以供参考):
01: class ViewController: UIViewController, UITextFieldDelegate 
02:    var textMatte: UIView!

03:    override func viewDidLoad() 
04:        super.viewDidLoad()
05:        setupTextfieldMatte(inView: view)
06:    

07:    func setupTextfieldMatte(inView: UIView) 
08:        textMatte = UIView()
09:        textMatte.backgroundColor = UIColor.lightGray
10:        textMatte.translatesAutoresizingMaskIntoConstraints = false
11:        inView.addSubview(textMatte)
12:        let tmGuide = textMatte.layoutMarginsGuide
13:        let inGuide = inView.layoutMarginsGuide
14:        tmGuide.topAnchor.constraint(equalTo: inGuide.topAnchor, constant: 40).isActive = true
15:        tmGuide.leadingAnchor.constraint(equalTo: inGuide.leadingAnchor, constant: 20).isActive = true
16:        tmGuide.trailingAnchor.constraint(equalTo: inGuide.trailingAnchor, constant: -20).isActive = true
17:        tmGuide.heightAnchor.constraint(equalToConstant: 40).isActive = true
18:    
19:    //... other code ...
20: 

破码

我想从 constraint 步骤中拆分 creation 步骤,以便进行“批处理”(因为还有更多的事情需要发生,因为最终这些子视图也将有自己的子视图...)。添加了相关行号,以便于进一步描述和(希望)你们的答案:
01: class ViewController: UIViewController, UITextFieldDelegate 
02:     var mattes = [UIView()]

03:     override func viewDidLoad() 
04:         super.viewDidLoad()
05:         for (index, title) in ["A", "B", "C"].enumerated()  // genericized for posting
06:            let v = createView(idx: index)
07:             mattes.append(v)
08:             view.addSubview(v)
09:         
10:         _ = mattes.popLast() //from testing/debugging found that there was an extraneous entry in the array
11:         addViewAnnotations(views: mattes, inView: self.view)
12:     

13:     func createView(idx: Int) -> UIView 
14:         let v = UIView()
15:         v.backgroundColor = UIColor.lightGray
16:         v.translatesAutoresizingMaskIntoConstraints = false
17:         v.tag = idx
18:         return v
19:     

20:     func addViewAnnotations(views: [UIView], inView: UIView) 
21:         let inGuide = inView.layoutMarginsGuide
22:         for (index, v) in views.enumerated() 
23:             let myGuide = v.layoutMarginsGuide
24:             if index == 0 
25:                 myGuide.topAnchor.constraint(equalTo: inGuide.topAnchor, constant: 40).isActive = true
***                 //^^^ libc++abi.dylib: terminating with uncaught exception of type NSException
26:             
27:             else 
28:                 myGuide.topAnchor.constraint(equalTo: views[index-1].layoutMarginsGuide.bottomAnchor, constant: 10).isActive = true
29:             
30:             myGuide.leadingAnchor.constraint(equalTo: inGuide.leadingAnchor, constant: 20).isActive = true
31:             myGuide.trailingAnchor.constraint(equalTo: inGuide.trailingAnchor, constant: -20).isActive = true
32:             myGuide.heightAnchor.constraint(equalToConstant: 40).isActive = true
33:         
34:     
35:     //... other code ...
36: 
错误发生在第 25 行(该行下方的注释中的错误消息),已通过该行上方和下方的 print 语句进行验证。我看不到其他错误/调试信息我有预计它可能在第 28 行中断,但第 25 行(在“损坏的代码”中)与第 14 行(在工作代码中)相同注意:如果我在行中添加了try,Xcode 告诉我:“在 'try' 表达式中没有调用抛出函数”

我觉得这是修改代码时经常出现的愚蠢疏忽之一,但我似乎无法找出问题的根源 - 所以我希望你们中的一个人能够。

2016 年 10 月 16 日更新

根据我添加的第一条评论中描述的修改,我对代码进行了更多尝试,发现问题仅在尝试使 [what is now] 最后一个约束 active i>:
001:    override func viewDidLoad() 
002:        super.viewDidLoad()

003:        for (index, title) in ["Small Blind", "Big Blind", "Ante"].enumerated() 
004:            let v = createView(idx: index)
005:            mattes.append(v)
006:            view.addSubview(v)
007:            let myGuide = v.layoutMarginsGuide
008:            let inGuide = view.layoutMarginsGuide
009:            myGuide.leadingAnchor.constraint(equalTo: inGuide.leadingAnchor, constant: 20 ).isActive = true
010:            myGuide.trailingAnchor.constraint(equalTo: inGuide.trailingAnchor, constant: -20).isActive = true
011:            myGuide.heightAnchor.constraint(equalToConstant: 40).isActive = true
012:            if index == 0 
013:                myGuide.topAnchor.constraint(equalTo: inGuide.topAnchor, constant: 40).isActive = true
014:            
015:            else 
016:                let x = myGuide.topAnchor.constraint(equalTo: mattes[index-1].bottomAnchor, constant: 10)
017:                x.isActive = true
                    //^^^ libc++abi.dylib: terminating with uncaught exception of type NSException
018:            
019:        
020:    
所有其他约束都没有问题。我是否使用似乎无关紧要: mattes[index-1].bottomAnchor mattes[index-1].layoutMarginsGuide.bottomAnchor 第一个计算结果为 <NSLayoutYAxisAnchor:0x174076540 "UIView:0x100b0d380.bottom">,第二个计算结果为 <NSLayoutYAxisAnchor:0x174076c00 "UILayoutGuide:0x174191e00'UIViewLayoutMarginsGuide'.bottom">如果这很重要?)如果我注释掉第 017 行,代码会运行,但第一个矩形位于正确的位置,其余两个则不正确 - 它们与屏幕顶部齐平,而不是在顶部指示器下方。那么 - 也许有更好的方法来设置它们?

【问题讨论】:

有趣的是,如果我将约束代码(第 21、23-32 行)移动到 viewDidLoad 函数中(在第 8 行之后),调整循环中的变化 - 它超越了行25 并且 确实 [现在] 在第 28 行中断...继续缓慢地绕行... 参见发布中的 UPDATE 2016-10-16,参考之前的评论 【参考方案1】:

异常的描述是什么?在添加约束之前,请务必将视图添加到它们的超级视图(在同一视图层次结构中)。您不能在不在同一层次结构中的视图之间创建约束。

在您的更新中,您使用index-1 作为mattes 数组的索引,这可能是第一次出现异常的原因。

我不知道这是否会导致问题,但我不会创建从一个视图的 layoutMarginsGuide 到超级视图的 layoutMarginsGuide 的约束。将该指南视为视图及其子视图。所以约束是从子视图本身(不是它的边距)到父视图的 layoutMarginsGuide。

【讨论】:

... 异常描述? libc++abi.dylib: terminating with uncaught exception of type NSException 就是这样。 ... 在添加约束之前将视图添加到超级视图(相同的视图层次结构)。您不能在视图之间创建约束 [not] 在同一层次结构中。“Broken Code”第 05-11 行 I:(a)创建子视图,(b)将子视图添加到 mattes 数组,(c ) 将子视图 as 子视图添加到主视图 - 都在约束之前。如果“A”有两个子视图“A1”和“A2”——“A1”和“A2”之间是否存在约束? (更多内容,受字符数和发帖时间限制) 在您的更新中,您使用 index-1 作为 mattes 数组的索引,这可能是第一次出现异常的原因。 if/else 子句应确保第一次通过在两个“遮罩”之间进行约束(抱歉,无法弄清楚如何正确格式化 cmets,尤其是在字符数内限制) ... 不要将视图的 layoutMarginsGuide 限制为 superview 的 layoutMarginsGuide。 ...约束 [是] 从子视图(不是它的边距)到父视图的 layoutMarginsGuide。 我尝试了不同的组合(一个“更新”代码):v.layoutMarginsGuide.topAnchor.constraint(equalTo : mattes[index-1].layoutMarginsGuide.bottomAnchor, 常量: 10)` v.topAnchor.constraint(equalTo: mattes[index-1].layoutMarginsGuide.bottomAnchor, 常量: 10)` v.topAnchor.constraint(equalTo: mattes [index-1].bottomAnchor, constant: 10)` 对于所有人来说,在x.isActive = true 上,它死于相同的异常。 你在index-1 之前的if 是对的,我的错。破译异常将是解决这个问题的关键。虽然您粘贴的行应该是抛出异常时控制台中的最后一行,但在此之上您应该看到堆栈跟踪,但在“First throw call stack”行之上,您应该看到异常的类型和抛出什么的描述。这会告诉你错误的原因。 (我以为我昨天回复了这个......)你(杰瑞)关于异常调用堆栈的描述是 - 通常 - 有效,但在这种情况下,除了一行我表明的。在网上搜索了一下,似乎这种由 Objective-C 代码生成的错误“已知”在 Swift 中不提供有用的错误处理。【参考方案2】:

我认为这是导致问题的多种因素,但首先让我介绍一下工作代码和几个屏幕截图

001: class ViewController: UIViewController, UITextFieldDelegate 
002:     var textfields: [UITextField] = []
003:     var labels:     [UILabel]     = []
004:     var mattes:     [UIView]      = []

005:     override func viewDidLoad() 
006:         super.viewDidLoad()

007:         view.backgroundColor = UIColor.blue
008:         for title in ["A", "B", "C"] 
009:             let matte     = setupMatte(inView: view)
010:             let textfield = setupTextfield(inView: view, title: title)
011:             let label     = setupLabel(inView: view, title: title)
012:             mattes.append(matte)
013:             textfields.append(textfield)
014:             labels.append(label)
015:         
016:         addConstraints(inView: view) // could probably leave off parameter, but for now...
017:     

018:     func setupMatte(inView: UIView) -> UIView 
019:         let matte = UIView()

             //...various view attribute configurations...
020:         matte.translatesAutoresizingMaskIntoConstraints = false
021:         inView.addSubview(matte)
022:         return matte
023:     

024:     func setupTextfield(inView: UIView, title: String) -> UITextField 
025:         let textfield = UITextField()

             //...various textfield attribute configurations...
026:         textfield.translatesAutoresizingMaskIntoConstraints = false
027:         inView.addSubview(textfield)
028:         return textfield
029:     

030:     func setupLabel(inView: UIView, title: String) -> UILabel 
031:         let label = UILabel()

             //...various label attribute configurations...
032:         label.translatesAutoresizingMaskIntoConstraints = false
033:         inView.addSubview(label)
034:         return label
035:     

036:     func addConstraints(inView: UIView) 
037:         for (index, matte) in mattes.enumerated() 
038:             let textfield = textfields[index]
039:             let label     = labels[index]

040:             matte.leadingAnchor.constraint(equalTo: inView.leadingAnchor).isActive = true
041:             matte.trailingAnchor.constraint(equalTo: inView.trailingAnchor).isActive = true
042:             matte.heightAnchor.constraint(equalToConstant: 50).isActive = true

043:             label.leadingAnchor.constraint(equalTo: matte.leadingAnchor, constant: 20).isActive = true
044:             label.widthAnchor.constraint(equalToConstant: 150).isActive = true

045:             textfield.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 5).isActive = true
046:             textfield.trailingAnchor.constraint(equalTo: matte.trailingAnchor, constant: -20).isActive = true

047:             if index == 0 
048:                 matte.topAnchor.constraint(equalTo: inView.topAnchor, constant: 20).isActive = true
049:             
050:             else 
051:                 matte.topAnchor.constraint(equalTo: mattes[index - 1].bottomAnchor, constant: 5).isActive = true
052:             

053:             textfield.centerYAnchor.constraint(equalTo: matte.centerYAnchor).isActive = true
054:             label.centerYAnchor.constraint(equalTo: textfield.centerYAnchor).isActive = true
055:         
056:     
         //... other code ...
057: 

有趣的是,虽然来自模拟器的图像可以正确处理横向模式,但当我在 iPhone 6s 上运行它时 - 当我将手机置于横向模式时它不会旋转 - 但我对此并不太担心时间,因为我正在开发的应用程序仅适用于纵向模式。

现在,我认为与我的原始代码有很大不同的部分: (注意:我不知道为什么,但出于某种原因,我最初将用于处理约束的函数命名为“注释”,这已在我当前的工作代码中修复)


坏了: var mattes = [UIView()]工作中: var mattes: [UIView] = []

我认为以上可能有很大的不同


BROKEN : viewDidLoad 函数循环: 创建子视图 将子视图附加到 mattes 数组 致电view.addSubview(v) 循环后: 弹出mattes数组的最后一个元素 添加注释

工作: viewDidLoad 函数循环:

创建子视图(matte)创建过程调用inView.addSubview(matte)(其中inViewviewself.view相同) 将子视图附加到 mattes 数组 循环后: 添加注释

我认为mattes 定义方式的差异消除了在添加约束之前弹出数组中最后一个(或任何)元素的需要


BROKEN :addViewAnnotations 中,我使用子视图 [父] 视图的layoutMarginGuide 进行了约束
v.layoutMarginGuide.topAnchor.constraint(equalTo: view.layoutMarginGuide.topAnchor, constant: 40).isActive = true

WORKING:addConstraints 我直接从子视图 [父] 视图进行了约束

matte.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true

(感谢 Jerry 指出这一点,我不明白其中的区别,而且我认为我在执行“更新”时没有完全修复我的代码)

以上可能是最显着的差异

【讨论】:

以上是关于以编程方式添加具有约束的多个子视图会引发异常的主要内容,如果未能解决你的问题,请参考以下文章

为啥我以编程方式创建的视图会忽略其约束?

将右对齐约束添加到以编程方式添加的子视图

以编程方式添加具有相对约束问题的标签

以编程方式将自动布局约束添加到恒定宽度和高度的子视图

以编程方式添加 UIConstraints 时不应用尾随约束

以编程方式将约束添加到相对于同级的视图