When I use the UITableViewController to create a tableView, you get a lot of override functions, but when you use a regular UIViewController you get an error when using these same override functions and you are forced to change them to regular functions. I believe this is why my core data won't load into my cells, and tried to use the viewDidLoad function to get my data to load.
I know my code should work since all I'm trying to do is transfer all my code from a UITableViewController to a UIViewController, and my code worked in my UITableViewController.
My effort so far:
override func viewDidLoad() {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let CellID:NSString = "CELL"
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
if let ip = indexPath as Optional {
var data:NSManagedObject = myList[ip.row] as NSManagedObject
cell.textLabel!.text = data.valueForKeyPath("username") as? String
}
return cell
}
}
Are the override functions the reason my cells are empty, or are there other aspects when using a regular UIViewController to show a tableView?
Any suggestions would be appreciated.
(1) You have to add UITableViewDelegate to the class in order to access the delegate methods, ex:
class ViewController: UIViewController, UITableViewDelegate {
After adding the UITableViewDelegate to the class, your UITableView delegate functions should auto-complete.
Also, make sure to set the UITableView's delegate to self in order to have the delegate methods populate the table.
(2) Right now, your cellForRowAtIndexPath method is within your viewDidLoad. Move it so it's not contained within any other method.
Related
I have a Social Network Feed in form UItableView which has a cell. Now each cell has an image that animates when an even is triggered. Now, This event is in form of a string, will be triggered at every cell. the options for the event are defined in another class(of type NSObject).
My issue:
I constructed a protocol delegate method in table view, which will be called whenever the event is triggered for each cell. Then, I define this function in UITableViewCell Class, since my the image will be animating on that.
All is working well but I am unable to figure out how to assign the delegate of TableView class to cell class. What I mean is, how can I use UITableView.delegate = self in cellView class. I have tried using a static variable, but it doesn't work.
I have been playing around the protocols for a while now but really unable to figure out a solution to this.
I hope I am clear. If not, I will provide with an example in the comments. I am sorry, This is a confidential project and I cant reveal all details.
If I understand you correctly, you are trying to make each of your cells conform to a protocol that belongs to their UITableView? If this is the case then this cannot be done. The Delegation design pattern is a one to one relationship, i.e only one of your UITableViewCells would be able to conform to the UITableView's delegate.
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.
Quote from the Apple Docs
I would suggest that your UITableViewCell should call a block (Objective-C) or a closure (Swift) whenever your specified event is triggered to achieve what you are looking for. Set up this closure in your tableView:cellForRowAtIndexPath function.
EXAMPLE
TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCellID", for: indexPath) as! MyTableViewCell
cell.eventClosure = {
//Do something once the event has been triggered.
}
return cell
}
TableViewCell
func eventTriggered()
{
//Call the closure now we have a triggered event.
eventClosure()
}
If I correctly understood your question, maybe this could help:
class ViewController: UIViewController, YourCustomTableDelegate {
#IBOutlet weak var tableView: YourCustomTableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.customTableDelegate = self
}
// table delegate method
func shouldAnimateCell(at indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.animate(...)
}
}
}
Try something like this:
Define your delegate protocol:
protocol CustomCellDelegate: class {
func animationStarted()
func animationFinished()
}
Define your CustomCell. Extremely important to define a weak delegate reference, so your classes won't retain each other.
class CustomCell: UITableViewCell {
// Don't unwrap in case the cell is enqueued!
weak var delegate: CustomCellDelegate?
/* Some initialization of the cell */
func performAnimation() {
delegate?.animationStarted()
UIView.animate(withDuration: 0.5, animations: {
/* Do some cool animation */
}) { finished in
self.delegate?.animationFinished()
}
}
}
Define your view controller. assign delegate inside tableView:cellForRowAt.
class ViewController: UITableViewDelegate, UITableViewDataSource {
/* Some view controller customization */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CustomCell.self)) as? CustomCell
cell.delegate = self
cell.performAnimation()
return cell
}
}
I was wondering if there is any problem with extending UIButton in order to have a ref of my Data?
For instance I have a table view and whenever user clicks on a cell I need to provide the user with the data for that specific cell.
Data is kept inside an array and array index was saved in UIButton tag but as my array gets updated, wrong indexes will be provided. So what i was trying to do was to extend a uibutton and then have a variable which holds my model.
This idea works fine but as Im not really experienced in swift I wanted to know what are the drawbacks and problems of doing such a thing.
You don't need to save the index as Button's tag. Subclassing the UIButton as Sneak pointed out in comment is clearly a very bad idea. On top of that saving your model in a UIComponent is disasters design.
You can do it multiple ways. One that I find neat :
Step 1:
Create a Class for your Custom Cell. Lets say MyCollectionViewCell. Because your Cell has a button inside it, you should create IBAction of button inside the Cell.
class MyCollectionViewCell: UICollectionViewCell {
#IBAction func myButtonTapped(_ sender: Any) {
}
}
Step 2:
Lets declare a protocol that we will use to communicate with tableView/CollectionView's dataSource.
protocol MyCollectionViewCellProtocol : NSObjectProtocol {
func buttonTapped(for cell : MyCollectionViewCell)
}
Step 3:
Lets create a property in our MyCollectionViewCell
weak var delegate : MyCollectionViewCellProtocol? = nil
After step 3 your MyCollectionViewCell class should look like
protocol MyCollectionViewCellProtocol : NSObjectProtocol {
func buttonTapped(for cell : MyCollectionViewCell)
}
class MyCollectionViewCell: UICollectionViewCell {
weak var delegate : MyCollectionViewCellProtocol? = nil
#IBAction func myButtonTapped(_ sender: Any) {
self.delegate?.buttonTapped(for: self)
}
}
Step 4:
In your tableView's CellForRowAtIndexPath or CollectionView's sizeForItemAtIndexPath confirm pass ViewController as delegate to cell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : MyCollectionViewCell = //deque your cell
(cell as! MyCollectionViewCell).delegate = self
}
Step 5:
Now make your ViewController confirm to protocol
class YourViewController: UIViewController,MyCollectionViewCellProtocol {
Step 6:
Finally implement method in your VC
func buttonTapped(for cell: MyCollectionViewCell) {
let indexPath = self.collectionView?.indexPath(for: cell)
//access array now
}
P.S:
Sorry though I know you are using TableView, in a hurry I have written code for CollectionView but the delegates are pretty same :) I hope you will be able to understand the logic
It was my understanding that in Swift you don't need to import your classes (only for some frameworks) but intatiation was required, but to my surprise this may not be true, I created a custom class (CustomCell.swift) for a custom cell I'm using in my UITableView and apparently there is no need to make an instance of the custom class before using it. Here is how I'm using it.
CustomCell Class
// CustomCell.swift
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var labelDisplayWattage: UILabel!
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
}
}
View Controller
// ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
// some code ...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
cell.labelDisplayWattage.text = String(totalWattageList[indexPath.row])
return cell
}
}
As you can see there are no instances of the CustomCell class exept for the call on method cellForRowAtIndexPath (as! CustomCell).
Can someone tell me why there was no need to create an instance of class CustomCell before using it in method cellForRowAtIndexPath? I was expecting to see something like...
var myCustomCell = CustomCell()
Thanks
As per apple docs for Datasource method
func dequeueReusableCellWithIdentifier(_ identifier: String,forIndexPath indexPath: NSIndexPath) -> UITableViewCell
For performance reasons, a table view’s data source should generally
reuse UITableViewCell objects when it assigns cells to rows in its
tableView:cellForRowAtIndexPath: method. A table view maintains a
queue or list of UITableViewCell objects that the data source has
marked for reuse. Call this method from your data source object when
asked to provide a new cell for the table view. This method dequeues
an existing cell if one is available, or creates a new one based on
the class or nib file you previously registered, and adds it to the
table.
so basically it creates a new object for you if the queue is empty.
Source: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/dequeueReusableCellWithIdentifier:forIndexPath:
There is a really important concept underneath the code you're seeing right now. If you're using storyboards, table views and collection views have the option of using a prototype cell. A prototype cell is a template of the cell that can/will be used with the table view or collection view. If your table/collection view is set to use a prototype cell, there is no need to create a new cell,UIKit takes care of creating one for you when you ask for a queued cell by calling tableView.dequeueReusableCellWithIdentifier.
Consider the following when NOT using prototype cells:
// ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
// some code ...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as? CustomCell
if nil == cell {
cell = CustomCell()
// more cell options can be set here
}
cell.labelDisplayWattage.text = String(totalWattageList[indexPath.row])
return cell
}
}
let cell = tableView.dequeueReusableCellWithIdentifier("reusableCell", forIndexPath: indexPath) as! CustomCell
Instantiation of the CustomCell is responsibility of tableview, we just fetch the cell, if the cell is not instantiated when we fetch it tableview will instantiate it before returning it to us.
This behavior is of architectural design, it is not specific to swift. It works in same way for objective-c as well.
I'm trying to change the text of a UILabel in a UITableView (a property of one class,) from inside another class, but I'm having trouble. The code looks like this (The problem is at the end, in the didSelectRowAtIndexPath method of myDataSource)
The View Controller
class ViewController: UIViewController
{
//MARK: Properties
#IBOutlet weak var myTableView: UITableView!
#IBOutlet weak var myLabel: UILabel!
//needed so data from myDataSource is retained by ViewController and not just thrown away
let importedDataSource = myDataSource()
override func viewDidLoad()
{
super.viewDidLoad()
myTableView.dataSource = myDataSource()
myTableView.delegate = myDataSource()
}
override func didReceiveMemoryWarning()
{super.didReceiveMemoryWarning()}
}
The UITableViewDataSource and Delegate
class myDataSource: NSObject, UITableViewDataSource, UITableViewDelegate
{
let cellIdentifier = "myTableViewCell"
let myArray = ["Label one", "Label two", "Label three"]
//MARK: TableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{return 1}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{return myArray.count}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! myTableViewCell
cell.myCellLabel.text = myArray[indexPath.row]
return cell
}
//MARK:TableView Delegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
//This Line Here returns the error
ViewController().myLabel.text = "test"
//I want the end product to be ViewController().myLabel.text = myArray[indexPath.row], but left it as "test" just to simplify and isolate the error
}
}
(there's also a UITableViewCell class called myTableViewCell, but I left it out to be shorter)
Running returns "fatal error: unexpectedly found nil while unwrapping an Optional value" upon selecting one of the rows. How should I call myLabel so as to avoid this problem? I've tried hooking it up as an #IBOutlet inside myDataSource by ctrl-dragging it from the storyboard, but as I expected, it only lets me connect it to the view controller.
sorry, the code is a bit confusing with the two similarly named labels. I'm trying to call the myLabel var created in the ViewController (first piece of code), not the myCellLabel created in myTableViewCell (not shown)
The reason you are seeing the message "fatal error: unexpectedly found nil while unwrapping an Optional value" is because you are trying to assign a value to a property of nil.
In the implementation of your UITableViewDelegate method tableView(_:didSelectRowAtIndexPath:) the statement:
ViewController().myLabel.text = "test"
creates a new instance of your ViewController class (that is what ViewController() does)
accesses the myLabel property of the new instance (recall that in your declaration of the class you defined this property to be of type UILabel!, an implicitly-unwrapped optional UILabel, and implicitly-unwrapped optionals begin life as nil until they are assigned a value)
tries to assign "test" to the text property of myLabel - but as we just noted, myLabel is nil and this is why you are receiving the error message
EDIT
In an effort to assist you in your stated goal of "trying to change the text of a UILabel" (that label being the property myLabel of your ViewController instance), the route I would recommend is:
have your ViewController class adopt the UITableViewDelegate protocol
implement your UITableViewDelegate protocol methods inside of your ViewController class, where they will have direct access to the myLabel property
once you've done this, your implementation of that delegate method (now residing inside of your ViewController class) would look like:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
myLabel.text = myTableView.dataSource.myArray[indexPath.row]
}
...and while this will work, even this can be improved - I recommend you check out Apple's Table View Programming Guide
furthermore, unless there is a compelling reason not to, I also recommend that you have your ViewController class act as your UITableViewDataSource - from the Table View Programming Guide: "[t]he class creating the table view typically makes itself the data source and delegate by adopting the UITableViewDataSource and UITableViewDelegate protocols" (see this section)
You need to get the actual table view cell that was tapped in order to access it's variables. Use cellForRowAtIndexPath. It'll look something like this:
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if let myCell = tableView.cellForRowAtIndexPath(indexPath) as? myTableViewCell {
myCell.myLabel.text = "test"
}
}
I have a basic method for populating a tableView with a custom cell in Swift
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let object = array[UInt(indexPath!.row)] as Word
var cell:DictionaryTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("dictionaryCell") as DictionaryTableViewCell
cell.originalText.text = object.original
return cell
}
Works fine. Now, I got a custom cell DictionaryTableViewCell with custom properties and some IBActions (for different buttons inside that cell).
My question is: How do I expose data that was passed to the cell from tableView and use it from inside my custom cell controller? For instance I would like to be able to manipulate it for each cell using IBActions.
For now my custom cell controller is nothing fancy
class DictionaryTableViewCell: UITableViewCell {
#IBOutlet weak var originalText: UILabel!
#IBAction func peak(sender: AnyObject) {
}
}
Is there a custom property I can use to recover data passed onto it? Or do I have to implement some sort of a protocol? If so - how?