Swift 中的尾随和前导约束以编程方式 (NSLayoutConstraints)

Posted

技术标签:

【中文标题】Swift 中的尾随和前导约束以编程方式 (NSLayoutConstraints)【英文标题】:Trailing and Leading constraints in Swift programmatically (NSLayoutConstraints) 【发布时间】:2016-09-27 20:16:06 【问题描述】:

我正在将一个 xib 视图添加到我的 ViewController 中。然后我将它的约束条件真正适合它

override func viewDidAppear(animated: Bool) 
    super.viewDidAppear(animated)
    ...
    ...
    view!.addSubview(gamePreview)
    gamePreview.translatesAutoresizingMaskIntoConstraints = false
    if #available(ios 9.0, *) 
        // Pin the leading edge of myView to the margin's leading edge
        gamePreview.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
        //Pin the trailing edge of myView to the margin's trailing edge
        gamePreview.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true

     else 
        // Fallback on earlier versions
        view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .TrailingMargin, relatedBy: .Equal, toItem: view, attribute: .TrailingMargin, multiplier: 1, constant: 0))

        view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .LeadingMargin, relatedBy: .Equal, toItem: view, attribute: .LeadingMargin, multiplier: 1, constant: 0))
    
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))

我正在尝试做的事情:真正适合我的视图,将其限制在顶部、前导、尾随到 ViewController 的视图,并具有前缀高度。我添加到主视图的视图有自己的透明背景视图,所以不需要边距(视图是设备的宽度大小,所以)。

我在 if 中放置了两对应该相等的行(在我的尝试中),因为 if 中的前 2 行实际上仅在 iOS9 中可用>,而我正在尝试这样做else 语句中的相同内容,适用于所有设备(从 iOS 8 开始)。

这是我得到的:

左边是iOS9+,右边是iOS8+。透明背景被涂成红色以显示发生了什么(不要介意图像中的不同高度,它们在应用程序中是相同的高度,而不是查看左右添加的边距)

我也尝试了AutoLayoutDSL-Swift,但没有帮助,我不是这方面的专家,但每次尝试只会让事情变得更糟。

如何使用经典的 NSLayoutConstraints 方法编写这些约束,以及如何使用 AutoLayoutDSL 之类的框架或其分支以更好的方式编写所有这些约束? (或替代方案,尽管现在我主要关注官方库)

【问题讨论】:

在 iOS 9 之前的代码中,您使用了 leadingMargintrailingMargin,因此您获得了利润。请改用leadingtrailing 另外,如果您进行了更改,那么相同的代码将适用于所有版本,iOS 8 及更高版本 有效!您还介意告诉我如何使用 AutoLayoutDSL-Swift 作为顶部锚点,特别是导航栏...我尝试了view => gamePreview.top == view.top,以完成我进入倒数第二个约束的操作,但视图位于导航栏下方。 ..这将帮助我将所有这些臃肿的代码减少到由 4 分割的行中。我尝试将偏移量与self.navigationController!.navigationBar.height 的总和相加,但它会产生编译错误,而它可以对实际数字进行数字化(如 30) . 抱歉,不知道那是什么。我几乎只使用故事板 @Paulw11 我知道是对的,但是因为我知道如何让它在 iOS9 及更高版本中工作,我在问题中添加了该代码以帮助人们更容易地猜测我愿意做什么,并且确实有效。我想知道为什么我没有早点提出 view => gamePreview.trailing == view.trailing ... 我担心左右属性,它一直在失败 【参考方案1】:

您需要使用leadingtrailing 属性,而不是leadingMargintrailingMargin 属性:

override func viewDidAppear(animated: Bool) 
    super.viewDidAppear(animated)
    ...
    ...
    view!.addSubview(gamePreview)
    gamePreview.translatesAutoresizingMaskIntoConstraints = false

    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0))            
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0))

    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
    view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))

【讨论】:

我如何获得该视图的当前约束?你知道吗??我找不到任何好的解决方案 “那个观点”是什么意思。通常,您在创建约束时会将对约束的引用存储在属性中,或者如果您使用情节提要,则使用 IBoutlet。 假设我不想要该视图约束的任何 IBOutlet 引用,并且 UIView.constraints 的数组属性返回一个数组。如何在不创建引用属性的情况下获得前导、尾随、顶部等?? 您必须遍历数组并尝试找到正确的约束。保持参考要简单得多。【参考方案2】:

感谢@Paulw11 猜到了……这就是解决方案

view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0))     
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0))        
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: gamePreview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute,multiplier: 1, constant: 131))

我还设法使用AutoLayoutDSL-Swift 为任何感兴趣的人重写了它

view => gamePreview.trailing == view.trailing
     => gamePreview.leading == view.leading
     => gamePreview.height == 131
     => gamePreview.top == view.top + self.navigationController!.navigationBar.bounds.height + UIApplication.sharedApplication().statusBarFrame.size.height

最后一行有点长,因为 view.top 指的是真正的视图顶部,并且不考虑由 statusBar 和 navigationBar 高度添加的填充。它们可以被替换为常量,等待有人提出更优雅的解决方案。

【讨论】:

【参考方案3】:

SWIFT 4 更新

stackView.translatesAutoresizingMaskIntoConstraints = false

stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: stackView, attribute: .trailing, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: stackView, attribute: .leading, multiplier: 1, constant: 0))

stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .top, relatedBy: .equal, toItem: self.addLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0))
stackView.addConstraint(NSLayoutConstraint(item: stackView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,multiplier: 1, 
constant: 131))

【讨论】:

【参考方案4】:

为什么不使用约束激活?你可以这样做:

    var anchors = [NSLayoutConstraint]()
    anchors.append(view.topAnchor.constraint(equalTo: gamePreview.topAnchor, constant: 0))
    anchors.append(view.leadingAnchor.constraint(equalTo: gamePreview.leadingAnchor, constant: 0))
    anchors.append(view.trailingAnchor.constraint(equalTo: gamePreview.trailingAnchor, constant: 0))
    anchors.append(view.heightAnchor.constraint(equalTo: gamePreview.heightAnchor, multiplier: 1, constant: 131))
    NSLayoutConstraint.activate(anchors)

这是另一个。希望能帮助到你。你可以根据父/子交换viewgamePreview,或者你可以为你的约束创建一个通用的辅助扩展。我在 YouTuber Here 提供的教程中找到了一个,并对其稍作修改以接受前导/尾随、左/右约束。您也可以在下面添加safeLayoutGuide,这使其对未来友好。像这样的:

    func anchor(_ top: UIView? = nil, left: UIView? = nil, bottom: UIView? = nil, right: UIView? = nil, width: UIView? = nil, height: UIView? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0, isForLeading: Bool = false) -> [NSLayoutConstraint] 
    translatesAutoresizingMaskIntoConstraints = false

    var anchors = [NSLayoutConstraint]()
    if #available(iOS 11.0, *) 
        if let top = top 
            anchors.append(topAnchor.constraint(equalTo: top.safeAreaLayoutGuide.topAnchor, constant: topConstant))
        

        if let left = left 
            if isForLeading  
                anchors.append(leadingAnchor.constraint(equalTo: left.safeAreaLayoutGuide.leadingAnchor, constant: leftConstant))
             else 
                anchors.append(leftAnchor.constraint(equalTo: left.safeAreaLayoutGuide.leftAnchor, constant: leftConstant))
            
        

        if let bottom = bottom 
            anchors.append(bottomAnchor.constraint(equalTo: bottom.safeAreaLayoutGuide.bottomAnchor, constant: -bottomConstant))
        

        if let right = right 
            if isForLeading  
                anchors.append(trailingAnchor.constraint(equalTo: right.safeAreaLayoutGuide.leadingAnchor, constant: rightConstant))
             else 
                anchors.append(rightAnchor.constraint(equalTo: right.safeAreaLayoutGuide.rightAnchor, constant: -rightConstant))
            
        
        if let width = width 
            anchors.append(widthAnchor.constraint(equalTo: width.safeAreaLayoutGuide.widthAnchor, multiplier: 1, constant: widthConstant))
         else if widthConstant > 0 
            anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
        

        if let height = height 
            anchors.append(heightAnchor.constraint(equalTo: height.safeAreaLayoutGuide.heightAnchor, multiplier: 1, constant: widthConstant))
         else if heightConstant > 0 
            anchors.append(widthAnchor.constraint(equalToConstant: heightConstant))
        
        anchors.forEach($0.isActive = true)
     else 
        if let top = top 
            anchors.append(topAnchor.constraint(equalTo: top.topAnchor, constant: topConstant))
        

        if let left = left 
            if isForLeading  
                anchors.append(leadingAnchor.constraint(equalTo: left.leadingAnchor, constant: leftConstant))
             else 
                anchors.append(leftAnchor.constraint(equalTo: left.leftAnchor, constant: leftConstant))
            
        

        if let bottom = bottom 
            anchors.append(bottomAnchor.constraint(equalTo: bottom.bottomAnchor, constant: -bottomConstant))
        

        if let right = right 
            if isForLeading  
                anchors.append(trailingAnchor.constraint(equalTo: right.leadingAnchor, constant: rightConstant))
             else 
                anchors.append(rightAnchor.constraint(equalTo: right.rightAnchor, constant: -rightConstant))
            
        

        if let width = width 
            anchors.append(widthAnchor.constraint(equalTo: width.widthAnchor, multiplier: 1, constant: widthConstant))
         else if widthConstant > 0 
            anchors.append(widthAnchor.constraint(equalToConstant: widthConstant))
        

        if let height = height 
            anchors.append(heightAnchor.constraint(equalTo: height.heightAnchor, multiplier: 1, constant: widthConstant))
         else if heightConstant > 0 
            anchors.append(widthAnchor.constraint(equalToConstant: heightConstant))
        
        anchors.forEach($0.isActive = true)
    

    return anchors

然后这样称呼它:

        _ = view.anchor(gamePreview, left: gamePreview, right: gamePreview, height: gamePreview, heightConstant: 131, isForLeading: true)

【讨论】:

以上是关于Swift 中的尾随和前导约束以编程方式 (NSLayoutConstraints)的主要内容,如果未能解决你的问题,请参考以下文章

如何在不制作 IBOutlet 的情况下以编程方式在 Swift 中为 UIView 高度约束设置动画?

无法以编程方式在 Swift 的堆栈视图中正确排列两个标签

以编程方式向UITextField添加约束

使用乘数的 Swift 约束与尾随但不领先

以编程方式添加前导/顶部约束

添加 UISearchController 并以编程方式使用约束对其进行定位