Custom cells with common features in ios - ios

I have an app in which I have a tableview. Inside this tableview I currently have 8 different types of cells. Each of these cells are different but also have similarities.
As an example (using only 2 cells for simplicity). Imagine you have cell 1 which can be divided into 3 parts:
--------------A--------------
--------------B--------------
--------------C--------------
Now cell 2 also has 3 parts. Parts A and C are the same as in cell1 (different data of course, but same structure):
--------------A--------------
--------------D--------------
--------------C--------------
Currently, in my cellForRowAt method I just check if the cell type should be 1 or 2 and then I dequeue cell for that type. The challenge is that I would like to avoid setting part A and C two different places in the code.
Eg. instead of
if type == type1 {
//Set A
//Set B
//Set C
} else if type == type2 {
//Set A
//Set D
//Set C
}
I would like
//Set A
//Set C
if type == type1 {
//Set B
} else if type == type2 {
//Set D
}
I was wondering if there is a way to "abstract" these commonalities?
Please let me know if anything is unclear.
EDIT: IB Challenge
I probably should also mention that a tricky part for me is figuring out how a parent cell would fit in in regards to IB. I have separate xib files for each cell type, but if I would only have one parent swift file with section A and C, can both my other xib files have the same outlets to this parent and how would this even be done?

Parent TableViewCell
class BaseTableViewCell: UITableViewCell {
#IBOutlet weak var ALabel: UILabel!
#IBOutlet weak var CLabel: 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
}
}
Subclass of BaseCell
class TypeOneTableViewCell: BaseTableViewCell {
#IBOutlet weak var BLabel: 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
}
}

Related

How do I prevent getting nil interface elements while using custom cell in UITableViewController?

There are many, many questions on this site and others addressing the issue of getting a Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value from trying to set an interface element in a custom table view cell.
How can I prevent the interface elements from being nil when it's time to set them?
I've done my homework and checked out the answers to these related questions.
I've made sure I don't register the class in the viewDidLoad, as I'm using a UITableViewController.
The UITableViewController is referenced properly in the Main.storyboard file.
The cells in the UITableViewController are given the proper reuseIdentifier and class.
My dequeueReusableCell call to the tableView has the proper identifier, matching the one in the storyboard.
I've broken the nib's outlets and reconnected them.
I'm setting up a UITableViewController, "FindTableViewController."
class FindTableViewController: UITableViewController {
var services = Fetch().getServices()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
/*Sections & rows defined here*/
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Monk", for:indexPath) as! Monk
let service = services[indexPath.row]
cell.set(service)
return cell
}
}
In another file, I define the custom cell Monk.
import UIKit
class Monk: UITableViewCell {
#IBOutlet weak var titleImage: UIImageView!
//other outlets defined here
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
}
func set(_ service:Service) {
if let imgv = self.titleImage {
imgv.image = service.image
} else {
print(self.titleImage)
}
self.titleLabel.text = service.title
/*other elements set here*/
}
}
I have a feeling that I did something really, really stupid.
I've set the custom class name to Monk, and also the cell identifier to Monk, because I didn't want that confusion to be the source of the issue and making the names different didn't solve the problem.
I've even printed the values in the Service object and they match what was expected.
if let imgv = self.titleImage {
imgv.image = service.image
} else {
print(self.titleImage)
}
Prints "nil" and the error is thrown on the next line,
self.titleLabel.text = service.title
I also set the identifier and class of the nib to "Monk".
This is something simple.
If you are using a custom NIB for your cell, then you do need to register it.
Something like this in viewDidLoad:
let myNib = UINib(nibName: "MonkCell", bundle: nil)
tableView.register(myNib, forCellReuseIdentifier: "Monk")
This is unnecessary if you choose to layout the cell directly in the storyboard for your view controller.

make UITableView updates from class of the cells populating it

I have used the same nib file to populated a UITableView. There are 2 different functionalities I have built out for these cells. If you anywhere on the cell other than a small button it runs a function and segues to another page. However if you click the small button, I want to update the UITableView and input a cell below the clicked cell, as a sort of dropdown. I have the below to successfully run a separate functionality:
import UIKit
class ContactCell: UITableViewCell {
//below are the outlets for the conact cells
#IBOutlet weak var msgStatus: UIView!
#IBOutlet weak var contactName: UILabel!
#IBOutlet weak var dropDown: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
msgStatus.layer.cornerRadius = msgStatus.frame.size.width/2
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func dropDown(_ sender: Any) {
print("selected drop down")
}
}
Moving forward I need to be able to get the index of the current cell and perform a UITableView update from the UITableViewCell's class. How would I be able to do this?
You could do this with delegation. Create a delegate protocol for your ContactCell:
protocol ContactCellDelegate {
func pleaseAddDropdown(to: ContactCell)
}
Then set up a delegate parameter for the cell:
var delegate: ContactCellDelegate?
Then subscribe your table view to the ContactCellDelegate protocol (you will have to subclass if you are using a standard UITableView). This means you will need to write an implementation of pleaseAddDropdown() for your table view class. When you dequeue a cell make sure the delegate parameter is assigned to your table view.
Now in your implementation of dropdown() you just need to do something like:
guard let _ = delegate else { return }
delegate!.pleaseAddDropdown(to: self)
Which will send that message up to the table view which can take the appropriate action.
Hope that helps.

Adding items to tableview with sections

I display a shopping list for different product groups as a table view with multiple sections. I want to add items with an add button for each group. So I equipped the header cell with a UIToolbar and a + symbol as a UIBarButtonItem.
Now every product group has an add button of his own:
If one add button was pressed, I have a problem identifying which one was pressed.
If I connect the add button with a seque, the function prepareForSeque(...) delivers a sender of type UIBarButtomItem, but there is no connection to the header cell from were the event was triggered.
If I connect an IBAction to the UITableViewController, the received sender is also of type UIBarButtomItem, and there is no connection to the header cell, too.
If I connect an IBAction to my CustomHeaderCell:UITableViewCell class, I am able to identify the right header cell.
This line of code returns the header cell title:
if let produktTyp = (sender.target! as! CustomHeaderCell).headerLabel.text
However, now the CustomHeaderCell class has the information I need.
But this information should be available in the UITableViewController.
I couldn't find a way to feed the information back to the UITableViewController.
import UIKit
class CustomHeaderCell: UITableViewCell {
#IBOutlet var headerLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func neuesProdukt(sender: UIBarButtonItem) {
if let produktTyp = (sender.target! as! CustomHeaderCell).headerLabel.text
{
print(produktTyp)
}
}
}
Here's how I typically handle this:
Use a Closure to capture the action of the item being pressed
class CustomHeaderCell: UITableViewCell {
#IBOutlet var headerLabel: UILabel!
var delegate: (() -> Void)?
#IBAction func buttonPressed(sender: AnyObject) {
delegate?()
}
}
When the Cell is created, create a closure that captures either the Index Path or the appropriate Section.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let section = indexPath.section
let cell = createCell(indexPath)
cell.delegate = { [weak self] section in
self?.presentAlertView(forSection: section)
}
}

How can I change a specific constraints on the last UITableViewCell?

I need to change the last UITAbleViewCell Bottom Constraints constant on runtime.
I've added an identifier on IB, and I've added the following extension to my UIView.
func getConstraintById(id:String) -> NSLayoutConstraint?{
let constraints = self.constraints()
for c in constraints{
if((c as! NSLayoutConstraint).identifier == id){
return c as? NSLayoutConstraint
}
}
return nil
}
All works fine, but it seems redundant to iterate all constraints for each cell.
Is there a better way?
I've solved this by adding a custom UITableView class with an outlet to the needed constraint, then I had a direct approach on my cellForRowAtIndexPath method.
class:
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var bottomPadding: NSLayoutConstraint!
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
}
}
Method:
if(index + 1 == ITEMS?.count){
cell.bottomPadding.constant = 5
}else{
cell.bottomPadding.constant = 0
}

keep UISwitch state of a prototype cell when changing ViewControllers

I have a UITableViewCell subclass where I have connected a UISwitch from a custom prototype cell. (data are passed in the Table View from a previous ViewController)
When I Change view controllers the state of the UISwitchchanges back to default
I need some way to remember the state of the switch while navigating through the app.
I have searched a few other topics, but nothing seemed to fall in my case.
Here is the code:
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var myStaticSwitch: UISwitch!
#IBOutlet weak var cellLabel: UILabel!
#IBAction func mySwitch(sender: UISwitch) {
if myStaticSwitch.on {
self.cellLabel.text = "on"
//Do other things
}
else {
self.cellLabel.text = "off"
//Do other things
}
}
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
}
}
You should have a model object that is used to determine the state of the UI representing it. This object can be saved to disk, persisted with Core Data, or even synced to a remote web service.
When you next encounter a need to display any UI related to the object, you can always check its properties to determine what to show. Your UI action method then simply updates the model representation:
#IBAction func mySwitch(sender: UISwitch) {
myModelObject.enabled = myStaticSwitch.on
}
The UI (e.g., your table cell) then looks for changes to enabled and updates the cellLabel as needed.
here is the code. Now I need a way to refer to each Switch separately and not as a whole.
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var myStaticSwitch: UISwitch!
#IBAction func mySwitch(sender: UISwitch) {
myStaticSwitch.on = (sender as UISwitch).on //Bool
NSUserDefaults.standardUserDefaults().setBool(myStaticSwitch.on, forKey: "autoAdjustSettings")
NSUserDefaults.standardUserDefaults().synchronize()
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
myStaticSwitch.on = NSUserDefaults.standardUserDefaults().boolForKey("autoAdjustSettings")
// Configure the view for the selected state
}
}

Resources