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

标签 ios swift uiview interface-builder xib

我使用 Swift 和 Xcode 6.4 是为了它的值(value)。

所以我有一个 View Controller ,它将包含一些多对 UILabel 和 UIImageView。我想将 UILabel-UIImageView 对放入自定义 UIView 中,这样我就可以简单地在上述 View Controller 中重复使用相同的结构。 (我知道这可以转化为 UITableView,但为了~学习~请多多包涵)。事实证明这是一个比我想象的更复杂的过程,我很难找到使这一切在 IB 中正常工作的“正确”方法。

目前我一直在与 UIView 子类和相应的 XIB 纠缠不清,覆盖 init(frame:) 和 init(coder),从 nib 加载 View 并将其添加为 subview 。到目前为止,这是我在互联网上看到/读到的内容。 (大约是:http://iphonedev.tv/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6)。

这给我带来了在 init(coder) 和从包中加载 nib 之间导致无限循环的问题。奇怪的是,这些文章或以前关于堆栈溢出的答案都没有提到这一点!

好的,所以我检查了 init(coder) 以查看是否已经添加了 subview 。这似乎“解决”了那个问题。但是,当我尝试为它们分配值时,我开始遇到自定义 View socket 为零的问题。

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

可重用组件看起来像是软件设计 101,但我觉得 Apple 在密谋反对我。我应该在这里使用容器 VC 吗?我是否应该只是嵌套 View 并在我的主要 VC 中拥有大量愚蠢的导出?为什么这么复杂?为什么每个人的示例都不适合我?

期望的结果(假装整个都是 VC,方框是我想要的自定义 uiview):

enter image description here

感谢阅读。

以下是我自定义的 UIView 子类。在我的主 Storyboard 中,我将子类设置为类的 UIView。

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:

enter image description here

结果:

enter image description here

问题:当尝试访问标签或图像导出时,它们为零。在上述访问的断点处检查时,标签和图像 subview 在那里, View 层次结构符合预期。

如果需要的话,我可以在代码中完成这一切,但我不太喜欢在代码中进行自动布局,所以如果有办法避免它,我宁愿不这样做!

编辑/问题转移:

我想出了如何让导出不再为零。

enter image description here

来自这个 SO 答案的灵感:Loaded nib but the view outlet was not set - new to InterfaceBuilder除了我没有分配 View socket ,而是分配了各个组件 socket 。

现在我正向墙壁扔屎,看看它是否能粘住。有谁知道我为什么必须这样做?这是什么黑魔法?

最佳答案

关于 View 重用的一般建议

你说得对,可重用和可组合元素是软件 101。Interface Builder 不太擅长这方面。

具体来说,xibs 和 Storyboard是通过重用在代码中定义的 View 来定义 View 的好方法。但是它们对于定义您自己希望在 xib 和 Storyboard中重复使用的 View 不是很好。 (这是可以做到的,但这是一项高级练习。)

所以,这是一个经验法则。如果您正在定义要从代码中重用的 View ,请按照您的意愿进行定义。但是,如果您要定义一个 View ,希望能够在 Storyboard 中重复使用,那么请在代码中定义该 View 。

所以在你的情况下,如果你试图定义一个你想从 Storyboard中重复使用的自定义 View ,我会用代码来完成。如果您对通过 xib 定义 View 一无所知,那么我会在代码中定义一个 View ,并在其初始化程序中让它初始化您的 xib 定义的 View 并将其配置为 subview 。

在这种情况下的建议

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

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.
  }
}

您现在可以在代码中和/或通过 Storyboard 对其进行实例化,但您无法在 Interface Builder 中按原样获得实时预览。

或者,通过将 xib 定义的 View 嵌入到代码定义的 View 中,您可以大致定义基于 xib 的可重用 View :

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
  }
}

现在您可以使用 Storyboard或代码中的代码定义 View ,它将加载其 nib 定义的 subview (IB 中仍然没有实时预览)。

关于ios - 在 Swift 中使用 XIB 自定义 UIView 子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32342145/

相关文章:

ios - 如何通过UICollectionView访问UICollectionViewCell?

ios - ViewController 委托(delegate)方法未从 AppDelegate 触发

ios - 如何制作基于百分比的水平布局?

swift - 如何以编程方式将自定义 uiview 添加到 View Controller SWIFT

iOS - 如何将一个 View 的 subview 拖到新 View 中(即拖放)?

ios - 如何使用上一个和下一个按钮(如一组 10 个问题的幻灯片放映)来导航页面?

iphone - 如何在 iOS 中不显示预览的情况下捕获图像

ios - 自动收缩内容拥抱

ios - View Controller 仅在第一次正确加载图像

ios - 复杂的交叉布局架构和关注点分离