Swift 中的自定义视图:导致子视图的边框和背景消失的约束

Custom View in Swift: Constraints Causing Subview's Border and Background to Disappear

我已经开始使用 Swift 在 Xcode 中创建自定义视图。我决定使用 http://www.thinkandbuild.it/building-a-custom-and-designabl-control-in-swift/ 中显示的方法,允许我在 Interface Builder 中设置控件的属性。

更新: 我继续构建视图,在自定义视图的子视图中排列标签并将子视图与视图对齐。我最终在两个级别上都使用了自动布局和约束,并设法以这种方式解决了宽度问题。我相应地更新了下面的代码。

还有两个问题:

  1. 作为最后一步,我设置了 txtButton.setTranslatesAutoresizingMaskIntoConstraints(false) 并设置了 txtButton 子视图的约束 => 子视图的边框和背景消失了
  2. 固有尺寸在 IB 中不可见,即我收到 IB 中报告的布局问题,建议将高度设置为 0 或 16。

自定义视图类:

import UIKit

@IBDesignable public class TextButtonView: UIView {

    @IBInspectable var borderColor: UIColor = UIColor.clearColor()
    @IBInspectable var borderWidth: CGFloat = 0
    @IBInspectable var cornerRadius: CGFloat = 0
    @IBInspectable var viewBackgroundColor: UIColor = UIColor.clearColor()

    @IBInspectable var mainText: String = ""
    @IBInspectable var mainTextSize: CGFloat = 15.0
    @IBInspectable var mainTextColor: UIColor = UIColor.blackColor()

    @IBInspectable var secText: String = ""
    @IBInspectable var secTextSize: CGFloat = 15.0
    @IBInspectable var secTextColor: UIColor = UIColor.blackColor()

    @IBInspectable var horizMargin: CGFloat = 5.0
    @IBInspectable var secHorizOffset: CGFloat = 0.0
    @IBInspectable var verticalMargin: CGFloat = 3.0
    @IBInspectable var lineSpacing: CGFloat = 10.0


    var txtButton: UIControl!
    var buttonHeight: CGFloat = 0.0


    #if TARGET_INTERFACE_BUILDER
    override func willMoveToSuperview(newSuperview: UIView?) {

        // Build the TextButton.
        txtButton = TextButton(
            borderColor: self.borderColor,
            borderWidth: self.borderWidth,
            cornerRadius: self.cornerRadius,
            viewBackgroundColor: self.viewBackgroundColor,
            mainText: self.mainText,
            mainTextSize: self.mainTextSize,
            mainTextColor: self.mainTextColor,
            secText: self.secText,
            secTextSize: self.secTextSize,
            secTextColor: self.secTextColor,
            horizMargin: self.horizMargin,
            secHorizOffset: self.secHorizOffset,
            verticalMargin: self.verticalMargin,
            lineSpacing: self.lineSpacing,
            frame: self.bounds)

        // Add the TextButton as subview of this view
        self.addSubview(txtButton)

        // Remember height for setting intrinsic content size.
        buttonHeight = txtButton.frame.size.height

        // Set remaining attributes for the container view.
        self.backgroundColor = UIColor.clearColor()

        // Setting constraints for the subview.
        txtButton.setTranslatesAutoresizingMaskIntoConstraints(false)
        self.addConstraint(NSLayoutConstraint(item: txtButton, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0))
        self.addConstraint(NSLayoutConstraint(item: txtButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
        self.addConstraint(NSLayoutConstraint(item: txtButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0))
    }

    #else
    override public func awakeFromNib() {
        super.awakeFromNib()

        // Build the TextButton.
        txtButton = TextButton(
            borderColor: self.borderColor,
            borderWidth: self.borderWidth,
            cornerRadius: self.cornerRadius,
            viewBackgroundColor: self.viewBackgroundColor,
            mainText: self.mainText,
            mainTextSize: self.mainTextSize,
            mainTextColor: self.mainTextColor,
            secText: self.secText,
            secTextSize: self.secTextSize,
            secTextColor: self.secTextColor,
            horizMargin: self.horizMargin,
            secHorizOffset: self.secHorizOffset,
            verticalMargin: self.verticalMargin,
            lineSpacing: self.lineSpacing,
            frame: self.bounds)

        // Add the TextButton as subview of this view.
        self.addSubview(txtButton)

        // Remember height for setting intrinsic content size.
        buttonHeight = txtButton.frame.size.height

        // Set remaining attributes for the container view.
        self.backgroundColor = UIColor.clearColor()

        // Setting constraints for the subview.
        txtButton.setTranslatesAutoresizingMaskIntoConstraints(false)
        self.addConstraint(NSLayoutConstraint(item: txtButton, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0))
        self.addConstraint(NSLayoutConstraint(item: txtButton, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
        self.addConstraint(NSLayoutConstraint(item: txtButton, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0))
    }
    #endif

    override public func intrinsicContentSize() -> CGSize {

        return CGSize(width: 250, height: buttonHeight)
    }
}

对照:

import UIKit

class TextButton: UIControl {

    // Designable properties and default values.
    var borderColor: UIColor?
    var borderWidth: CGFloat?
    var cornerRadius: CGFloat?
    var viewBackgroundColor: UIColor?
    var mainText: String?
    var mainTextSize: CGFloat?
    var mainTextColor: UIColor?
    var secText: String?
    var secTextSize: CGFloat?
    var secTextColor: UIColor?
    var horizMargin: CGFloat?
    var secHorizOffset: CGFloat?
    var verticalMargin: CGFloat?
    var lineSpacing: CGFloat?

    convenience init(
        borderColor: UIColor,
        borderWidth: CGFloat,
        cornerRadius: CGFloat,
        viewBackgroundColor: UIColor,
        mainText: String,
        mainTextSize: CGFloat,
        mainTextColor: UIColor,
        secText: String,
        secTextSize: CGFloat,
        secTextColor: UIColor,
        horizMargin: CGFloat,
        secHorizOffset: CGFloat,
        verticalMargin: CGFloat,
        lineSpacing: CGFloat,
        frame: CGRect) {

            self.init(frame: frame)

            self.mainText = mainText
            self.mainTextSize = mainTextSize

            // Button margins.
            self.horizMargin = horizMargin
            self.verticalMargin = verticalMargin
            self.secHorizOffset = secHorizOffset
            self.lineSpacing = lineSpacing

            // Define the Fonts
            let mainFont = UIFont(name: "Helvetica Neue", size: mainTextSize)
            let secFont = UIFont(name: "Helvetica Neue", size: secTextSize)

            // Create main label.
            let mainLabel: UILabel = UILabel()
            mainLabel.backgroundColor = UIColor.clearColor()
            mainLabel.textColor = mainTextColor
            mainLabel.textAlignment = .Left
            mainLabel.font = mainFont
            mainLabel.text = mainText

            // Calculate the main label's height.
            var mainLabelDummy: UILabel = mainLabel
            mainLabelDummy.sizeToFit()
            var mainLabelHeight: CGFloat = mainLabelDummy.frame.size.height

            // Create secondary label.
            let secLabel: UILabel = UILabel()
            secLabel.backgroundColor = UIColor.clearColor()
            secLabel.textColor = secTextColor
            secLabel.textAlignment = .Left
            secLabel.font = secFont
            secLabel.text = secText

            // Calculate the secondary label's height.
            var secLabelDummy: UILabel = secLabel
            secLabelDummy.sizeToFit()
            var secLabelHeight: CGFloat = secLabelDummy.frame.size.height

            // Add labels to view.
            addSubview(mainLabel)
            addSubview(secLabel)

            // Set constraints for labels.
            mainLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
            secLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
            self.addConstraint(NSLayoutConstraint(item: mainLabel, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: horizMargin))
            self.addConstraint(NSLayoutConstraint(item: mainLabel, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0 - horizMargin))
            self.addConstraint(NSLayoutConstraint(item: mainLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: verticalMargin))
            self.addConstraint(NSLayoutConstraint(item: secLabel, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: horizMargin + secHorizOffset))
            self.addConstraint(NSLayoutConstraint(item: secLabel, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0 - horizMargin))
            self.addConstraint(NSLayoutConstraint(item: secLabel, attribute: .Top, relatedBy: .Equal, toItem: mainLabel, attribute: .Bottom, multiplier: 1, constant: lineSpacing))

            // Adjust frame to match content.
            self.frame.size.height =
                2 * verticalMargin
                + 2 * borderWidth
                + lineSpacing
                + mainLabelHeight
                + secLabelHeight

            // Set remaining view properties.
            self.layer.borderColor = borderColor.CGColor
            self.layer.borderWidth = borderWidth
            self.layer.cornerRadius = cornerRadius
            self.backgroundColor = viewBackgroundColor
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

进一步阅读后,我提出了以下解决方案,通过删除子视图进行了简化:

import UIKit

@IBDesignable class TextButtonView: UIControl {

    // Properties accessible in Interface Builder.
    @IBInspectable var borderColor: UIColor = UIColor.clearColor() {didSet { updateUI() }}
    @IBInspectable var borderWidth: CGFloat = 0 {didSet { updateUI() }}
    @IBInspectable var cornerRadius: CGFloat = 0 {didSet { updateUI() }}
    @IBInspectable var backgrColor: UIColor = UIColor.clearColor() {didSet { updateUI() }}
    @IBInspectable var mainText: String = "" {didSet { updateUI() }}
    @IBInspectable var mainTextSize: CGFloat = 20.0 {didSet { updateUI() }}
    @IBInspectable var mainTextColor: UIColor = UIColor.blackColor() {didSet { updateUI() }}
    @IBInspectable var secText: String = "" {didSet { updateUI() }}
    @IBInspectable var secTextSize: CGFloat = 12.0 {didSet { updateUI() }}
    @IBInspectable var secTextColor: UIColor = UIColor.blackColor() {didSet { updateUI() }}
    @IBInspectable var horizMargin: CGFloat = 0.0 {didSet { updateUI() }}
    @IBInspectable var secHorizOffset: CGFloat = 0.0 {didSet { updateUI() }}
    @IBInspectable var verticalMargin: CGFloat = 0.0 {didSet { updateUI() }}
    @IBInspectable var lineSpacing: CGFloat = 0.0 {didSet { updateUI() }}

    var mainLabel: UILabel!
    var secLabel: UILabel!
    var textButtonHeight: CGFloat = 0.0
    var fontName: String = "Helvetica Neue"

    required init(coder: NSCoder) {
        super.init(coder:coder)
        setupUI()
    }

    override init(frame: CGRect) {
        super.init(frame:frame)
        setupUI()
    }

    func setupUI() {

        // Set up static properties.
        mainLabel = UILabel()
        mainLabel.backgroundColor = UIColor.clearColor()
        mainLabel.textAlignment = .Left
        secLabel = UILabel()
        secLabel.backgroundColor = UIColor.clearColor()
        secLabel.textAlignment = .Left

        // Add labels to view.
        addSubview(mainLabel)
        addSubview(secLabel)

        // Update variable properties.
        updateUI()
    }

    func updateUI() {

        // Set borders and background.
        self.layer.borderColor = borderColor.CGColor
        self.layer.borderWidth = borderWidth
        self.layer.cornerRadius = cornerRadius
        self.layer.backgroundColor = backgrColor.CGColor

        // Update main label.
        mainLabel.textColor = mainTextColor
        mainLabel.font = UIFont(name: fontName, size: mainTextSize)
        mainLabel.text = mainText

        // Update secondary label.
        secLabel.textColor = secTextColor
        secLabel.font = UIFont(name: fontName, size: secTextSize)
        secLabel.text = secText

        // Calculate view's height.
        var mainLabelCopy: UILabel = mainLabel
        mainLabelCopy.sizeToFit()
        var mainLabelHeight: CGFloat = mainLabelCopy.frame.size.height
        var secLabelCopy: UILabel = secLabel
        secLabelCopy.sizeToFit()
        var secLabelHeight: CGFloat = secLabelCopy.frame.size.height
        textButtonHeight =
            2 * verticalMargin
            + 2 * borderWidth
            + lineSpacing
            + mainLabelHeight
            + secLabelHeight

        setNeedsUpdateConstraints()
    }

    override func updateConstraints() {

        // Set constraints for labels.
        setTranslatesAutoresizingMaskIntoConstraints(false)
        mainLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
        secLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
        removeConstraints(constraints())
        self.addConstraint(NSLayoutConstraint(item: mainLabel, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: horizMargin))
        self.addConstraint(NSLayoutConstraint(item: mainLabel, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0 - horizMargin))
        self.addConstraint(NSLayoutConstraint(item: mainLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: verticalMargin))
        self.addConstraint(NSLayoutConstraint(item: secLabel, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: horizMargin + secHorizOffset))
        self.addConstraint(NSLayoutConstraint(item: secLabel, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0 - horizMargin))
        self.addConstraint(NSLayoutConstraint(item: secLabel, attribute: .Top, relatedBy: .Equal, toItem: mainLabel, attribute: .Bottom, multiplier: 1, constant: lineSpacing))
        self.addConstraint(NSLayoutConstraint(item: self, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: textButtonHeight))

        super.updateConstraints()
    }
}

在运行时,这现在按预期工作。

在 Interface Builder 中,它仍然会抛出一些关于放错位置的视图的警告,这些视图实际上没有任何意义。报告的实际坐标似乎不正确,IB 中的手动或自动更正均未修复。

尽管如此,这让我可以继续,所以我 post 它作为建议的答案。

我遇到了与此类似的问题,即以编程方式创建约束并且子视图根本不显示。我解决这个问题的方法是将高度和宽度设置为约束。我很确定这会解决您遇到的第一个问题。