Use delegate to show/hide a tableview cell - ios

I have a tableview with three rows, In the second row there is UISwitch that when it's off, the third row should be hidden and when it's on, it would be shown.
I use delegate for that:
here is the protocol:
protocol ChangeStatusOFSwitchBtnDelegate {
func toggle(isOn: Bool)
}
and here is the configuration of the delegate in UITableViewCell
#IBOutlet weak var deadlineSwitchState: UISwitch!
func configure(swicthIsOn: Bool, delegate: ChangeStatusOFSwitchBtnDelegate) {
deadlineSwitchState.isOn = swicthIsOn
self.switchBtnDelegate = delegate
}
#IBAction func changeStateToggle(_ sender: UISwitch) {
if deadlineSwitchState.isOn == true {
switchBtnDelegate!.toggle(isOn:true)
} else {
switchBtnDelegate!.toggle(isOn:false)
}
}
var switchBtnDelegate: ChangeStatusOFSwitchBtnDelegate?
and here is some part of the codes in UIViewController
class AddListPopup: UIViewController, UITableViewDelegate, UITableViewDataSource, ChangeStatusOFSwitchBtnDelegate {
// Delegate
private var switchBtnIsOn = false
func toggle(isOn: Bool) {
self.switchBtnIsOn = isOn
NewListDetailsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (taskCellArray.count - 1 ) + (switchBtnIsOn ? 1 : 0)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
indexPath.row == 1 {
let switchCell = tableView.dequeueReusableCell(withIdentifier: "switch cell", for: indexPath) as! NewListPopupViewCell
switchCell.configure(swicthIsOn: switchBtnIsOn, delegate: self) //Set delegate here
return switchCell
}
return UITableViewCell()
}
the problem is:
I will get two errors here:
first, if I unwrap switchBtnDelegate in UITableViewCell same as the code you see here, I got:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
this error appears here:
#IBOutlet weak var deadlineSwitchState: UISwitch!
#IBAction func changeStateToggle(_ sender: UISwitch) {
if deadlineSwitchState.isOn == true {
switchBtnDelegate!.toggle(isOn:true) //The error appears here
} else {
switchBtnDelegate!.toggle(isOn:false)
}
}
var switchBtnDelegate: ChangeStatusOFSwitchBtnDelegate?
and If i use ? to keep it optional:
#IBOutlet weak var deadlineSwitchState: UISwitch!
#IBAction func changeStateToggle(_ sender: UISwitch) {
if deadlineSwitchState.isOn == true {
switchBtnDelegate?.toggle(isOn:true)
} else {
switchBtnDelegate?.toggle(isOn:false)
}
}
var switchBtnDelegate: ChangeStatusOFSwitchBtnDelegate?
I got this error:
class AppDelegate: UIResponder, UIApplicationDelegate { Thread 1: signal SIGABRT
Could anyone help me to where the problem comes from?
Thank you so much

(1)
The reason that your delegate is not being set appears to be that you are missing an if in your code on the first line of tableView(UITableView, cellForRowAt:IndexPath). It looks like that code in the block that you had intended to be an if statement is never running.
I would add an if on that first line and see if it is doing what you wanted.
Update: This would also explain the message unrecognized selector sent to instance 0x7fd3a4068800' in the console, because your method would always return a UITableViewCell, instead of a NewListPopupViewCell.
(2)
Crashing like this
class AppDelegate: UIResponder, UIApplicationDelegate { Thread 1: signal SIGABRT
usually happens to me when:
1. I'm trying to load a Storyboard that is not part of the target, or
2. I'm trying to use a segue that is not on the controller I'm messaging, or
3. There are missing connections between the controller and other elements in the storyboard.
So, I would make sure that everything in your storyboard (or xib) has the appropriate connections, and try again.
(3)
Regarding the delegate ... You should make sure that your delegates are always declared using the weak keyword, and they are always treated as optional.
weak var delegate: ChangeStatusOFSwitchBtnDelegate?

Related

SIGABRT error in dequeueReusableCell(withIdentifier:), when using custom UITableViewCell

I'm building an app with multiple scenes and a table view with custom cells in each. I got the home screen table view to work fine and then I segue to the new scene from the custom cells. When it segues, my second view controller crashes.
Here is my code for the view controller
import UIKit
class QuestionViewController: UIViewController {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var submitButton: UIButton!
#IBOutlet weak var qTableView: UITableView!
var answers : [QuestionOption] = []
override func viewDidLoad() {
super.viewDidLoad()
answers = [QuestionOption(text: "test"), QuestionOption(text: "test"), QuestionOption(text: "test"), QuestionOption(text: "test")]
qTableView.delegate = self
qTableView.dataSource = self
submitButton.setTitle("Submit", for: .normal)
questionLabel.text = "test question"
}
}
extension QuestionViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return answers.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let a = answers[indexPath.row]
let cell = qTableView.dequeueReusableCell(withIdentifier: "QuestionOptionCell") as! QuestionOptionCell
cell.setOption(option: a)
return cell
}
}
Here's my code for the cell
import UIKit
class QuestionOptionCell: UITableViewCell {
#IBOutlet weak var cellTitle: UILabel!
func setOption(option: QuestionOption){
cellTitle.text = option.text
}
}
Here's my code for the QuestionOption class
import Foundation
import UIKit
class QuestionOption{
var text: String
init(text: String){
self.text = text
}
}
Crash log
2019-02-20 14:33:28.394695-0800 iQuiz[8935:822409] *** NSForwarding: warning: object 0x7fd608407c40 of class 'iQuiz.QuestionOption' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[iQuiz.QuestionOption initWithCoder:]
2019-02-20 14:33:28.395281-0800 iQuiz[8935:822409] Unrecognized selector -[iQuiz.QuestionOption initWithCoder:]
Here's my storyboard if that helps at all
I've made sure my identifier matches and I don't have any extraneous or unconnected outlets, those are the only solution to this problem I can find online.
The crash log says that QuestionOption must be a subclass of NSObject and adopt NSCoding which is overkill in this case. Actually a struct would be sufficient.
You can avoid it by deleting the method in QuestionOptionCell
func setOption(option: QuestionOption){
cellTitle.text = option.text
}
and set the value in cellForRowAt directly by replacing
cell.setOption(option: a)
with
cell.cellTitle.text = a.text
Things to check:
Verify that "QuestionOptionCell" is indeed the reuse identifier for the cell.
Verify that the selected type for the cell is QuestionOptionCell.
In cellForRowAt, use tableView.dequeueReusableCell instead of qTableView.dequeueReusableCell.
Otherwise, share the crash log with us.

Swift 4.2: Unable to add custom behaviour to a custom UITableViewCell using Protocol oriented delegate concepts

I've a custom UITableViewCell, in that I've two UILabels & one UIButton. I'm able to load data...and display it as per requirement.
Problem Statement-1: Now problem exist in my UIButton, which is in my UICustomTableViewCell. Due to this I'm unable to handle click event on that UIButton.
Problem Statement-2: On button Click I have to identify the index of that Button click and pass data to next ViewController using segue.
Now have a look on...what did I've tried for this...
Yes, first-of-all I have thought that Binding IBOutlet action in my CustomCell will resolve my problem...but actually it doesn't solved my problem.
After that I've accessed button using .tag and initialised index path.row to it.
But it won't helped me.
So now I'm using Protocol oriented concept using delegate to handle click event on my UIButton which is available in CustomCell.
What did I tried:
SwiftyTableViewCellDelegate:
protocol SwiftyTableViewCellDelegate : class {
func btnAuditTrailDidTapButton(_ sender: LeadCustomTableViewCell)
}
CustomTableViewCell with delegate:
class LeadCustomTableViewCell: UITableViewCell {
#IBOutlet weak var lblMeetingPersonName: UILabel!
#IBOutlet weak var lblPolicyNo: UILabel!
#IBOutlet weak var btnLeadAuditTrail: UIButton!
weak var delegate: SwiftyTableViewCellDelegate?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func btnAuditTrailTapped(_ sender: UIButton) {
delegate?.btnAuditTrailDidTapButton(self)
}
}
ViewController implementing delegate:
class LeadViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, SwiftyTableViewCellDelegate {
//IBOutlet Connections - for UITableView
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//setting dataSource & delegates of UITableView with this ViewController
self.tableView.dataSource = self
self.tableView.delegate = self
//Reloading tableview with updated data
self.tableView.reloadData()
//Removing extra empty cells from UITableView
self.tableView.tableFooterView = UIView()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:LeadCustomTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! LeadCustomTableViewCell
//Assigning respective array to its associated label
cell.lblMeetingPersonName.text = (meetingPersonNameArray[indexPath.section] )
cell.lblPolicyNo.text = (String(policyNoArray[indexPath.section]))
cell.btnLeadAuditTrail.tag = indexPath.section
cell.delegate = self
return cell
}
//This is delegate function to handle buttonClick event
func btnAuditTrailDidTapButton(_ sender: LeadCustomTableViewCell) {
guard let tappedIndexPath = tableView.indexPath(for: sender) else { return }
print("AuditTrailButtonClick", sender, tappedIndexPath)
}
Don't know why this is not working.
Link the touch up inside event in cellForRow by adding the following code:
cell.btnLeadAuditTrail.addTarget(self, action:#selector(btnAuditTrailDidTapButton(_:)), for: .touchUpInside)

UITableView with controller separate from ViewController

I'm a newbie to Swift and XCode, taking a class in iOS development this summer. A lot of projects we're doing and examples I'm seeing for UI elements like PickerViews, TableViews, etc. are defining everything in the ViewController.swift file that acts as the controller for the main view. This works fine, but I'm starting to get to the point of project complexity where I'd really like all of my code to not be crammed into the same Swift file. I've talked to a friend who does iOS development on the side, he said this is sane and reasonable and well in-line with proper object-oriented programming... but I just can't seem to get it to work. Through trial and error I've gotten to this situation: the app runs in the simulator, the UITableView appears, but I'm not getting it populated with entries. I can get it working just fine when all the code is in the ViewController, but once I start trying to create a new controller class and make an instance of that class the dataSource/delegate of the UITableView I start getting nothing. I feel like I'm either missing some core understanding of Swift here, or doing something wrong with the Interface Builder in XCode.
My end result should be a UITableView with three entries in it; currently I'm getting a UITableView with no entries. I'm following along with a few different examples I've Googled, but primarily this other SO question: UITableView example for Swift
ViewController.swift:
import UIKit
class ViewController: UIViewController{
#IBOutlet var stateTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
var viewController = StateViewController()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
}
StateViewController.swift:
import UIKit
class StateViewController: UITableViewController{
var states = ["Indiana", "Illinois", "Nebraska"]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return states.count;
}
func tableView(cellForRowAttableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style:UITableViewCellStyle.default, reuseIdentifier:"cell")
cell.textLabel?.text = states[indexPath.row]
return cell
}
}
In XCode I have the UITableView hooked up to the View Controller; the outlets are set to dataSource and delegate and the referencing outlet is stateTableView.
I'm not getting any errors; I do get a warning on my `var viewController = StateViewController()' statement in ViewController.swift where it wants me to use a constant, but switching it to a constant doesn't change the behavior (this is as it should be, I assume).
Originally I assumed that the error was in my StateViewController.swift file, where I'm not creating an object that adheres to the UITableViewDataSource or UITableViewDelegate protocol, but if I even add them into the class statement I immediately get errors like "Redundant conformance of 'StateViewController' to protocol 'UITableViewDataSource'" - I'm reading that this is because inheriting from UITableViewController automatically inherits the other protocols as well.
The last thing I tried was instead referring to self.states in the StateViewController's tableView functions, but I'm pretty sure self in Swift works the same as it does in Python and it feels like I'm just trying to add magic words at this point.
I've investigated as far as my currently-limited Swift knowledge can take me, so any answer that explains what I'm doing wrong rather than just telling me what to fix would be very appreciated.
Your issue is being caused by a memory management problem. You have the following code:
override func viewDidLoad() {
super.viewDidLoad()
var viewController = StateViewController()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
Think about the lifetime of the viewController variable. It ends when the end of viewDidLoad is reached. And since a table view's dataSource and delegate properties are weak, there is no strong reference to keep your StateViewController alive once viewDidLoad ends. The result, due to the weak references, is that the dataSource and delegate properties of the table view revert back to nil after the end of viewDidLoad is reached.
The solution is to create a strong reference to your StateViewController. Do this by adding a property to your view controller class:
class ViewController: UIViewController{
#IBOutlet var stateTableView: UITableView!
let viewController = StateViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.stateTableView.delegate = viewController
self.stateTableView.dataSource = viewController
}
}
Now your code will work.
Once you get that working, review the answer by Ahmed F. There is absolutely no reason why your StateViewController class should be a view controller. It's not a view controller in any sense. It's simply a class that implements the table view data source and delegate methods.
Although I find it more readable and understandable to implement dataSource/delegate methods in the same viewcontroller, what are you trying to achive is also valid. However, StateViewController class does not have to be a subclass of UITableViewController (I think that is the part that you are misunderstanding it), for instance (adapted from another answer for me):
import UIKit
// ViewController File
class ViewController: UIViewController {
var handler: Handler!
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
handler = Handler()
tableView.dataSource = handler
}
}
Handler Class:
import UIKit
class Handler:NSObject, UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell")
cell?.textLabel?.text = "row #\(indexPath.row + 1)"
return cell!
}
}
You can also use adapter to resolve this with super clean code and easy to understand, Like
protocol MyTableViewAdapterDelegate: class {
func myTableAdapter(_ adapter:MyTableViewAdapter, didSelect item: Any)
}
class MyTableViewAdapter: NSObject {
private let tableView:UITableView
private weak var delegate:MyTableViewAdapterDelegate!
var items:[Any] = []
init(_ tableView:UITableView, _ delegate:MyTableViewAdapterDelegate) {
self.tableView = tableView
self.delegate = delegate
super.init()
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableViewAutomaticDimension
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func setData(data:[Any]) {
self.items = data
reloadData()
}
func reloadData() {
tableView.reloadData()
}
}
extension MyTableViewAdapter: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Hi im \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
delegate?.myTableAdapter(self, didSelect: items[indexPath.row])
}
}
Use Plug and Play
class ViewController: UIViewController, MyTableViewAdapterDelegate {
#IBOutlet var stateTableView: UITableView!
var myTableViewAdapter:MyTableViewAdapter!
override func viewDidLoad() {
super.viewDidLoad()
myTableViewAdapter = MyTableViewAdapter(stateTableView, self)
}
func myTableAdapter(_ adapter: MyTableViewAdapter, didSelect item: Any) {
print(item)
}
}
You are trying to set datasource and delegate of UITableView as UITableViewController. As #Ahmad mentioned its more understandable in same class i.e. ViewController, you can take clear approach separating datasource and delegate of UITableView from UIViewController. You can make subclass of NSObject preferably and use it as datasource and delgate class of your UITableView.
You can also also use a container view and embed a UITableViewController. All your table view code will move to your UITableViewController subclass.Hence seprating your table view logic from your View Controller
Hope it helps. Happy Coding!!
The way I separate those concerns in my projects, is by creating a class to keep track of the state of the app and do the required operations on data. This class is responsible for getting the actual data (either creating it hard-coded or getting it from the persistent store). This is a real example:
import Foundation
class CountriesStateController {
private var countries: [Country] = [
Country(name: "United States", visited: true),
Country(name: "United Kingdom", visited: false),
Country(name: "France", visited: false),
Country(name: "Italy", visited: false),
Country(name: "Spain", visited: false),
Country(name: "Russia", visited: false),
Country(name: "Moldova", visited: false),
Country(name: "Romania", visited: false)
]
func toggleVisitedCountry(at index: Int) {
guard index > -1, index < countries.count else {
fatalError("countryNameAt(index:) - Error: index out of bounds")
}
let country = countries[index]
country.visited = !country.visited
}
func numberOfCountries() -> Int {
return countries.count
}
func countryAt(index: Int) -> Country {
guard index > -1, index < countries.count else {
fatalError("countryNameAt(index:) - Error: index out of bounds")
}
return countries[index]
}
}
Then, I create separate classes that implement the UITableViewDataSource and UITableViewDelegate protocols:
import UIKit
class CountriesTableViewDataSource: NSObject {
let countriesStateController: CountriesStateController
let tableView: UITableView
init(stateController: CountriesStateController, tableView: UITableView) {
countriesStateController = stateController
self.tableView = tableView
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
super.init()
self.tableView.dataSource = self
}
}
extension CountriesTableViewDataSource: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return the number of items in the section(s)
return countriesStateController.numberOfCountries()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// return a cell of type UITableViewCell or another subclass
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
let country = countriesStateController.countryAt(index: indexPath.row)
let countryName = country.name
let visited = country.visited
cell.textLabel?.text = countryName
cell.accessoryType = visited ? .checkmark : .none
return cell
}
}
import UIKit
protocol CountryCellInteractionDelegate: NSObjectProtocol {
func didSelectCountry(at index: Int)
}
class CountriesTableViewDelegate: NSObject {
weak var interactionDelegate: CountryCellInteractionDelegate?
let countriesStateController: CountriesStateController
let tableView: UITableView
init(stateController: CountriesStateController, tableView: UITableView) {
countriesStateController = stateController
self.tableView = tableView
super.init()
self.tableView.delegate = self
}
}
extension CountriesTableViewDelegate: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected row at index: \(indexPath.row)")
tableView.deselectRow(at: indexPath, animated: false)
countriesStateController.toggleVisitedCountry(at: indexPath.row)
tableView.reloadRows(at: [indexPath], with: .none)
interactionDelegate?.didSelectCountry(at: indexPath.row)
}
}
And this is how easy is to use them from the ViewController class now:
import UIKit
class ViewController: UIViewController, CountryCellInteractionDelegate {
public var countriesStateController: CountriesStateController!
private var countriesTableViewDataSource: CountriesTableViewDataSource!
private var countriesTableViewDelegate: CountriesTableViewDelegate!
private lazy var countriesTableView: UITableView = createCountriesTableView()
func createCountriesTableView() -> UITableView {
let tableViewOrigin = CGPoint(x: 0, y: 0)
let tableViewSize = view.bounds.size
let tableViewFrame = CGRect(origin: tableViewOrigin, size: tableViewSize)
let tableView = UITableView(frame: tableViewFrame, style: .plain)
return tableView
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard countriesStateController != nil else {
fatalError("viewDidLoad() - Error: countriesStateController was not injected")
}
view.addSubview(countriesTableView)
configureCountriesTableViewDelegates()
}
func configureCountriesTableViewDelegates() {
countriesTableViewDataSource = CountriesTableViewDataSource(stateController: countriesStateController, tableView: countriesTableView)
countriesTableViewDelegate = CountriesTableViewDelegate(stateController: countriesStateController, tableView: countriesTableView)
countriesTableViewDelegate.interactionDelegate = self
}
func didSelectCountry(at index: Int) {
let country = countriesStateController.countryAt(index: index)
print("Selected country: \(country.name)")
}
}
Note that ViewController didn't create the countriesStateController object, so it must be injected. We can do that from the Flow Controller, from the Coordinator or Presenter, etc. I did it from AppDelegate like so:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let countriesStateController = CountriesStateController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if let viewController = window?.rootViewController as? ViewController {
viewController.countriesStateController = countriesStateController
}
return true
}
/* ... */
}
If it's never injected - we get a runt-time crash, so we know we must fix it straight away.
This is the Country class:
import Foundation
class Country {
var name: String
var visited: Bool
init(name: String, visited: Bool) {
self.name = name
self.visited = visited
}
}
Note how clean and slim the ViewController class is. It's less than 50 lines, and if create the table view from Interface Builder - it becomes 8-9 lines smaller.
ViewController above does what it's supposed to do, and that's to be a mediator between View and Model objects. It doesn't really care if the table displays one type or many types of cells, so the code to register the cell(s) belongs to CountriesTableViewDataSource class, which is responsible to create each cell as needed.
Some people combine CountriesTableViewDataSource and CountriesTableViewDelegate in one class, but I think it breaks the Single Responsibility Principle. Those two classes both need access to the same DataProvider / State Controller object, and ViewController needs access to that as well.
Note that View Controller had now way to know when didSelectRowAt was called, so we needed to create an additional protocol inside UITableViewDelegate:
protocol CountryCellInteractionDelegate: NSObjectProtocol {
func didSelectCountry(at index: Int)
}
And we also need a delegate property to make the communication possible:
weak var interactionDelegate: CountryCellInteractionDelegate?
Note that neither CountriesTableViewDataSource not CountriesTableViewDelegate class knows about the existence of the ViewController class. Using Protocol-Oriented-Programming - we could even remove the tight-coupling between those two classes and the CountriesStateController class.

is there a way of refreshing the whole UITableView through a button that is in one of the cells?

I have a dynamically generated UITableView with many dynamic UITableViewCells and one static UITableViewCell.
The static one has a button and I want to refresh the whole table view when user presses it.
My code attached to the cell is simple:
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
//from here I want to refresh the table
}
}
How can I refresh the parent table from that button? In the class MyStaticCell I don't have any instance of the table, so that's my problem for now :|
The cleanest way to do this is through delegation. This ensures that the cell class doesn't need to know what should happen when the button is pressed; that logic can remain in your view controller where it belongs.
protocol CommentButtonProtocol {
func commentButtonTapped(sender: MyStaticCell)
}
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
var delegate: CommentButtonProtocol?
#IBAction func sendCommentButtonAction(sender: AnyObject) {
self.delegate?.commentButtonTapped(self)
}
}
Then in your view controller you can set it as the delegate in cellForRowAtIndexPath and comply with the protocol in order to handle the event:
class ViewController: UIViewController, CommentButtonProtocol {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! MyStaticCell
cell.delegate = self
return cell
}
func commentButtonTapped(sender: MyStaticCell) {
// Do whatever you need to do when the button is tapped
}
}
You could access the tableView using superview.
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
(superview as? UITableView)?.reloadData()
}
}
This isn't as stable as it could be so maybe consider this extension:
extension UIResponder {
func nextResponder<T: UIResponder>(ofType type: T.Type) -> T? {
switch nextResponder() {
case let responder as T:
return responder
case let .Some(responder):
return responder.nextResponder(ofType: type)
default:
return nil
}
}
}
It allows you to find the next parent of a particular type, in the cells case, a UITableView.
class MyStaticCell: UITableViewCell {
#IBOutlet weak var sendCommentButton: UIButton!
#IBAction func sendCommentButtonAction(sender: AnyObject) {
nextResponder(ofType: UITableView.self)?.reloadData()
}
}

Custom TableView functions are not getting called?

On my main.Storyboard I have a TableViewController which is set to a custom class with TableViewController.swift.
The swift file has all the tableview functions defined and the #IBOutlet for the UITableView connected. The classes defined are UINavigationController,UITableViewDelegate. This viewController is called from a secondViewController via the prepareForSegue function.
I also created CustomCell.swift with class UITableViewCell and all #IBOutlet for the labels in my UITableViewCell which has been set to the customCell class.
I can't paste all my code but if you need to look at any specific code let me know and I will be happy to post that.
The Build succeeds and the app runs but the tableviewcells don't show up and none of the tableview functions are called. I see 2 flash animated screens - indicating that the tableviewcell might have 2 views - but can't figure out where I should be checking?
//Below is the segue function triggering the TableViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//print("In prepareswgue: ",segue, " ",sender)
if(segue.identifier == "resultSegue")
{
let nav = segue.destinationViewController as! UINavigationController
let svc = nav.topViewController as! TableViewController
svc.serialNo = self.TSSerialNoField.text
}
}
//Below is the custom TableVIewController class code
class TableViewController: UINavigationController,UITableViewDataSource, UITableViewDelegate
{
var serialNo:String!
var ashHardwareData: NSMutableArray!
#IBOutlet var ResultTableView: UITableView!
//#IBOutlet weak var LogCaseButton: UIButton!
#IBOutlet weak var TypeResultLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
self.ResultTableView?.allowsSelectionDuringEditing = true
self.ResultTableView?.delegate = self
ResultTableView?.dataSource = self
}
override func viewDidAppear(animated: Bool) {
self.getHardwareData(serialNo.uppercaseString)
}
/*override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
}*/
func getHardwareData(serialno:String)
{
ashHardwareData = NSMutableArray()
ashHardwareData = ModelManager.getInstance().getHardwareData(serialno)
ResultTableView?.reloadData()
}
//TableView Delegate Methods
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
print("In height func")
return 50
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(ashHardwareData.count)
return ashHardwareData.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:ResultsCell = tableView.dequeueReusableCellWithIdentifier("results", forIndexPath: indexPath) as! ResultsCell
let hardware:HardwareInfo = ashHardwareData.objectAtIndex(indexPath.row) as! HardwareInfo
let contract:ContractInfo = ashHardwareData.objectAtIndex(indexPath.row) as! ContractInfo
cell.SNOLabel.text = "Serial N0: \(hardware.SerialNo)"
cell.ContractIDLabel.text = "Contract ID: \(contract.ContractID)"
cell.OrgLabel.text = "Organisation: \(hardware.Organisation)"
cell.ModelLabel.text = "Model: \(hardware.Model)"
if(contract.DaystoExpiry > 0) {
cell.TypeLabel.text = "Contract Type: Active"
self.TypeResultLabel.hidden = false
self.TypeResultLabel.text = "To log a technical case for the Hardware please click on Log Technical Case button."
cell.LogCaseButton.hidden = false
cell.LogCaseButton.tag = indexPath.row
}
else {
cell.TypeLabel.text = "Contract Type: Expired"
cell.LogCaseButton.hidden = true
self.TypeResultLabel.hidden = false
self.TypeResultLabel.text = "Support Contract for the hardware expired. Please contact Sales team to renew the contract."
}
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
//here is the code for the ResultsCell custom UITableViewCell
import Foundation
import UIKit
class ResultsCell: UITableViewCell {
#IBOutlet weak var SNOLabel: UILabel!
#IBOutlet weak var LogCaseButton: UIButton!
#IBOutlet weak var ContractIDLabel: UILabel!
#IBOutlet weak var OrgLabel: UILabel!
#IBOutlet weak var TypeLabel: UILabel!
#IBOutlet weak var ModelLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
have you configured ResultsCell in story board or in view didload. check it once. if not try to add the following code into your view didload
[self.tableView registerNib:[UINib nibWithNibName:#"ResultsCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:#"ResultsCell"];
I have spent fair bit of my time trying to get this issue resolved and decided to go without the tableviewcell, instead I have created custom viewControllers and defined labels to display the results from the DB search (which is working OK) and used the perform segue function to pass values between viewcontrollers. So now my app works the way I want - maybe not ideal from a programming side but due to time constraints I had to get this working.
I have my app as a Tabbed Application with First and Second ViewControllers. The FirstViewController adds data to the SQLLite DB and the Second searches for the data and displays the results. for displaying the results I created a Custom viewController with labels for the data that I wanted displayed and passed all data from the DB results in SecondViewController functions to it and updated the labels with the data. As long as I get the results I wanted I am happy. I will re-visit this for improvement if I have to. Thanks to all who responded with solutions and suggestions. It has been a good learning experience :)

Resources