I cannot understand why some values are well passed from the tableViewController to my custom UITableViewCell, and some others are not.
In my UItableviewController.cellForRowAtIndexPath i set up a cell with some values, before returning this cell :
cell.label1.text = myCustomObject.id
cell.label2.text = myCustomObject.path
cell.myCustomObjectCellMember = myCustomObject
cell.pathCellMember = myCustomObject.path
return cell
On the custom UITableViewCell side, in awakeFromNib method, the two first cell members are Ok, the two last ones contain nil.
The only difference between the two first cell members and the two last ones is that the two first are declared as IBOutlet and linked to the storyboard, while the two others are not linked to the UI. But yet, it should be OK to write in these vars from the tableViewController, right ?
Here are the declarations of these variables in the custom UITableViewCell:
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
var pathCellMember : String!
var myCustomObjectCellMember: MyCustomObjectCellMember!
When logged (inside UITableViewCell.awakeFromNib), label1.text and label2.text show the correct value,
but pathCellMember and myCustomObjectCellMember display nil instead of the value assigned in UItableviewController.cellForRowAtIndexPath.
As requested, a more explicit code :
class CustomCellTableViewCell: UITableViewCell {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
var pathCellMember : String!
var myCustomObjectCellMember: MyCustomObjectCellMember!
override func awakeFromNib() {
super.awakeFromNib()
println("label1 : \(self.label1.text!)") //displays the value assigned
println("label2 : \(self.label2.text!)") //displays the value assigned
println("pathCellMember: \(self.pathCellMember!)") //displays nil
println("myCustomObjectCellMember.path : \(self.myCustomObjectCellMember.path)") //displays `nil`
}
Thank you
In cellForRowAtIndexPath is where you will reuse (deque) each cell. This is where you need to assign your vars values to each cell. While awake does set the initial values they will only fire the first time before the cell is reused. Assign everything in cellForRow or willDisplayCell (background colors, etc).
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/
Discussion
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 using the class or nib file you previously registered. If no cell is available for reuse and you did not register a class or nib file, this method returns nil.
If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its initWithStyle:reuseIdentifier: method. For nib-based cells, this method loads the cell object from the provided nib file. If an existing cell was available for reuse, this method calls the cell’s prepareForReuse method instead.
Related
I used UITableViewCell like this.
class WeatherListTableViewCell: UITableViewCell {
#IBOutlet weak var weatherImageView: UIImageView!
#IBOutlet weak var city: UILabel!
#IBOutlet weak var degree: UILabel!
#IBOutlet weak var rainPercent: UILabel!
var weather: String?
}
I thought the cell is kind of View so it can't have not UI stuff.
But sometimes I added a value like weather for using hidden info and the cell worked like ViewController.
Can I use cell like this?
UITableViewCell is a class like any other in iOS. If you derive from it you can extend its capabilities and therefore add properties and functions.
Like you have already done you can add #IBOutlets that represent parts of the cell's UI but you can also add non-visible fields that you just use within the class to hold information that you need.
Think about functions that you add, they are also not part of the UI but they are within the declaration of your WeatherListTableViewCell class.
There could be a function which uses the weather string you already added and extracts information from in, populating your UI elements with their corresponding values.
I would like to know if it is "correct" to store a reference to the Model that a UITableViewCell represents in it.
The reason I ask is due to the necessity of knowing the Model in case of a click action in a button inside it.
Is there a better (a.k.a: desirable) way of doing this?
Example:
class Person {
var name: String
var lastName: String
var age: Int
}
protocol PersonCellDelegate: NSObjectProtocol {
// should the second parameter be the model that the cell represents?
func songCell(_ cell: PersonCell, didClickAtEditButtonOfPerson person: Person)
}
class PersonCell: UITableViewCell {
#IBOutlet private weak var nameLabel: UILabel!
#IBOutlet private weak var lastNameLabel: UILabel!
#IBOutlet private weak var ageLabel: UILabel!
#IBOutlet private weak var editButton: UIButton!
// does the cell need to store its reference?
var person: Person! {
didSet {
nameLabel.text = person.name
// ...
}
}
weak var delegate: PersonCellDelegate?
// ...
}
A table view cell is a view. Less it knows about the application logic, better it is.
You could retrieve the entity used using the indexPath(for:) method :
protocol MyTableViewCellDelegate: AnyObject {
func myTableViewCellDidSomething(_ cell: MyTableViewCell)
}
class MyTableViewCell: UITableViewCell {
weak var delegate: MyTableViewCellDelegate?
}
class ViewController: UITableViewController, MyTableViewCellDelegate {
var personList: [Person] = []
func myTableViewCellDidSomething(_ cell: MyTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let person = personList[indexPath.row]
// ...
}
}
You ask:
does the cell need to store its reference?
No. In fact, that locks you into reference semantics and you might consider value semantics for the Person object. I also think it muddies the ownership model. Who now owns this Person object?
And even if you were committed to reference semantics, and wanted to use this pattern to detect Person changes, be wary that your didSet pattern is only half of the solution. The Person type is mutable and you’re detecting when the object is replaced with a new Person object, but not when the individual properties of Person change. If you’re going to go down this didSet road with mutable reference types, you might also want to add KVO for the relevant properties, too.
This pattern entails a fairly tight coupling of view objects and model objects. As others have suggested, you might consider other patterns for addressing this and/or reducing the burden on the view controller.
If you’re looking for automatic updating of the cell when the Person object mutates (and potentially vice versa), you can consider binding patterns, such as offered by libraries like RxSwift, Bond, etc.
I’d also refer you to Dave Delong’s presentation A Better MVC, which walks you through considerations if you don’t want to give up on MVC, but figure out ways to work with it, or Medium’s iOS Architecture Patterns, which is an introduction to other options.
In strict MVC, view should not access model directly.
When the user click the button in the cell, call delegate method. Let the delegate (usually is view controller) handle the click event (like modify model).
After updating the model, controller will update the view if needed.
I do use it in some cases, especially as you do here, in a custom cell.
I don't rely on the UItableViewCell to hold the data for me, that I have in the model, as the cell can be reused, when it is off screen
It depends:
If you can move cells in the table view (manually or by pressing a button) using insertRows and deleteRows then it's almost the only way (along with protocol/delegate) to be able to get the index path of a cell efficiently without reloading the entire table view.
In a straight table view where no cells are moved don't pass the model to the cell. You can use callback closures which capture the index path and even the model item.
So, there isn't one right way, so I can just tell you would I would do.
If I didn't move cells, I would keep having model property. In cell class you shouldn't set properties of outlets, since cells are reusable. You just let controller know that data source is changed and you should reload rows/certain row.
var person: Person! // instead, tell controller, that person has been changed
Next, I would left delegate pattern and I would use closure variables. It makes code more Swifty (in future you can search for RxSwift).
class PersonCell: UITableViewCell {
var personChanged: (Person) -> Void = { _ in }
var person: Person!
func foo() {
// change person's properties
personChanged(person)
}
func setCell() {
nameLabel.text = person.name
}
}
Then set all the things such as label's text in cellForRowAt UITableViewDelegate's method. Also don't forget to set cell's closure and declare what should happen after person is changed
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = // ...
cell.person = people[indexPath.row]
cell.setCell()
cell.personChanged = { [weak self] person in
guard let self = self else { return }
self.people[indexPath.row] = person
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
return cell
}
I'm attempting to make a list using a UITableView and am getting the error:
[UITableView] has no member 'dequeueReusableCell'
I have created a table ui with a prototype cell that has the identifier "CellController".
I have initialised my table view at the beginning of my class
#IBOutlet var myTaskListView: [UITableView]!
Then when I'm trying to declare a new cell I'm unable to use dequeueReusableCell
let cell = myTaskListView.dequeueReusableCell(withIdentifier: String(CellController)) as! CellController
Is there something more I have to do to make "dequeueReusableCell" a member of UITableView?
I'm relatively new to Xcode and swift programming, I hope I provided everything needed to help solve the issue.
There is problem with outlet that you have created for your table view.
#IBOutlet var myTaskListView: [UITableView]!
You can try this:
#IBOutlet var yourTableView: UITableView!
let cell : yourCell = self.yourTableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier") as! yourCell
I am new to iOS development so forgive me if I'm missing something obvious. I have a view controller that contains a subview in which I've created a numpad, and for the time being I want to give the numpad view its own UIView subclass because I want to do a few different things with it. Right now the numpad is just creating a string from the keys that get pressed, and I've set up a delegate to pass that string anywhere else I want to use it (though I've also tried accessing the raw input directly in the view controller with let a = subview(); label.text = a.rawInput).
Whenever I try to set the text of the UILabel in the view controller to the subview's raw input, whether by delegation or directly, the UILabel is found to be nil and throws the error in the title.
Things I've tried:
Setting the text inside a viewDidLoad override, and outside of it
Setting a variable (testInput) inside the view controller to adopt the subview's raw input and setting the label text to that (I've confirmed that the variable inside the view controller gets properly set, so no delegation issues)
Using didSet on the testInput variable both to set label text to testInput and to try calling viewDidLoad and set the label text in there (printing testInput inside this didSet does print the right string, FWIW)
Deleting and relinking the IBOutlet for my label
Strong and weak storage for the IBOutlet variable
Trying to do the same thing in another subview within the view controller, in case for some reason it was the view controller's own fault
Searching everywhere for a solution that works
I'm stumped. Here is my relevant numpad code:
import UIKit
protocol NumpadDelegate {
func updateInput(input: String)
}
class Numpad: UIView {
// MARK: UI outlets
#IBOutlet weak var decButton: UIButton!
// MARK: Properties
var rawInput: String = ""
var visibleInput: String = ""
var calcInput: String = ""
var operandReady = 1
var percentWatcher = 0
var delegate: NumpadDelegate? = BudgetViewController()
// MARK: Functions
func handleRawInput(str: String) {
rawInput += str
print("numpad input is \(rawInput)")
delegate?.updateInput(rawInput)
}
And here is the view controller code:
import UIKit
class BudgetViewController: UIViewController, NumpadDelegate {
// MARK: Properties
//#IBOutlet weak var transactionValueField: UITextField!
#IBOutlet weak var remainingCashForIntervalLabel: UILabel!
#IBOutlet weak var intervalDenoterLabel: UILabel!
#IBOutlet weak var currencyDenoterLabel: UILabel!
#IBOutlet weak var mainDisplayView: TransactionType!
#IBOutlet weak var inactiveInputView: InactiveInput!
#IBOutlet weak var numpadView: Numpad!
#IBOutlet weak var rawInputLabel: UILabel!
var remainingCashForInterval = 40
let display = TransactionType()
var testInput = "" {
didSet {
viewDidLoad()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// let numpad = Numpad()
// numpad.delegate = self
// print("\(numpad.delegate)")
self.rawInputLabel.text = testInput
}
func updateInput(input: String) {
print("view controller now has \(input)")
display.mainInput = input
testInput = input
}
As a side note, in case you noticed that my protocol isn't a class type, for some reason adding : class to it and declaring my delegate as a weak variable prevents the delegation from working. Any suggestions there?
You assigned the delegate like so:
var delegate: NumpadDelegate? = BudgetViewController()
That doesn't reference the view controller whose scene was presented, but rather a new blank one. And that's why when you used weak, why it was deallocated (because that orphaned instance of the view controller has no strong references to it).
You should define the protocol to be a class protocol again, and define delegate to be:
weak var delegate: NumpadDelegate?
And then, in the view controller's viewDidLoad, uncomment the line that sets that delegate:
numpadView.delegate = self
But, do not uncomment the line that says numpad = Numpad(); that is incorrect as that creates yet another Numpad instance. But you do want to set the delegate of the existing Numpad, though.
Both of these issues (namely, getting a reference to the view controller that is to be the delegate of the Numpad view; and getting a reference to the Numpad view that the storyboard presented) suggest some misunderstanding about the the process of presenting a storyboard scene.
The process is basically as follows:
the view controller is instantiated, using whatever class you specified as the base class for that scene;
its root view, as well as all of the subviews on that scene will be instantiated for you;
the storyboard will hook up the IBOutlet references in the scene's base class to the outlets you created; and
the view controller's viewDidLoad is called.
That's an oversimplification, but that's the basic process.
But the key is that all of these view controllers and views that are referenced on the storyboard scene are created for you. You don't want to try to create any of these yourself (and the presence of the () at the end of BudgetViewController() or Numpad() says "create a new instance of x", which is not what we want to do here).
So, when we need to get a reference to the view controller so that we can programmatically specify the delegate for one of the views, you can do this in viewDidLoad, at which point self references the view controller that the storyboard instantiated for us. We don't want to instantiate a new one. Likewise, when you want to reference the Numpad that the storyboard instantiated for us (in order to hook up its delegate), you use the IBOutlet you hooked up in Interface Builder, rather than programmatically instantiate a new Numpad with Numpad().
I don't think the title uses the right terminology so I'll try to clarify now.
I have two view controllers which i want to pass data between. view 1 has a tableView and the 2nd view has a MKMapView. Now in the corresponding controllers I want when you click on a cell in view 1 it sends you to the place on the map which the cell indicates eg. a New York cell would send to a map of new York. So I tried in the didSelectRowAtIndexPath that I create an instance of the second controller which would transfer the data to it. But when I did that it would always return a nil value in the second view controller.
So instead I created a 3rd swift file which has some global variables and when I transfer the data via them it works perfectly. Why is this so?
Cheers.
EDIT: Before the I went via a third file
Code for the ViewController 1
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell = array[indexPath.row]
var viewController2 = ViewController2()
viewController2.textLabel1 = selectedCell.name
coord.textLabel2 = selectedCell.coordinate
}
Code for ViewController 2
#IBOutlet weak var textLabel1: UILabel!
#IBOutlet weak var textLabel2: UILabel!
Code for View Controller 2 going via 3rd file
#IBOutlet weak var textLabel1: UILabel!
#IBOutlet weak var textLabel2: UILabel!
override func viewDidLoad() {
textLabel1.text = globalVar1
textLabel2.text = globalVar2
}
Code from the 3rd File
var globalVar1: String!
var globalVar2: String!
So from the comments below I take it that in the first way the textLabels hadn't been initialised yet, so the values I assigned to them where turned into nil values. Is this correct? If so how would you do the first way correctly
If I had to guess, and I would because I cannot comment for more info yet(i get in trouble ).
It's because you are trying to assign it to an outlet.
The outlet has not been set yet which means when it is set (I think around ViewDidLoad)
the outlet will be set to nil.
The properties should however be retained if the object hasn't gone out of the heap that is.
PSedu code:
first view :
Your table view
Second View:
your map view
How to work
In you second view:
Add a NSMutableArray *valueArraytoGet in .h file,set its property
#synchronize it in .m file
Now in your first view at didSelectRowAtIndex method
create object of Second View Controller
and assign data as
SecondViewController *object=......
object.valueArraytoGet=[assign ur value array here]....
Hope it will help