在Swift中使用XIB自定义UIView子类

Foz*_*zle 9 interface-builder xib uiview ios swift

我正在使用Swift和Xcode 6.4来获得它的价值.

所以我有一个视图控制器,它将包含一些UILabels和UIImageViews.我想将UILabel-UIImageView对放入自定义UIView中,因此我可以在上述视图控制器中重复使用相同的结构.(我知道这可以翻译成UITableView,但为了〜学习〜请耐心等待我).事实证明,这是一个比我想象的更复杂的过程,我无法找到使这一切在IB工作的"正确"方法.

目前我一直在使用UIView子类和相应的XIB,重写init(frame :)和init(编码器),从nib加载视图并将其添加为子视图.这是我到目前为止在互联网上看到/阅读的内容.(这大概是这样的:http://iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6) .

这给了我在init(编码器)和从bundle加载nib之间引起无限循环的问题.奇怪的是,这些文章或之前关于堆栈溢出的答案都没有提到这一点!

好的,我在init(编码器)中检查是否已经添加了子视图.这似乎是"解决了".但是当我尝试为它们分配值时,我开始遇到一个问题,我的自定义视图出口是零.

我做了一个didSet并添加了一个断点来看看...它们肯定是在某一点设置的,但是当我尝试修改标签的textColor时,该标签是零.我有点在这里撕扯我的头发.

可重用的组件看起来像软件设计101,但我觉得Apple正在密谋反对我.我应该在这里使用集装箱风险投资吗?我应该只是在我的主要VC中嵌套视图并拥有一个愚蠢的大量插座吗?为什么这么复杂?为什么每个人的例子都不适合我?

期望的结果(假装整个事情是VC,框是我想要的自定义uiviews):

在此输入图像描述

谢谢阅读.

以下是我的自定义UIView子类.在我的主要故事板中,我将UIViews与子类设置为他们的类.

class StageCardView: UIView {

@IBOutlet weak private var stageLabel: UILabel! {
    didSet {
        NSLog("I will murder you %@", stageLabel)
    }
}
@IBOutlet weak private var stageImage: UIImageView!

var stageName : String? {
    didSet {
        self.stageLabel.text = stageName
    }
}
var imageName : String? {
    didSet {
        self.stageImage.image = UIImage(named: imageName!)
    }
}
var textColor : UIColor? {
    didSet {
        self.stageLabel.textColor = textColor
    }
}
var splatColor : UIColor? {
    didSet {
        let splatImage = UIImage(named: "backsplat")?.tintedImageWithColor(splatColor!)
        self.backgroundColor = UIColor(patternImage: splatImage!)
    }
}

// MARK: init
required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    if self.subviews.count == 0 {
        setup()
    }
}

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

func setup() {
    if let view = NSBundle.mainBundle().loadNibNamed("StageCardView", owner: self, options: nil).first as? StageCardView {
        view.frame = bounds
        view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight

        addSubview(view)
    }
}




/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
    // Drawing code
}
*/

}
Run Code Online (Sandbox Code Playgroud)

编辑:这是我迄今为止得到的......

厦门国际银行:

在此输入图像描述

结果:

在此输入图像描述

问题:尝试访问标签或图像插座时,它们是零.在检查所述访问的断点时,标签和图像子视图在那里,视图层次结构与预期一致.

我可以在代码中完成这一切,如果这就是它需要的东西,但我并不是很擅长在代码中进行Autolayout,所以如果有办法避免它,我宁愿不要!

编辑/问题转移:

我想出了如何让出口停止为零.

在此输入图像描述

这个SO答案的灵感:加载的笔尖但视图插座没有设置 - InterfaceBuilder的新功能,除了分配视图插座我分配了各个组件插座.

现在这就是我只是在墙上乱扔东西,看看它是否坚持.有谁知道我为什么要这样做?这是什么样的黑暗魔法?

alg*_*gal 12

关于视图重用的一般建议

你是对的,可重用和可组合的元素是软件101.Interface Builder不是很擅长.

具体来说,xib和storyboard是通过重用代码中定义的视图来定义视图的好方法.但是它们不适合定义您自己希望在xib和storyboard中重用的视图.(可以这样做,但这是一项高级练习.)

所以,这是一个经验法则.如果要定义要从代码中重用的视图,请根据需要定义它.但是,如果要定义一个您希望能够在故事板中重用的视图,那么请在代码中定义该视图.

因此,在您的情况下,如果您尝试定义要从故事板重用的自定义视图,我会在代码中执行此操作.如果您已经通过xib定义视图,那么我将在代码中定义一个视图,并在其初始化程序中初始化您的xib定义视图并将其配置为子视图.

在这种情况下的建议

以下是您在代码中定义视图的大致方式:

class StageCardView: UIView {
  var stageLabel = UILabel(frame:CGRectZero)
  var stageImage = UIImageView(frame:CGRectZero)
  override init(frame:CGRect) {
   super.init(frame:frame)
   setup()
  }
  required init(coder aDecoder:NSCoder) { 
   super.init(coder:aDecoder)  
   setup()
  }
  private func setup() {
    stageImage.image = UIImage(named:"backsplat")
    self.addSubview(stageLabel)
    self.addSubview(stageImage)
    // configure the initial layout of your subviews here.
  }
}
Run Code Online (Sandbox Code Playgroud)

您现在可以在代码中或通过故事板实例化它,尽管您不会按原样在Interface Builder中进行实时预览.

或者,通过在代码定义的视图中嵌入xib定义的视图,大致如何定义基于xib的可重用视图:

class StageCardView: UIView {
  var embeddedView:EmbeddedView!
  override init(frame:CGRect) {
   super.init(frame:frame)
   setup()
  }
  required init(coder aDecoder:NSCoder) { 
   super.init(coder:aDecoder)  
   setup()
  }
  private func setup() {
    self.embeddedView = NSBundle.mainBundle().loadNibNamed("EmbeddedView",owner:self,options:nil).lastObject as! UIView
    self.addSubview(self.embeddedView)
    self.embeddedView.frame = self.bounds
    self.embeddedView.autoresizingMask = .FlexibleHeight | .FlexibleWidth
  }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以使用故事板或代码中的代码定义视图,它将加载其nib定义的子视图(并且在IB中仍然没有实时预览).

  • 谢谢你的建议.我对使用XIB文件来布局自定义视图感到有些顽固,因为它在过去的Obj-C中对我有好处,所以在我放弃之前,纯粹通过代码完成它,也许你有关于这种行为的一些想法:我能够将我的XIB设计的自定义子视图显示在视图控制器中,但是出口是零,所以我无法以编程方式修改它.我注意到子视图存在,似乎出口参考不存在.任何线索为什么会这样? (2认同)

Kam*_*ski 3

我能够解决这个问题,但解决方案有点棘手。收益是否值得付出努力还有待争论,但这是我纯粹在界面生成器中实现它的方式

首先我定义了一个UIView名为的自定义子类P2View

@IBDesignable class P2View: UIView
{
    @IBOutlet private weak var titleLabel: UILabel!
    @IBOutlet private weak var iconView: UIImageView!

    @IBInspectable var title: String? {
        didSet {
            if titleLabel != nil {
                titleLabel.text = title
            }
        }
    }

    @IBInspectable var image: UIImage? {
        didSet {
            if iconView != nil {
                iconView.image = image
            }
        }
    }

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

    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
    }

    override func awakeFromNib()
    {
        super.awakeFromNib()

        let bundle = Bundle(for: type(of: self))

        guard let view = bundle.loadNibNamed("P2View", owner: self, options: nil)?.first as? UIView else {
            return
        }

        view.translatesAutoresizingMaskIntoConstraints = false
        addSubview(view)

        let bindings = ["view": view]

        let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
        let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"H:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)

        addConstraints(verticalConstraints)
        addConstraints(horizontalConstraints)        
    }

    titleLabel.text = title
    iconView.image = image
}
Run Code Online (Sandbox Code Playgroud)

这就是界面生成器中的样子

Xib定义

这就是我将此自定义视图嵌入到情节提要上定义的示例视图控制器中的方式。的属性P2View在属性检查器中设置。

嵌入视图控制器中的自定义视图

有3点值得一提

第一的:

Bundle(for: type(of: self))装载笔尖时使用。这是因为界面构建器在单独的进程中渲染可设计项,该进程的主包与您的主包不同。

第二:

    @IBInspectable var title: String? {
        didSet {
            if titleLabel != nil {
                titleLabel.text = title
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

IBInspectables结合时IBOutlets你必须记住函数didSet是在方法之前调用的awakeFromNib。因此,插座尚未初始化,您的应用程序此时可能会崩溃。不幸的是,您不能省略该didSet函数,因为界面生成器不会呈现您的自定义视图,因此我们必须将其保留在此处。

第三:

    titleLabel.text = title
    iconView.image = image
Run Code Online (Sandbox Code Playgroud)

我们必须以某种方式初始化我们的控件。当函数被调用时我们无法做到这一点didSet,因此我们必须使用存储在IBInspectable属性中的值并在方法结束时初始化它们awakeFromNib。

这是您在 Xib 上实现自定义视图、将其嵌入故事板、在故事板上配置、渲染并拥有不崩溃的应用程序的方法。它需要破解,但这是可能的。