I am having difficulty with something that I feel like should be working, I am wondering if there are some things that I'm not understanding about initializing values of IBOutlets.
So I've created a class that has three IBOutlets. I then created initializers for those outlet variables. When I create a new instance of the class and pass it hard-coded values I get nil in return.
So I've println()'ed the incoming settingLabel value to make sure incoming parameter indeed carries a value, it comes back as "hello" as to be expected. Then I attempt to assign the value to the settingsLabel.text, however when I call println(settingsLabel.text) comes back as nil.
(Thread 1: EXV_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0))
My Question is:
What am I missing here and/or How do I assign an IBOutlet a value with an initializer?
This is my call to the initializer:
let cell = SettingCell(settingLabel: "hello", settingSwitch: true, timeSetting: 3)
This is my SettingCell.swift class:
class SettingCell: UITableViewCell {
#IBOutlet weak var settingsLabel: UILabel!
#IBOutlet weak var settingsSwitch: UISwitch!
#IBOutlet weak var timeSetting: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
init(settingLabel: String, settingSwitch: Bool, timeSetting: NSNumber) {
println(settingLabel) // returns hello <--------------------
super.init(style: .Default, reuseIdentifier: "SwitchSettingCell")
settingsLabel.text = settingLabel
self.settingsSwitch.on = settingSwitch
self.timeSetting.text = timeSetting.description as String
println(settingsLabel.text) // returns nil <--------------------
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var cellDelegate: SettingCellDelegate?
#IBAction func handledSwitchChange(sender: UISwitch) {
self.cellDelegate?.didChangeSwitchState(sender: self, isOn:settingsSwitch!.on)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
myStoryboard:
The IBOutlet keyword is just a way to tell Xcode that the view should be visible inside your storyboard. You can connect these outlets in your storyboard, and in doing so cause the variables to be instantiated & added as subviews from the storyboard instead of in code.
In your code you are creating your cells programmatically, not from a storyboard (indeed if you tried to create them from a storyboard your app would crash, since you haven't implemented init(coder:)). Because of this, your variables have no reason to be "outlets" and are not ever initialised. You must initialise them yourself, add them as subviews & setup constraints, all in code.
In your initialiser, your code is ineffectual because your variables are still nil. In fact, in your edit, you removed the optional chaining ?s which will have the effect of crashing your app, since the variables are force unwrapped, but are nil. This is the same reason println(settingsLabel.text) is crashing.
Edit:
Ok, so your issue is that you want to initialise your cells from the storyboard, but you're instead doing it programmatically by calling your custom initialiser. In tableView(_:cellForRowAtIndexPath:), you get your cells by dequeuing one from the reuse queue:
let cell = tableView.dequeueReusableCellWithIdentifier("SettingsCell", forIndexPath: indexPath) as! SettingCell
(This assumes you have given your cell prototype the identifier "SettingsCell" in the storyboard)
Whenever a view is created from a storyboard, the view's init(coder:) initialiser is used, so you must provide an implementation of this in your SettingCell subclass. Once the cell has been dequeued, the cell's outlets will be initialised and added as subviews of the cell.
So after dequeuing a cell, you can setup your labels and switch:
cell.settingsLabel.text = //...
cell.settingsSwitch.on = //...
cell.timeSetting.text = //...
Related
I'm having an issue calling a segue from a TableView header that is associated with a programatically created TableView. As a stop-gap I have saved an object reference to the main UITableViewController using a Singleton but this is not the ideal solution.
My TableViewController has multiple sections with either 1 or 2 rows within each section depending on whether the top-level section row is selected. The second row is effectively used to expand on the content that is displayed on the selected row.
The second row contains a custom cell that contains a slider menu, and depending on the menu item selected, 1 of 5 subviews are displayed in the container view.
One of these subviews contains a programatically generated TableView with its own custom header and a custom cell. This cell contains a header and an embedded button. When the button is pressed in the header, I want to segue to another navigation controller, however, my problem is I cannot correctly initialise a delegate to access the main TableViewController.
It gets a bit more complex, as the custom cell creates its own TableView and handles its own functions for operations that are normally performed on the main controller using overrides e.g. didSelectRowAt, numberOfRowsInSelection, headerForRowAt, etc.
class pendingNotificationCustomCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
var dataSample:[String] = ["Row 1,"Row 2", "Row 3"]
var PendingTableView: UITableView?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
print("##### awakeFromNib")
super.awakeFromNib()
// Initialization code
setUpTable()
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style , reuseIdentifier: reuseIdentifier)
setUpTable()
}
func setUpTable() {
PendingTableView = UITableView(frame: CGRect.init(), style:UITableView.Style.plain)
PendingTableView?.delegate = self
PendingTableView?.dataSource = self
PendingTableView?.register(MyCell.self, forCellReuseIdentifier: "pendingnotificationsCellID")
PendingTableView?.register(PendingNotificationsHeader.self, forHeaderFooterViewReuseIdentifier: "pendingHeaderID")
PendingTableView?.sectionHeaderHeight = 40
PendingTableView?.allowsSelection = true
PendingTableView?.allowsMultipleSelection = false
self.addSubview(PendingTableView!)
}
I can easily setup protocols and associated delegates to access functions on the controller that manages the main TableView custom cells. However, as the problematic custom cell does not inherit functions relating to performing segues I need to access the function on the main controller.
I've experimented quite a bit with this and haven't been able to come up with a viable solution other than the Singleton hack.
let pendingCell = pendingNotificationCustomCell()
pendingCell.delegate4 = mStats.mainController
When I try assigning delegate4 with an initialised outlet that references the main TableViewController it always has a value of 'nil' when it gets there. Even when I assign it with the Singleton value in the class the generates the second
TableView. The commented out line fails whereas calling the method using the mStats Singleton works fine.
//delegate4.callSegueFromCell2(myData: myData)
mStats.mainController?.callSegueFromCell2(myData: myData)
The delegate4 above, which is commented out, is set in the cell header classes as follows:
protocol MyCustomCellDelegator3 {
func callSegueFromCell2(myData dataobject: AnyObject)
}
class PendingNotificationsHeader: UITableViewHeaderFooterView {
var delegate4:MyCustomCellDelegator3!
var MainTableViewController: MainTableViewController?
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Pending"
label.font = UIFont.systemFont(ofSize: 17)
label.textColor = .white
return label
}()
let priceAlertButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add new", for: .normal)
button.titleLabel?.font = UIFont (name: "Helvetica", size: 15)
button.setTitleColor(.white, for: .normal)
button.addTarget(self,action: #selector(createNewPriceAlertButtonPressed), for: .touchUpInside)
return button
}()
#IBAction func createNewPriceAlertButtonPressed(_ sender: Any) {
print ("##### New Price Alert Label Pressed")
// Get the view
var mydata = self
//delegate4.callSegueFromCell2(myData: myData)
mStats.mainController?.callSegueFromCell2(myData: myData)
}
Appreciate any guidance.
let pendingCell = pendingNotificationCustomCell()
looks odd.
You should set up your cells in tableView(cellForRow:at:) UITableViewDataSource method.
The issue related to setting the delegate on the cell and not passing it on to the section header. I ended up solving the issue as follows:
I implemented a function and delegate in the MainViewController called API_Segue_01
final class MainTableViewController: UITableViewController, API_Segue_01 {
func API_Segue_01(myData dataobject: AnyObject) {
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
self.performSegue(withIdentifier: "showCreateNotificationsViewController", sender:dataobject )
}
/// Rest of class code
/// ...
In the custom cell I created a header prototype to reference the external function:
protocol API_Segue_01 {
func API_Segue_01(myData dataobject: AnyObject)
}
I defined a var to hold the reference to the MainTableViewController in the custom cell and associated header
class pendingNotificationCustomCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
var funcCallTo: API_Segue_01!
/// Rest of class code
/// ...
class PendingNotificationsHeader: UITableViewHeaderFooterView {
var funcCallTo:API_Segue_01!
/// Rest of class code
/// ...
When creating an instance of the custom cell I provided a reference to the MainTableViewController
pendingCell.funcCallTo = mainTableViewController
As the action button is located in the cell header I passed the reference to the mainTableViewController within the viewForHeaderInSection as follows
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
print("##### viewForHeaderInSection")
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "pendingHeaderID") as! PendingNotificationsHeader
header.funcCallTo = self.funcCallTo
return header
}
I was then able to use this reference to call the API_Segue_01 function
#IBAction func createNewPriceAlertButtonPressed(_ sender: Any) {
print ("##### New Price Alert Label Pressed")
// Get the view
var mydata = self /
funcCallTo.API_Segue_01(myData: mydata)
}
I have a view controller which holds a tableview. Inside that tableview I have a dynamic cell. Inside of that cell I have a UIView. Inside the UIView I have a label with a tap recognizer which is supposed to perform a segue to a different view controller. I have tried making a delegate between the view controller by putting vc.delegate = self in the viewdidload, but it did not work. I have also tried putting the same line inside cellforrowat, but it still doesn't do anything. How can I make a delegate that communicates from my cell with UIView to this view controller? Also, the thing that will be communicating is my UIView class and not my cell class.
If I understand it correctly. Create a delegate protocol for your cell view, put delegate property to the cell and pass it via the tableView method tableView(_:cellForRowAt:) to the controller.
Protocol implementation:
protocol MyCellViewDelegate: class {
func viewDidTap(_ view: MyCellView)
}
Cell View Implementaiton:
class MyCellView: UIView {
private let tapGesture = UITapGestureRecognizer()
weak var delegate: MyCellViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
tapGesture.addTarget(self, action: #selector (viewDidTap))
}
#objc
func viewDidTap() {
delegate?.viewDidTap(self)
}
}
Cell Implementation:
Then, in your Cell implementation. Pass the delegate reference to the MyCellView, which will be handled in the Controller.
class MyCell: UITableViewCell {
private let myContentView = MyCellView()
weak var delegate: MyCellViewDelegate? {
didSet { myContentView.delegate = delegate }
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
}
Then you should be able to set delegate in the TableView DataSource delegate methods.
Controller Implementation:
extension MyController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue the cell...
cell.delegate = self
return cell
}
}
And it will force you to add delegates.
extension MyController: MyCellViewDelegate {
func viewDidTap(_ view: MyCellView) {
// Do some stuff
}
}
However, TableView could "steal" your Tap Gesture action. Maybe, if you set tableView.isUserInteractionEnabled = false, it could work.
The easy way is NotificationCenter. Just post the notification and receive at ViewController.
Though, you achieve this as well by using the delegate where you need to pass the information as below. This is very high level where I am assuming you have below view as a separate class.
UIView -> [Cell -> TableView] -> UIViewController
You can use the completion handler as well.
I've created a tableview with five cells in Xcode 8. Each cell has its own label and textfield. I want to be able to access the user input from these textfields in a separate tableViewController file/class and then do stuff with the user input.
I've created a swift file/class for each cell.
I've made each of the classes public.
And, when creating the IBOutlet for each of the textfields, I've made those conform to the following format:
public class NameCell: UITableviewCell {
#IBOutlet public weak var NameTextField!
override public func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override public func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
What I think I need to do is declare a variable in the viewcontroller class for the textfields in each class. I was wondering if this can be done? And if so, how do I declare the variables/textfields from another class? Or is there a better way?
I was thinking something along the lines of:
var NameTextField.NameCell = ""
Or
var NameCell.NameTextField = ""
But these are obviously wrong. I've also put the textfield delegate in the TableViewController class rather than the NameCell class. I don't know if this is contributing to the issue or not.
Any help would be much appreciated!
Make sure your IBOutlet variable is declared correctly. It should be:
#IBOutlet weak var textField: NameTextField!
Now, to access the text from the cell's textField, you first need to access the cell itself. You can call var cell = tableView.cellForRow(at: indexPath) as! NameCell where indexPath is that of the cell you are attempting to access. Then, you can call cell.textField.text. This will give you the text that the user has entered into the textField of the given cell.
Also, make sure to read up on swift basics to understand the proper usage of variables and type annotations, as well as proper naming conventions.
I'm new to Swift, and am trying to connect a custom UITableViewHeaderFooterView with a nib to my viewcontroller. When I do, I get this common error, which I've dealt with before and which is summarized in this post.
However this time, I'm a bit confused as to what optional is being unwrapped and hence how to fix the problem.
As far as I can tell everything is hooked up correctly-- the UITableView I'm using (menuTableView) is connect to the IBOutlet, as are all the parts of the custom header in the nib. The identifier and nib name are correct as well. Any idea what's going on? Here's the code where the error is originating:
menuTableView.register(UINib(nibName: "MenuTableHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: "MenuTableHeader")
And here's my MenuTableHeader.swift file, minus a function or two.
class MenuTableHeader: UITableViewHeaderFooterView {
#IBOutlet var menuTableHeader: UIView!
#IBOutlet var menuHeaderLabel: UILabel?
#IBOutlet var menuHeaderImage: UIImageView?
var delegate: MenuTableHeaderDelegate?
var section: Int = 0
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
// Call tapHeader when tapping on this header
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(MenuTableHeader.tapHeader(_:))))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Thanks!
It seems to me that the nib name and the identifier should not be the same, even if there is only one element in the nib. For example, in my project, I used the following line:
tableView.register(UINib(nibName: "PostCellTextAndPic", bundle: nil), forCellReuseIdentifier: "PostCellTextAndPicId")
Rename the file and the identifier accordingly.
I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!