[英]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.