iOS 8.3 尝试添加文本字段时 UIAlertController 崩溃

iOS 8.3 UIAlertController crashes when trying to add a textfield

我有一个 iPad 应用程序。我正在创建一个 UIAlertController 并添加一个文本字段。它崩溃了。它仅在我添加文本字段时崩溃。

let alert = UIAlertController(title: "Enter Name", message:nil, preferredStyle: UIAlertControllerStyle.Alert);         
alert.addTextFieldWithConfigurationHandler { (textfield:UITextField!) -> Void in
                textfield.placeholder = "Sexy time";

            }
 alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: {(action:UIAlertAction!) -> Void in
      //Some action here   
 }));

 self.presentViewController(alert, animated: true, completion: nil);

我遇到一个有趣的崩溃,告诉我约束被搞砸了。此代码在 < 8.3 中运行良好,没有警告。即使在一个除了这段代码什么都没有的干净项目上,它也会崩溃 - 该项目需要是 iPad 上的 splitview 项目。

这是完整的堆栈跟踪加上奇怪的约束警告,它仅在尝试将文本字段添加到 alertController 后出现。

2015-04-10 15:25:07.155 Observation[18235:281813] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb66cf9dfc0 UITableView:0x7fb66b855000.left == UIView:0x7fb66fae68e0.left>
    When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2015-04-10 15:25:07.155 Observation[18235:281813] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb66cf9e010 UITableView:0x7fb66b855000.right == UIView:0x7fb66fae68e0.right>
    When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2015-04-10 15:25:07.155 Observation[18235:281813] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb66fb37f90 UITableView:0x7fb66b855000.top == UIView:0x7fb66fae68e0.top>
    When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2015-04-10 15:25:07.156 Observation[18235:281813] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fb66fb80580 UITableView:0x7fb66b855000.bottom == UIView:0x7fb66fae68e0.bottom>
    When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2015-04-10 15:25:13.589 Observation[18235:281813] View hierarchy unprepared for constraint.
    Constraint: <NSLayoutConstraint:0x7fb66cf9dfc0 UITableView:0x7fb66b855000.left == UIView:0x7fb66fae68e0.left>
    Container hierarchy: 
<UIView: 0x7fb66fa86e00; frame = (0 0; 0 0); layer = <CALayer: 0x7fb66fadf8e0>>
   | <UIView: 0x7fb66af3e080; frame = (0 0; 0 0); clipsToBounds = YES; layer = <CALayer: 0x7fb66fae32c0>>
   |    | <_UIAlertControllerShadowedScrollView: 0x7fb66fa68c80; frame = (0 0; 0 0); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x7fb66fa38a80>; layer = <CALayer: 0x7fb66fa97560>; contentOffset: {0, 0}; contentSize: {0, 0}>
   |    |    | <UIView: 0x7fb66fa87350; frame = (0 0; 0 0); layer = <CALayer: 0x7fb66fadf810>>
   |    |    |    | <UILabel: 0x7fb66fa88740; frame = (0 0; 0 0); text = 'Enter Name'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fb66fa94ed0>>
   |    |    |    | <UILabel: 0x7fb66fa73710; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fb66cc0ee10>>
   |    |    |    | <UIView: 0x7fb66fae68e0; frame = (0 0; 0 0); clipsToBounds = YES; layer = <CALayer: 0x7fb66fa90160>>
   |    | <UILabel: 0x7fb66fa3ad40; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fb66fa73680>>
   |    | <UICollectionView: 0x7fb66c130200; frame = (0 0; 0 0); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x7fb66faebab0>; layer = <CALayer: 0x7fb66fa3acf0>; contentOffset: {0, 0}; contentSize: {0, 0}> collection view layout: <_UIAlertControllerCollectionViewFlowLayout: 0x7fb66fae0b30>
    View not found in container hierarchy: <UITableView: 0x7fb66b855000; frame = (0 20; 768 1004); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x7fb66cf79f30>; layer = <CALayer: 0x7fb66cf600a0>; contentOffset: {0, 0}; contentSize: {768, 25}>
    That view's superview: NO SUPERVIEW
2015-04-10 15:25:13.594 Observation[18235:281813] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view.  Does the constraint reference something from outside the subtree of the view?  That's illegal. constraint:<NSLayoutConstraint:0x7fb66cf9dfc0 UITableView:0x7fb66b855000.left == UIView:0x7fb66fae68e0.left> view:<UIView: 0x7fb66fa86e00; frame = (0 0; 0 0); layer = <CALayer: 0x7fb66fadf8e0>>'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000102940c65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010221dbb7 objc_exception_throw + 45
    2   CoreFoundation                      0x0000000102940b9d +[NSException raise:format:] + 205
    3   Foundation                          0x0000000101daf479 -[NSLayoutConstraint _addToEngine:integralizationAdjustment:mutuallyExclusiveConstraints:] + 187
    4   UIKit                               0x00000001039bca34 __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke_2 + 474
    5   Foundation                          0x0000000101dbd1be -[NSISEngine withBehaviors:performModifications:] + 155
    6   UIKit                               0x00000001039bc83a __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke + 452
    7   UIKit                               0x00000001039bc64d -[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:] + 197
    8   UIKit                               0x00000001039bc933 __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke_2 + 217
    9   Foundation                          0x0000000101dbd1be -[NSISEngine withBehaviors:performModifications:] + 155
    10  UIKit                               0x00000001039bc83a __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke + 452
    11  UIKit                               0x00000001039bc64d -[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:] + 197
    12  UIKit                               0x00000001039bc933 __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke_2 + 217
    13  Foundation                          0x0000000101dbd1be -[NSISEngine withBehaviors:performModifications:] + 155
    14  UIKit                               0x00000001039bc83a __57-[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:]_block_invoke + 452
    15  UIKit                               0x00000001039bc64d -[UIView(AdditionalLayoutSupport) _switchToLayoutEngine:] + 197
    16  UIKit                               0x00000001033b5717 __40-[UIView(Hierarchy) layoutBelowIfNeeded]_block_invoke + 39
    17  Foundation                          0x0000000101dbd1be -[NSISEngine withBehaviors:performModifications:] + 155
    18  UIKit                               0x00000001033b5556 -[UIView(Hierarchy) layoutBelowIfNeeded] + 320
    19  UIKit                               0x000000010374a394 -[_UIAlertControllerAnimatedTransitioning animateTransition:] + 470
    20  UIKit                               0x000000010344fa4e __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 1867
    21  UIKit                               0x000000010336562c _applyBlockToCFArrayCopiedToStack + 314
    22  UIKit                               0x00000001033654a6 _afterCACommitHandler + 533
    23  CoreFoundation                      0x0000000102873ca7 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    24  CoreFoundation                      0x0000000102873c00 __CFRunLoopDoObservers + 368
    25  CoreFoundation                      0x0000000102869a33 __CFRunLoopRun + 1123
    26  CoreFoundation                      0x0000000102869366 CFRunLoopRunSpecific + 470
    27  GraphicsServices                    0x0000000106dd6a3e GSEventRunModal + 161
    28  UIKit                               0x0000000103341900 UIApplicationMain + 1282
    29  Observation                         0x0000000101612927 main + 135
    30  libdyld.dylib                       0x0000000104f60145 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

尝试将 alert.view.frame 明确设置为较小的值。我认为默认情况下它可能与应用程序视图一样大,在其子视图上触发它。

我只是 运行 我的测试项目中的代码 — 在 8.3 上一切正常。

View not found in container hierarchy:

如果您弄乱了 UIViewControllerchild/parent 关系,通常会出现此错误。有几点需要考虑:

  • 您用来显示警报的控制器位于最顶层,没有显示任何其他内容
  • 父控制器要么拥有您作为子控制器使用的控制器 (addChildViewController:),要么处于通过 presentViewController:animated:completion: 或类似的方式呈现它的状态
  • 控制器视图在呈现另一个视图之前加载 ViewController

另外,框架肯定有问题。

iOS 8.3 相关警报中似乎确实存在错误。它同时出现在(已弃用的)UIAlertView 和 iOS8-only UIAlertController 上。当我尝试向这些控制器中的任何一个添加文本字段时,出现以下崩溃:

 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The layout constraints still need update after sending -updateConstraints to <_UIKeyboardLayoutAlignmentView: 0x792d28e0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x792d2ab0>>.
_UIKeyboardLayoutAlignmentView or one of its superclasses may have overridden -updateConstraints without calling super. Or, something may have dirtied layout constraints in the middle of updating them.  Both are programming errors.'

没有文本字段的警报是可以的,但是显示样式为 UIAlertViewStylePlainTextInput 的 UIAlertView 或显示带有通过 addTextFieldWithConfigurationHandler 添加的文本字段的 UIAlertController 将导致在上面的崩溃中。

解决方法似乎是在调用 show 之前在 UIAlertController 上设置一个预防性框架。此帧在显示之前被覆盖,但可以防止崩溃。

if (NSClassFromString(@"UIAlertController")) {
    // iOS8
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Alert"
                                                                   message:@"Be alert, not alarmed"
                                                            preferredStyle:UIAlertControllerStyleAlert];
    [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
        textField.keyboardType = UIKeyboardTypeEmailAddress;

    }];
    alert.view.frame = CGRectMake(0.0, 0.0, 320.0, 400.0); // Workaround iOS8.3 bug - set this to larger than you'll need

    [self presentViewController:alert animated:YES completion:^{
        [alert.textFields[0] becomeFirstResponder];
    }];
} else {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Alert"
                                                    message:@"Be alert, not alarmed"
                                                   delegate:nil
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil];
    alert.alertViewStyle = UIAlertViewStylePlainTextInput;
    UITextField *emailField = [alert textFieldAtIndex:0];
    emailField.keyboardType = UIKeyboardTypeEmailAddress;

    [alert show];
}

我对新的 iOS 8.3 和 Swift 1.2 也有同样的问题。带有嵌套 UITableView 的 UIViewController 中的 UIAlertController 不能像开始主题那样工作。

所以,我做了一个真正的模态形式。

  1. 创建新的 UIViewController 宽度,将视图背景设置为黑色,不透明度为 0.4
  2. 添加带有两个按钮、标签和 UITextField 的 300x150 UIView
  3. 添加约束和其他需要的人员
  4. 将其他控制器与新控制器模态连接
  5. 并为新控制器指定特殊 class 作为 "PopupWindow"。然后创建这个 class:

    import UIKit
    
    class PopupWindow: UIViewController, UITextFieldDelegate {
    
        @IBOutlet var wrapper: UIView!
        @IBOutlet var btnCancel: UIButton!
        @IBOutlet var btnSave: UIButton!
        @IBOutlet var textField: UITextField!
        @IBOutlet var labelField: UILabel!
    
        var keyboardHeight: CGFloat = 0
        var data = [:]
        var closure: ((textField: UITextField!) -> Void)?
        var tmpText = ""
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            self.labelField.text = self.data["title"] as? String
            self.textField.text = self.data["text"] as? String
    
            self.btnCancel.setTitle("cancel", forState: UIControlState.Normal)
            var tmp = self.data["save"] != nil
                ? self.data["save"] as! String
                : "save"
            self.btnSave.setTitle(tmp, forState: UIControlState.Normal)
            self.btnSave.enabled = false
    
            self.wrapper.layer.cornerRadius = 5.0 as CGFloat
            self.textField.becomeFirstResponder()
        }
    
        @IBAction func onBtnCancel() {
            self.dismissViewControllerAnimated(true, completion: nil)
        }
    
        @IBAction func onBtnSave() {
            if self.closure != nil {
                self.dismissViewControllerAnimated(true, completion: {
                    _ in
                    self.closure!(textField: self.textField)
                })
            }
    
        }
    
        override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
            self.fixTopOffset()
        }
    
        override func viewWillAppear(animated: Bool) {
            super.viewWillAppear(animated)
    
            NSNotificationCenter.defaultCenter().addObserver(self, selector: "textFieldDidChange:", name: UITextFieldTextDidChangeNotification, object: self.textField)
    
            NSNotificationCenter.defaultCenter().addObserver(self, selector: "onKeyboadWillShow:", name: UIKeyboardWillShowNotification, object: nil)
            NSNotificationCenter.defaultCenter().addObserver(self, selector: "onKeyboadWillShow:", name: UIKeyboardWillChangeFrameNotification, object: nil)
            NSNotificationCenter.defaultCenter().addObserver(self, selector: "onKeyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
        }
    
        override func viewWillDisappear(animated: Bool) {
            super.viewWillDisappear(animated)
    
            NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: nil)
    
            NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
            NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillChangeFrameNotification, object: nil)
            NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
        }
    
        func onKeyboadWillShow(notification: NSNotification) {
            self.fixTopOffset()
            let info: NSDictionary = notification.userInfo!
            if let rectValue = info[UIKeyboardFrameBeginUserInfoKey] as? NSValue {
                let kbSize: CGRect = rectValue.CGRectValue()
                if self.keyboardHeight != kbSize.size.height {
                    self.keyboardHeight = kbSize.size.height
                    self.fixTopOffset()
                }
            }
        }
    
        func onKeyboardWillHide(notification: NSNotification) {
            self.fixTopOffset()
            if self.keyboardHeight != 0 {
                self.keyboardHeight = 0
                self.fixTopOffset()
            }
        }
    
        func fixTopOffset(landscape: Bool = true) {
            let height1: CGFloat = self.view.frame.height / 2
            let height2: CGFloat = height1 - self.keyboardHeight / 2
            let margin: CGFloat = height1 - height2
            for tmp in self.view.constraints() {
                if let constraint = tmp as? NSLayoutConstraint {
                    if constraint.firstAttribute == NSLayoutAttribute.CenterY {
                        if constraint.constant != margin {
                            constraint.constant = margin
                        }
                    }
                }
    
            }
        }
    
        func textFieldDidChange(notification: NSNotification) {
            if notification.object != nil {
                if let textField = notification.object as? UITextField {
                    self.btnSave.enabled = textField.text != self.tmpText
                }
            }
        }
    
    }
    

您需要通过Ctrl+拖动将所有IBOutlets与视图中对应的元素连接起来。

  1. 现在您可以在任何连接的控制器中使用它 window。例如:

    import UIKit
    
    class ExampleView: UIViewController {
    
        override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {       
            if segue.identifier == "showPopup" {
                if let controller = segue.destinationViewController as? PopupWindow {
                    controller.data = sender as! NSDictionary
                    // This action will be called after form submission
                    // We just print text in console
                    controller.closure = {
                        (textField: UITextField!) in
                        println(textField)
                    }
                }
            }
        }
    
        func showPopupWindow() {
            let data = [
                    "title": "My Window",
                    "save": "Save",
                    "text": "Do you realy want to save this text?",
            ]
            self.performSegueWithIdentifier("showPopup", sender: data)
        }
    }
    

现在我可以向控制器添加一个 segue 并在 prepareSegue 方法中编写任何闭包 - 它有效!

我在我的应用中制作了带有弹出窗口 window 的短视频 - http://youtu.be/JmqAAeUN-XU

啊哈,我认为这可能由 jcesarmobile 在另一个线程中回答: UIAlertController/UIAlertView scrolling on iOS 8

许多人为处理 UIAlertController 中的长文本错误而添加的解决方法现在会导致 8.3 中的崩溃。并且长文本问题已在 8.3 中修复,因此可以在 8.3

中有条件地避免这种情况的解决方法

我有一个类似的问题,在评论中也提到了。 收到请看这里

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The layout constraints still need update after sending -updateConstraints to <_UIKeyboardLayoutAlignmentView: 0x792d28e0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = >. _UIKeyboardLayoutAlignmentView or one of its superclasses may have overridden -updateConstraints without calling super. Or, something may have dirtied layout constraints in the middle of updating them. Both are programming errors.'

编辑: 此修复会根据您使用 Xcode 6 还是 Xcode 7 进行构建而变化,因此我添加了相关信息Xcode.

的两个版本

我 运行 今天进入这个,它说的是它不能将文本字段添加到视图控制器的视图中,或者它不能将自动布局约束添加到它的父视图中。这似乎是因为它还没有创建超级视图来添加它,所以它所有的恐慌和崩溃。

我发现的简单修复是在告诉警报控制器显示后添加文本字段。这对我来说是固定的,虽然我不确定它是否会影响任何东西,比如在出现警报时弹出的文本字段。

内置 Xcode 6

let alertController = UIAlertController(title: "Enter Name", message:nil, preferredStyle: .Alert)

let okAction = UIAlertAction(title: "Ok", style: .Default) { (_) -> Void in
    // Some action here
}
alertController.addAction(okAction)

presentViewController(alertController, animated: true, completion: nil)

// Add any text fields after presenting the alert controller to fix crash in iOS 8.3
alertController.addTextFieldWithConfigurationHandler { (textfield) -> Void in
    textfield.placeholder = "Name"
}

P.s。作为代码示例的旁注,请记住使用 Swift,您不需要在每一行的末尾使用 ;,尽管这样做并不重要。


内置 Xcode 7

当您使用 Xcode 7 构建应用程序时,Apple 似乎已经解决了这个问题。使用上面显示的方法将不再在 iOS 9 中显示文本字段(即使它在 iOS 8 中仍然正确显示)。

下面是一段代码,它本来应该是这样的,当在 Xcode 7 中构建时,它在 iOS 8 和 iOS 9 中正确运行。

let alertController = UIAlertController(title: "Enter Name", message:nil, preferredStyle: .Alert)

alertController.addTextFieldWithConfigurationHandler { (textfield) -> Void in
    textfield.placeholder = "Name"
}

let okAction = UIAlertAction(title: "Ok", style: .Default) { (_) -> Void in
    // Some action here
}
alertController.addAction(okAction)

presentViewController(alertController, animated: true, completion: nil)

在 Swift 2.0 和 Obj-C 中测试 Xcode 7 GM (7A218)

这立即修复了它(对我来说)。我遇到了与 Chris 描述的完全相同的错误:

答案只是确保在呈现 UIAlertController 时将动画设置为 "NO"。

[self presentViewController:alert animated:NO completion:nil];

如果在调用 -addTextFieldWithConfigurationHandler: ([alert isViewLoaded] == YES) 时加载了 UIAlertController 的视图,则会触发此错误。

如果您在调用 -addTextFieldWithConfigurationHandler 之前访问 alert.view,请将您使用 alert.view 进行的操作移到调用 -addTextFieldWithConfigurationHandler: 之后。

如@Dex 所述,我通过在添加文本字段后修改控制器的视图来解决此问题。我正在修改视图的色调。

controller.view.tintColor = ...some color...

将此移动到 "after" 添加文本字段为我修复了它。接受的答案和其他建议给我带来了其他错误。抱歉,post 的代表还没有对@Dex 的回答发表评论。

我真的不喜欢我看到的解决方案并提出了一个更简单的解决方案。当键盘可见并显示带有输入字段的警报时,崩溃发生(至少对我而言)。

我只是在显示警报之前关闭键盘,然后一切正常。

这似乎与 UIAlertController 视图的样式有关,正如@BigShay 所回避的那样。

在我的示例中,我在添加 UITextField 之前设置了视图的 tintColor,然后我在 iOS 中崩溃了。 8. @Baza207 给出的在显示警报后添加 UITextField 的解决方法避免了 iOS 8,但是它也会导致 iOS 9.

中根本没有出现任何文本字段

如果您在添加文本字段后将样式移动到,则它适用于iOS 8 和iOS 9(在iOS 8,并且 iOS 9 中没有缺少文本字段):

在 iOS 8

中崩溃

工作于 iOS9

alertController.view.tintColor = UIColor.blueColor()

alertController.addTextFieldWithConfigurationHandler { textField in
}

presentViewController(alertController, animated: true, completion: nil)

工作于 iOS 8

iOS9

中没有文本文本字段
alertController.view.tintColor = UIColor.blueColor()

presentViewController(alertController, animated: true, completion: nil)

alertController.addTextFieldWithConfigurationHandler { textField in
}

工作于 iOS 8

工作于 iOS9

alertController.addTextFieldWithConfigurationHandler { textField in
}

alertController.view.tintColor = UIColor.blueColor()

presentViewController(alertController, animated: true, completion: nil)