I am attempting to load a view from a .xib file as per this question: Assign xib to the UIView in Swift
My setup is as follows:
- StripView.xib — the .xib file from which I am loading the view, contains a single UIView
- StripView.swift — the 'owner' of the UIView in StripView.xib, contains references/IBOutlets for each component in StripView.xib
- StripViewController.swift — The view controller, which loads an instance of StripView and handles most of the logic/data source/delegate stuff.
This all worked fine in Xcode 6 Beta 4, however, upgrading to Xcode 6 Beta 5 (the latest) breaks. On app launch, I simply get a blank, black screen. All other parts of the app seem to work fine. This bug only occurs when I attempt to set the delegate or data source of a UIPickerView (owned by StripView) to the StripViewController.
Code snippet:
class StripViewController: SWRevealViewController, RSColorPickerViewDelegate, UIPickerViewDataSource, UIPickerViewDelegate, BLEDelegate, UIActionSheetDelegate, UITextFieldDelegate {
var stripView: StripView!
var colorPicker: RSColorPickerView!
var connectBtn:UIButton!
var connectionSpinner:UIActivityIndicatorView!
var tapRecognizer:UITapGestureRecognizer!
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// use UIView extension as per https://stackoverflow.com/questions/24370061/assign-xib-to-the-uiview-in-swift
stripView = UIView.loadFromNibNamed("StripView") as StripView
self.view.addSubview(stripView)
// nav bar setup //
self.title = "Strip \(self.tabBarController.selectedIndex+1)"
let leftNav:UIView = UIView(frame: CGRectMake(0,0,100,40))
self.connectBtn = UIButton(frame: CGRectMake(0,0,95,40))
connectBtn.frame = CGRectMake(0,0,connectBtn.bounds.size.width,connectBtn.bounds.size.height)
connectBtn.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
connectBtn.setTitle("Connect", forState: UIControlState.Normal)
connectBtn.setTitleColor(UIColor(red: 0.0, green: 122.0/255.0, blue: 1.0, alpha: 1.0), forState: UIControlState.Normal)
connectBtn.addTarget(self, action: "connectButtonPressed:", forControlEvents: UIControlEvents.TouchDown)
leftNav.addSubview(connectBtn)
self.connectionSpinner = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
connectionSpinner.frame = CGRectMake(connectBtn.frame.minX + connectBtn.bounds.size.width, connectBtn.frame.minY + 10, connectionSpinner.bounds.size.width, connectionSpinner.bounds.size.height)
connectionSpinner.hidesWhenStopped = true
leftNav.addSubview(connectionSpinner)
let leftNavItem:UIBarButtonItem = UIBarButtonItem(customView: leftNav)
self.navigationItem.leftBarButtonItem = leftNavItem
// set up color picker
colorPicker = RSColorPickerView(frame: CGRectMake(20, 78, 160, 160))
self.view.addSubview(colorPicker)
colorPicker.delegate = self
// set up mode picker
// THESE APPEAR TO BE THE OFFENDING LINES —
// COMMENTING THEM OUT DISPLAYS THE VIEW AS EXPECTED
self.stripView.modePicker.dataSource = self;
self.stripView.modePicker.delegate = self;
// set up notifications
NSNotificationCenter.defaultCenter().addObserver(self, selector: "colorFieldDidChange:", name: "ColorFieldDidChangeNotification", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
// set up UI and populate fields
self.stripView.setAnimSpeed()
// set up BLE
BLEManager.instance.ble.delegate = self
}
[...]
Once more, the offending lines appear to be:
self.stripView.modePicker.dataSource = self;
self.stripView.modePicker.delegate = self;
If I comment them out, all else displays as normal. I even attempted moving the delegate/data source into StripView.swift (not StripViewController), but I see the same result.
Here's the part of StripViewController that implements UIPickerViewDataSource and UIPickerViewDelegate methods:
func numberOfComponentsInPickerView(pickerView: UIPickerView!) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView!, numberOfRowsInComponent component: Int) -> Int {
return DataModel.instance.ModeNames.count
}
func pickerView(pickerView: UIPickerView!, titleForRow row: Int, forComponent component: Int) -> String! {
return DataModel.instance.ModeNames[row]
}
func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int) {
// send mode to arduino
println("--- sending mode select command ---")
var buf:[UInt8] = [0x00, 0x00, 0x00, 0x00]
buf[0] = Modes.instance.SET_MODE
buf[1] = UInt8(DataModel.instance.ModeOpcodes[DataModel.instance.ModeNames[row]]!)
let data:NSData = NSData(bytes: buf, length: 4)
BLEManager.instance.ble.write(data)
}
Did something change in the way the Swift language handles delegates/data sources, or view loading from a XIB?
EDIT: I've narrowed down the problem to the DataModel singleton I'm using as a datasource. That code (DataModel.swift) looks like so, implementing a basic singleton pattern as per https://github.com/hpique/SwiftSingleton :
class DataModel {
// setup singleton
class var instance: DataModel {
struct Static {
static let instance : DataModel = DataModel()
}
return Static.instance
}
// modes
// eventually may replace this with a more flexible data structure
// for now, we just need names
let ModeNames: Array<String> = ["Light All", "Rainbow"]
let ModeOpcodes: Dictionary<String, UInt8> = [DataModel.instance.ModeNames[0]: Modes.instance.LIGHT_ALL, DataModel.instance.ModeNames[1]: Modes.instance.RAINBOW];
}
Replacing instances of this with a variable defined in StripViewController results in things working fine. Something about it being a singleton seems to be throwing things off. Thoughts? Perhaps the singleton is not being instantiated correctly?