Swift默认AlertViewController突破约束


78

我正在尝试使用.actionSheet样式的默认AlertViewController。由于某种原因,警报导致约束错误。只要没有通过按钮触发(显示)alertController,整个视图就不会出现约束错误。难道这是Xcode错误?

我得到的确切错误是这样的:

2019-04-12 15:33:29.584076+0200 Appname[4688:39368] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x6000025a1e50 UIView:0x7f88fcf6ce60.width == - 16   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000025a1e50 UIView:0x7f88fcf6ce60.width == - 16   (active)>

这是我使用的代码:

@objc func changeProfileImageTapped(){
        print("ChangeProfileImageButton tapped!")
        let alert = UIAlertController(title: "Change your profile image", message: nil, preferredStyle: .actionSheet)

        alert.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: nil))
        alert.addAction(UIAlertAction(title: "Online Stock Library", style: .default, handler: nil))
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        alert.view.tintColor = ColorCodes.logoPrimaryColor

        self.present(alert, animated: true)
    }

如您所见,这是非常基本的。这就是为什么我对我得到的奇怪行为感到非常困惑,因为此默认实现不应导致任何错误,对吗?

我得到的输出

尽管通过打破限制,警报可以在所有屏幕尺寸上正确显示,但我非常感谢我所获得的任何帮助。


您还在代码中构造任何其他约束吗?
Sh_Khan

@Sh_Khan不,我不知道。我的意思是,很明显,我在“警报”视图下还有其他视图,但是它们可以正常工作并且不会产生任何约束错误。但是我没有在AlertView约束条件下进行任何更改
linus_hologram

1
@linus_hologram,是否会引起屏幕上的视觉故障?如果是,那又如何?如果没有,请不要浪费时间尝试修复甚至没有损坏的东西。
Holex

1
约束日志并不总是正确的,有时会产生误导性的信息
Sh_Khan

1
我已经检查了调试器的问题,它是动态添加的约束...在呈现UIAlertController之前我找不到此约束...在下面的答案中查看更多
Agisight,

Answers:


42

该错误并不严重,似乎是Apple修复的错误。出现后,此约束以动画样式显示。在此处输入图片说明在演示之前,我尝试捕获并更改它(更改值,关系,优先级)–由于这种动态添加的约束,因此没有成功。

当您关闭动画self.present(alert, animated: false)并使用alert.view.addSubview(UIView())–错误消失。我无法解释,但是有效!

let alert = UIAlertController(title: "Change your profile image", message: nil, preferredStyle: .actionSheet)

alert.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "Online Stock Library", style: .default, handler: nil))
let cancel = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)

alert.addAction(cancel)
alert.view.addSubview(UIView()) // I can't explain it, but it works!

self.present(alert, animated: false)

1
这个答案与OP的约束问题有什么关系?
Holex

3
关闭动画+alert.view.addSubview(UIView())为我工作。但是没有动画,感觉就很奇怪...无论如何,谢谢您的解决方案。
泰达·魏

1
是的,我将其解释为来自Apple的错误。我解释了 但是,如果您可以解决问题,则可以尝试获取所需的约束并进行更改。我认为这不是一个好主意,因为这是一个错误。
Agisight,

1
我认为添加子视图会强制重新计算自动布局约束,从而使警告消失。
FormigaNinja '19

3
animation设置为true时,会发生此问题。我认为这是一个内部错误,由于actoinSheet正确显示,因此可以忽略。我很高兴可以将其范围缩小到根本原因,起初我教过它是我的代码。
杰里·奥卡福

44

以下内容删除了警告,而无需禁用动画。并且假设苹果最终解决了警告的根本原因,那么它就不会破坏其他任何东西。

extension UIAlertController {
    func pruneNegativeWidthConstraints() {
        for subView in self.view.subviews {
            for constraint in subView.constraints where constraint.debugDescription.contains("width == - 16") {
                subView.removeConstraint(constraint)
            }
        }
    }
}

然后可以这样使用:

// After all addActions(...), just before calling present(...)
alertController.pruneNegativeWidthConstraints()

1
效果很好,没有更多的原木污染!🥳
克拉斯

实际上,这实际上导致了更多问题(iOS 13.2.2),不确定在早期版本中的工作方式。导致UIAlertController显示固定在屏幕顶部。
jovanjovanovic

@jovanjovanovic很遗憾听到您遇到这种情况,但是我无法复制您所描述的内容。该解决方案仍然是删除警告而不会造成不利影响的解决方案。如果您有可以重现您所描述内容的代码示例,请分享。
jims1103

我在创建警报后就使用了这个角色,但似乎无法正常工作!放置代码的最佳位置在哪里?
卢卡斯·特利亚布

@LucasTegliabue在呈现alertcontroller之类之前使用它,就像让alertController = UIAlertController()alertController.pruneNegativeWidthConstraints()self.present(alertController,animated:true,completion:nil)
杰克

15

这是iOS版本中的新错误:

  • 12.2
  • 12.3
  • 12.4
  • 13.0
  • 13.1
  • 13.2
  • 13.2.3
  • 13.3
  • 13.4
  • 13.4.1
  • 13.5
  • 13.6
  • 14.0

我们唯一能做的就是向Apple提交错误报告(我只是这样做,您也应该这样做)。

发行后,我将尝试为新版本的iOS更新答案。


8

添加到此答案...这似乎为我消除了这个问题,不需要对现有代码进行任何更改。

extension UIAlertController {
    override open func viewDidLoad() {
        super.viewDidLoad()
        pruneNegativeWidthConstraints()
    }

    func pruneNegativeWidthConstraints() {
        for subView in self.view.subviews {
            for constraint in subView.constraints where constraint.debugDescription.contains("width == - 16") {
                subView.removeConstraint(constraint)
            }
        }
    }
}

2

安全解决方案

您不应删除该约束,因为将来会以正确的值使用该约束。

或者,您可以将其常数更改为正值:

class PXAlertController: UIAlertController {
    override func viewDidLoad() {
        super.viewDidLoad()

        for subview in self.view.subviews {
            for constraint in subview.constraints {
                if constraint.firstAttribute == .width && constraint.constant == -16 {
                    constraint.constant = 10 // Any positive value
                }
            }
        }
    }
}

然后使用以下方法初始化控制器:

let controller = PXAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)

我尝试了所有适用于Swift的建议解决方案,但均未成功。我正在使用XCode 11.5,针对ios 13.0进行编译,并在iPhone Xs Max中运行。还在变"<NSLayoutConstraint:0x28002b930 UIView:0x102e32420.width == - 16 (active)>"。如果不告诉您问题仍然存在,我无济于事。正如有人建议的那样,我只会忽略该错误,但我不喜欢它。
Nicola Mingotti

它根本不起作用。RichW的解决方案至少可在iOS 13上使用
Vyachaslav Gerchicov,

@VyachaslavGerchicov但是要小心,我相信将运行时将必需的约束更改为不需要时,较旧的iOS版本会崩溃。stackoverflow.com/questions/31186187/...
乔希贝恩菲尔德

1

摆脱NSLayoutConstraint错误的另一种方法是使用preferredStyle: .alert代替preferredStyle: .actionSheet。此方法不会产生警告,但会以模态显示菜单。


0

Objective-C的解决方案:

  1. 从UIAlertController继承您自己的Alert Controller
  2. 像以前的回复一样定义修剪功能

    @implementation TemplateAlertController
    
    -(void) viewDidLoad {
    
        [super viewDidLoad];
        [self mPruneNegativeWithConstraints];
    }
    
    -(void) mPruneNegativeWithConstraints {
    
        for (UIView* iSubview in [self.view subviews]) {
            for (NSLayoutConstraint* iConstraint in [iSubview constraints]) {
                if ([iConstraint.debugDescription containsString:@"width == - 16"]) {
                    [iSubview removeConstraint:iConstraint];
                }
            }
        }
    }
    
    @end
    

0

这里有趣的想法。我个人不喜欢删除约束或更改其值(大小)的想法。

由于问题取决于约束解决方案被迫达到必须打破授权(优先级1000)约束的位置,因此,一种比较不残酷的方法只是告诉框架,如果需要,可以打破此约束。

因此(基于Josh的“安全”类):

class PXAlertController: UIAlertController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tweakProblemWidthConstraints()
    }
    
    func tweakProblemWidthConstraints() {
        for subView in self.view.subviews {
            for constraint in subView.constraints {
                // Identify the problem constraint
                // Check that it's priority 1000 - which is the cause of the conflict.
                if constraint.firstAttribute == .width &&
                    constraint.constant == -16 &&
                    constraint.priority.rawValue == 1000 {
                    // Let the framework know it's okay to break this constraint
                    constraint.priority = UILayoutPriority(rawValue: 999)
                }
            }
        }
    }
}

这样的优点是它不会更改任何布局尺寸,而且在框架修复的情况下,也很有可能表现良好。

在iPhone SE模拟器中进行了测试(这给了我我最初的问题)-与约束相关的调试已消失。


可在iOS 13中使用,但不能在iOS 12中使用。在iPhone X模拟器上试用过
Vyachaslav Gerchicov

@RichW这是一个好方法。不过请小心,我相信将运行时将必需的约束更改为不需要时,较旧的iOS版本会崩溃。stackoverflow.com/questions/31186187/...
乔希贝恩菲尔德

0

如果要保留动画和所有约束,则应在呈现警报控制器之前找到一个负约束并将其设为正。

// Find negative constraint and make it positive
for subview in alert.view.subviews {
    for constraint in subview.constraints {
        if constraint.constant < 0 {
            constraint.constant = -constraint.constant
        }
    }
}

// Present alert controller
present(alert, animated: true)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.