以编程方式添加具有约束的多个子视图会引发异常
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)
(其中inView
与view
或self.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 指出这一点,我不明白其中的区别,而且我认为我在执行“更新”时没有完全修复我的代码)
以上可能是最显着的差异
【讨论】:
以上是关于以编程方式添加具有约束的多个子视图会引发异常的主要内容,如果未能解决你的问题,请参考以下文章