UITableView cell reuse identifier - swift

I have created a structure for different Cell Identifiers:
enum CustomCellIdentifiers
{
static let cellForCountry = "cellForCountry"
static let cellForCity = "cellForCity"
static let cellForStoreType = "cellForStoreType"
}
and I am registering the cell to the table as per switch case, like:
view.tableForItems.register(UINib.init(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier:cellIdentifier)
but when I registered the cell I got at error that the reuseIdentifier is nil:
class CustomTableCell: UITableViewCell
{
override func awakeFromNib()
{
super.awakeFromNib()
// Initialization code
switch self.reuseIdentifier ?? "cellForCountry" //It only work with country cell
{
case CustomCellIdentifiers.cellForCountry:
print("cellForCountry") break;
case CustomCellIdentifiers.cellForCity:
print("cellForCity") break;
case CustomCellIdentifiers.cellForStoreType:
print("cellForStoreType")
break;
default: break
}
}
}

set reUseIdentifier in the xib file too.
if you are not able to set the reuseIdentifier in xib class means you created xib file from UIView. Instead
Take the UITableviewcell Class to create xib not the UIView..
then you are able to assign reuseIdentifier from xib file
Please see the image below

Related

Usability of a button inside a UICollectionViewCell?

I have a ProductVC.swift (ProductViewController) file and a ProductCell.swift. The ProductVC contains a UICollectinView and ProductCell is a specific UICollectionViewCell.
ProductCell.xib looks like this:
ProductVC contains an array with all the cell data (products) and populates the cells.
My goal: The user should have the possibility to like an product. He can do it by clicking the like button on the top right corner of every cell. Every cell shows a specific product which is specified by a productID.
My Problem: The like button action (IBAction func) is in the ProductCell. ProductCell doesn´t have the cell data. Cell data is stored in ProductVC in an array. So I don´t know how catch the product(productID) the user wants to like.
My Tries: With the code below I can get the indexPath of the cell where the user clicked the like button. But I can´t use this indexPath to get the product data because the data is stored in ProductVC. I could also store the data in ProductCell but it is not a clean way. Is it possible mb to give this indexPath to the ProductVC?
extension UICollectionView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let center = view.center
let viewCenter = self.convert(center, from: view.superview)
let indexPath = self.indexPathForItem(at: viewCenter)
return indexPath
}
}
let superview = self.superview as! UICollectionView
if let indexPath = superview.indexPathForView(button) {
print(indexPath) // indexPath of the cell where the button was pressed
}
SOLVED Solution is a callback closure:
//UICollectionViewCell
var saveProductLike: ((_ index: Int) -> Void)?
#IBAction func likedButtonClicked(_ sender: UIButton) {
print("Liked button clicked!")
let productArrayIndex = calculateProductArrayIndex(for: sender)
saveProductLike?(productArrayIndex!)
}
//UIViewController
cell.saveProductLike = { (index) -> Void in
print(index)
}
There are several approaches to solve this but I'll talk about the most common one which is using delegation.
protocol ProductCellDelegate: AnyObject {
func productCellDidPressLikeButton(_ cell: ProductCell)
}
in ProductCell define a property weak var delegate: ProductCellDelegate? and in the target action of the like button inform your delegate
#objc private likeButtonPressed(_ sender: Any) {
delegate?.productCellDidPressLikeButton(self)
}
In your view controller you could conform to the protocol and implement it like this:
func productCellDidPressLikeButton(_ cell: ProductCell) {
guard let ip = collectionView.indexPath(for: cell) else { return }
// process event, get product via index...
}
Then you need to set the view controller to be the delegate in collectionView(_:willDisplay:forItemAt:) or
collectionView(_:cellForItemAt:): cell.delegate = self.

Different behavior between T.Type & [T.Type]

I am using generics func to improve tableView cells as below;
public func registerNib<T: UITableViewCell>(_:T.Type) {
print("Registering.....")
print(T.self)
let nib = UINib(nibName: String(describing: T.self), bundle: nil)
register(nib, forCellReuseIdentifier: String(describing: T.self))
}
And I wrote two func to register cell, one for single cell and one for multiple cells.
//Single Cell Register
func setup<T: UITableViewCell>(cell:T.Type) {
print(cell)
tableView.registerNib(cell)
self.setupParameters(.....)
}
//Multiple Cell Register
func setup<T: UITableViewCell>(cells:[T.Type]) {
for cell in cells {
print(cell)
tableView.registerNib(cell)
}
self.setupParameters(.....)
}
For single cell implementation, you can call func as below and it will registerNib correctly.
self.baseTableView.setup(cell: CompetencyCell.self, rowHeight: 60)
**************
Output:
CompetencyCell
Registering...
CompetencyCell
But problem is occur with multiple implementation;
self.baseTableView.setup(cells: [CompetencyCell.self, BehaviorCell.self], rowHeight: 60)
**************
Output:
CompetencyCell
BehaviorCell
Registering...
UITableViewCell
UITableViewCell
I really wonder what is the reason, or what I miss? It looks like same behavior but when I want to pass T.Type value to RegisterNib method it turns to UITableViewCell and after some point of course I got crash because it could not find the cell.
Do you have any advice?
Thanks a lot.
When calling a generic function the generic type T represents a single static type.
[T.Type] is nonsensical because it declares an array of the same type specifier.
T doesn't mean Any!
As described in vadian's answer, generic is not a good tool to use with Array containing multiple types of values.
You can try something like this:
extension UITableView {
public func registerNib(_ type: UITableViewCell.Type) {
print("Registering.....")
print(type)
let nib = UINib(nibName: String(describing: type), bundle: nil)
register(nib, forCellReuseIdentifier: String(describing: type))
}
}
And this:
func setup(cells: [UITableViewCell.Type] /*, otherParams: ...*/) {
for cell in cells {
print(cell)
tableView.registerNib(cell)
}
//self.setupParameters(.....)
}

unit testing cell is nil

Issue:
I loaded collectionView with 3 Dummy items. However Cell came back nil, is it because view was never loaded? How do you guys test your collectionViewCell type?
Code
var window: UIWindow?
var sut: QuestsDataProvider!
var collectionView: UICollectionView!
override func setUp() {
super.setUp()
bulletinController = BulletinController(collectionViewLayout: UICollectionViewFlowLayout())
sut = QuestsDataProvider(acceptedQuests: false, completedQuests: false)
bulletinController.collectionView?.dataSource = sut
bulletinController.collectionView?.delegate = sut
window = UIWindow()
window?.makeKeyAndVisible()
window?.rootViewController = bulletinController
}
func testCellIsQuestCell() {
let indexPath = IndexPath(item: 1, section: 0)
let cell = collectionView.cellForItem(at: indexPath)
guard let count = sut.questManager?.quests.count else {return XCTFail()}
XCTAssertTrue(cell is QuestCell)
}
Edit:
Upon Further testing, I'm able to see the dummy Cell inside my simulator and get a accurate count from numberOfitems(InSection: Int). However I have no visible Cell.
2nd Edit:
After further research, I found out the issue is collectionView.cellForItem(at: indexPath) only shows visible cell. Is there any other method for unit testing collection view cell type?
You need to access the view object of the view controller before it and its subview components will be fully initialised.
You should be able to just do let _ = bulletinController.view in your setup function. it is quite a common approach, see here
Relevant parts included below
func setupCreateOrderViewController()
{
let bundle = NSBundle(forClass: self.dynamicType)
let storyboard = UIStoryboard(name: "Main", bundle: bundle)
createOrderViewController = storyboard.instantiateViewControllerWithIdentifier("CreateOrderViewController") as! CreateOrderViewController
_ = createOrderViewController.view
}
Quote from link:
But there are two very, very important things happening on the last line:
Asking for the view property of createOrderViewController causes the view to be loaded. The viewDidLoad() method is called as a result.
After the view is loaded, all the IBOutlets are also set up and ready to be used in out tests. For example, you can assert that a text field outlet’s text equal to a string you expect.
EDIT:
You can also just call loadViewIfNeeded() on the view controller, which will do the same thing.
Loads the view controller’s view if it has not yet been loaded.

Display a xib view into a UITableView Cell

I made a view in a xib file which is loaded in the main ViewController called "ExperienceScreen". Adding this xib view to the ExperienceScreen works perfectly. The problem is that I would like to add this xib view in a UITableViewCel. I am using the following code to do that :
let experiences = service.getExperiences()
// 3. Loop through the array of experiences
for element in experiences {
if let customView = Bundle.main.loadNibNamed("ExperienceDetail", owner: self, options: nil)?.first as? ExperienceDetail
{
customView.lblTitle.text = element.title
customView.lblCompany.text = element.company
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
cell.addSubview(customView)
cell.bringSubview(toFront: customView)
}
}
tableView.reloadData()
When launching, the subview is not shown in the UITableViewCell. The customView View is filled correctly with the xib View. I checked this using a breakpoint.
Someone knows what I'am doing wrong?
Many thanks for helping !!
If you want to display your xib file as UITableViewCell, then following scenario works
1. make sure your xib class is sub class of UITableViewCell.
2. register your xib
//class of xib file
class TableCell: UITableViewCell {
static let identifier = "TableCell"
static let nib = UINib(nibName: "TableCell", bundle: nil)
}
// In view controller
func setupTableView() {
tableView.dataSource = self
tableView.delefate = self
tableView.register(TableCell.nib, forCellReuseIdentifier:
TableCell.identifier)
}
call setupTableView() in viewDidLoad()
Don't try to set up your cells all at once.
You should implement the UITableViewDataSource protocol and configure each cell using the method tableView(_:cellForRowAt:). In this method, you can call register(_:forCellReuseIdentifier:) to use your XIB for newly created cells.
There are lots of tutorials on creating table views where you can find step-by-step instructions on doing this.

Cannot load image from PARSE into PFTableViewCell's imageView property

I'm working with the PFQueryTableViewController and setting it up to find only friendships for this user which has been approved or sent to him.
"fromUser" and "toUser" are pointers to the user class, where I need the username and profilePicture from for each of the users contained in the queries results.
Now I try to fetch those in my cellForRowAtIndexPath method and load the image:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cellIdentifier = "contactCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! PFTableViewCell
if let user = object?["fromUser"] as? PFUser {
user.fetchInBackgroundWithBlock({ (user, error) -> Void in
if error != nil {
println("Could not fetch user object")
}
let user = user as! PFUser
cell.textLabel?.text = user.username!
cell.imageView?.file = user["profilePicture"] as? PFFile
cell.imageView?.loadInBackground()
})
} }
Getting the username to display in the tableView works just fine, but the image is actually never loaded. I tried different approaches to get the image loaded, but my cell's imageView property is always nil.
The prototype's class is set to PFTableViewCell
The controller is linked to the view in storyboard
Please let me know, if you guys have any idea why this built in property is nil and how to fix that.
Thanks,
Well I found a workaround which actually works quite good:
Create a prototype cell in your TableView (Storyboard) and set it's class to the normal "UITableViewCell"
Set the reuseIdentifier property of this cell to a value of your liking
Let your custom cell's file owner (I created a nib file for this cell) to be a subclass of PFTableViewCell
Create custom outlets for the textLabel and imageView
Register that Nib for the reuseIdentifier set in Step 2 in your TableViewController
Finally, use that class in your cellForRowAtIndexPath method like this:
let cell: PeopleTableViewCell! = tableView.dequeueReusableCellWithIdentifier("peopleCell") as? PeopleTableViewCell
I hope this will fix your problems too.
Regards,
[Edit]: It seems like SO doesn't like my code snippet to be formatted. It's embedded in code tags..
The PFTableViewCell does not work well with standard styles. So you have to make your cell a custom cell.
These re the steps I took to make it work (in Storyboard):
subclass PFTableViewCell with you own class
in the storyboard, customize the prototype cell using a custom cell
drag a UIImageView from the palette but then set its class as PFImageView
connect it to an IBOutlet in your PFQueryTableViewController subclass
implement cellForRowAtIndexPath: in the PFQueryTableViewController the way you did (your code was OK).
That way worked for me.