[英]Custom UIView subclass with XIB in Swift
我正在使用Swift和Xcode 6.4来实现其价值。
因此,我有一个视图控制器,其中将包含一些多对UILabel和UIImageViews。 我想将UILabel-UIImageView对放入自定义UIView中,因此我可以简单地在上述视图控制器中重复使用相同的结构。 (我知道这可以转换为UITableView,但是为了〜学习〜请耐心等待)。 事实证明,这比我想象的要复杂得多,我在弄清楚使这一切在IB中正常工作的“正确”方式时遇到了麻烦。
目前,我一直在苦苦研究UIView子类和相应的XIB,覆盖init(frame :)和init(coder),从笔尖加载视图并将其添加为子视图。 到目前为止,这是我在互联网上看到/阅读的内容。 (大约是这样: http : //iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6 ) 。
这给了我一个问题,该问题导致init(coder)和从包中加载笔尖之间出现无限循环。 奇怪的是,这些文章或堆栈溢出的先前答案都没有提到这一点!
好的,所以我检查init(coder)以查看是否已经添加了子视图。 那个似乎解决了。 但是,当我尝试为它们分配值时,我开始遇到自定义视图出口为零的问题。
我做了一个didSet并添加了一个断点来查看...它们肯定被设置在一点上,但是当我尝试修改标签的textColor时,该标签为nil。 我有点在这里扯头发。
可重用组件看起来像软件设计101,但我感觉苹果正在密谋反对我。 我应该在这里使用容器VC吗? 我是否应该只是嵌套视图并在我的主要VC中拥有数量惊人的网点? 为什么这么复杂? 为什么每个人的示例都不适合我?
所需的结果(假装整个东西是VC,方框是我想要的自定义uiview):
谢谢阅读。
以下是我的自定义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
}
*/
}
编辑:这是到目前为止我能得到的...
XIB:
结果:
问题:尝试访问标签或图像出口时,它们为零。 当在所述访问的断点处检查时,标签子视图和图像子视图在那里,并且视图层次结构符合预期。
如果可以的话,我可以在代码中进行所有操作,但是我对在代码中进行自动布局的工作并不在意,所以我宁愿不要这样做,如果可以避免的话!
编辑/问题转移:
我想出了如何使网点停止为零的方法。
这样的答案的启示: 装入了笔尖,但未设置视图出口-InterfaceBuilder的新增功能,除了分配视图出口外,我分配了各个组件出口。
现在是在这里,我只是往墙上扔屎,看它是否会粘住。 有人知道我为什么要这样做吗? 这是什么黑魔法?
没错,软件101是可重用和可组合的元素。InterfaceBuilder并不是很好。
具体地说,XIB和情节提要是通过重用代码中定义的视图来定义视图的好方法。 但是它们对于定义您自己希望在xib和情节提要中重用的视图不是很好。 (可以完成,但这是高级练习。)
因此,这是一个经验法则。 如果要定义要从代码中重复使用的视图,则可以根据需要定义它。 但是,如果您要定义一个希望能够从情节提要中重复使用的视图,请在代码中定义该视图。
因此,在您的情况下,如果您想定义一个要从情节提要中重复使用的自定义视图,则可以在代码中完成。 如果您不愿意通过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.
}
}
现在,您可以在代码中或通过情节提要实例化此实例,尽管您将无法在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
}
}
现在,您可以从情节提要或代码中使用代码定义的视图,它将加载其笔尖定义的子视图(并且IB中仍然没有实时预览)。
我可以解决它,但是解决方案有点棘手。 是否值得付出努力尚有争议,但这是我纯粹在接口生成器中实现的方法
首先,我定义了一个名为P2View
的自定义UIView
子类。
@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
}
这是界面生成器中的外观
这就是我将此自定义视图嵌入到故事板上定义的示例视图控制器中的方式。 在属性检查P2View
中设置P2View
的属性。
有3点值得一提
第一:
加载笔尖时使用Bundle(for: type(of: self))
。 这是因为界面生成器在单独的过程中渲染可设计项,而主捆绑包与您的主捆绑包不同。
第二:
@IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}
将IBInspectables
与IBOutlets
结合使用时,必须记住在awakeFromNib
方法之前调用didSet
函数。 因此,出口未初始化,此时您的应用可能会崩溃。 不幸的是,您不能省略didSet
函数,因为接口构建器不会呈现您的自定义视图,因此,如果在这里,我们必须将其保留为脏状态。
第三:
titleLabel.text = title
iconView.image = image
我们必须以某种方式初始化控件。 调用didSet
函数时,我们无法执行此操作,因此我们必须使用IBInspectable
属性中存储的值,并在awakeFromNib
方法的末尾对其进行初始化。
这是您可以在Xib上实现自定义视图,将其嵌入到情节提要中,在情节提要上进行配置,渲染并具有不崩溃的应用程序的方式。 它需要破解,但是有可能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.