I have a tableView which have one two cells.
first for section header having 3 buttons, acting as a check box,and
second cell with simple labels to populate the data.
Now what I want is to update the tableView's second cells data with a section header like shown in screenshot below. But I'm unable to get the clickable action for these buttons on the same header.
What I tried so far is:
first I used tapReconizer for all three of them, it was working but it was not changing the image of the button (which is imp, as through image it is acting like a checkbox)
then I made the action outlet for all three now they are working as in but I'm unable to update data from the cell's custom class, below is the code
class SavedCallHeader : UITableViewCell{
var checkBox_PlannedisOn:Bool = false
var checkBox_BothisOn:Bool = true
var checkBox_unPlannedisOn:Bool = false
let defaults = UserDefaults.standard
#IBOutlet weak var PlannedBoxBtn: UIButton!
#IBOutlet weak var BothBoxBtn: UIButton!
#IBOutlet weak var unPlannedBoxBtn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func PlannedCheckBox(_ sender: UIButton) {
if checkBox_PlannedisOn == false {
self.PlannedBoxBtn.setImage(UIImage(named: "Checked Checkbox-26.png"), for: UIControlState.normal)
checkBox_PlannedisOn = true
print("i'm finally here proper click!")
// self.fetchData() // wont' work here as it is in the main VC Class
// tableView.reloadData() // won't work here as well
}else {
self.PlannedBoxBtn.setImage(UIImage(named: "Unchecked Checkbox-26.png"), for: UIControlState.normal)
print("i'm finally heress proper click!")
checkBox_PlannedisOn = false
}
}
I want to update and refresh data on every time the user select/deSelect the checkBox. Below is my main code:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifierHeader:String = "SavedCallHeader"
let headerCell = tableView.dequeueReusableCell(withIdentifier: identifierHeader) as! SavedCallHeader
/* let tapPlanned = UITapGestureRecognizer(target: self, action: #selector(self.respondToSwipeGestureP))
let tapBoth = UITapGestureRecognizer(target: self, action: #selector(self.respondToSwipeGestureB))
let tapUnPlanned = UITapGestureRecognizer(target: self, action: #selector(self.respondToSwipeGestureU))
headerCell.PlannedBoxBtn.addGestureRecognizer(tapPlanned)
headerCell.BothBoxBtn.addGestureRecognizer(tapBoth)
headerCell.unPlannedBoxBtn.addGestureRecognizer(tapUnPlanned)
*/
return headerCell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier:String = "savedcall_cell"
let cell:SavedCalls_Cell = self.tableView.dequeueReusableCell(withIdentifier:identifier ) as! SavedCalls_Cell!
if(drname.count > 0){
//Fetching data from table view and then reload
}
Creating callBack like this in your cell class
var callBackForReload : ((Bool) -> ())?
#IBAction func PlannedCheckBox(_ sender: UIButton) {
// when you call this call back it will excute in where you acces it.
// I pass bool value for examble. you will pass whtever datatype you want
self.callBackForReload!(true)
}
The below code execute after CallBack code executed in your cell class
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let identifierHeader:String = "SavedCallHeader"
let headerCell = tableView.dequeueReusableCell(withIdentifier: identifierHeader) as! SavedCallHeader
headerCell.callBackForReload = { [weak self] (isCalled) -> Void in
//This will call when the call back code excuted in your cell class
// in The isCalled variable you will get the value from cell class
// You will reload your changed value here
}
return headerCell
}
You have to do below things,
Instead of UITableViewCell, you have to take Button Outlets in ViewController.
When User clicked on any cell you can get on which button and cell user clicked. Ref
Now reload that cell/section as user clicked. Ref
Related
I want to implement UITableView Where I want to have 3 buttons in each UITableViewCell. I want to perform a diffeent action for each button. How can I identify which button is bring pressed and then get the object(index row ) of the cell that was selected?
UIViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let show=shows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ShowCell") as!
ShowCell
cell.setShow(show: show)
return cell
}
UITableViewCell
#IBOutlet weak var graphButton: FlatButton!
#IBOutlet weak var buyButton: FlatButton!
#IBOutlet weak var reviewButton: FlatButton!
func setShow(show :StubHubEvent ){
let url = URL(string: show.imageurl)!
showImageView.af_setImage(withURL: url)
showImageView.contentMode = .scaleAspectFill
showImageView.clipsToBounds = true
nameLabel.text = show.title
dateLabel.text = show.time
implement your button action in UIviewcontroller not a UITableViewCell, create the target in inside the cellforRow as well as add the Tag for each target for identify which button was user pressed.for E.g
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let show=shows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ShowCell") as!
ShowCell
cell.graphButton.tag = indexPath.row
cell.buyButton.tag = indexPath.row
cell.reviewButton.tag = indexPath.row
cell.graphButton?.addTarget(self, action: #selector(self.graphButtonClicked(_:)), for: .touchUpInside)
cell.buyButton?.addTarget(self, action: #selector(self.buyButtonClicked(_:)), for: .touchUpInside)
cell.reviewButton?.addTarget(self, action: #selector(self.reviewButtonClicked(_:)), for: .touchUpInside)
cell.setShow(show: show)
return cell
}
and handle the action as like
#objc func buyButton( _ sender: UIButton) {
print("buyButton Action Found the index of \(sender.tag)")
}
#objc func graphButtonClicked( _ sender: UIButton) {
print("graphButtonClicked Action Found the index of \(sender.tag)")
}
#objc func reviewButtonClicked( _ sender: UIButton) {
print("reviewButtonClicked Action Found the index of \(sender.tag)")
}
Option 2
if you want to perform in your button action in UItableviewcell class using delegate pattern, then refer this duplicate answer
there are two ways to get button click execution in the ViewController from TableViewCell
Use Delegate pattern
Use blocks as callbacks and handle block execution in the cellForRow method
Add addTarget(:) to add a target method for the button click
Details:
The first approach is best among all the three mentioned approaches, in this, you need to create a delegate which redirects your user actions from cell to view controller. Check below code example.
The second approach is similar to the first one, it just redirects the same method calls using the blocks instead of Delegate methods and protocol.
The third approach is not good, as it is tightly coupled with the indexPath.row value, in the software development industry
Cohesion should be high, Coupling should be low.
Code of first Approach:
//MARK:- Model - StubHubEvent
class StubHubEvent {
//you model class implementation
}
//MARK:- Protocol - ShowCellUIInteractionDelegate - used to redirect user actions from cell to viewController
protocol ShowCellUIInteractionDelegate: AnyObject {
func showCell(cell: ShowCell, didTapBuyFor show: StubHubEvent)
func showCell(cell: ShowCell, didTapGraphFor show: StubHubEvent)
func showCell(cell: ShowCell, didTapReviewFor show: StubHubEvent)
}
//MARK:- Cell- ShowCell
class ShowCell: UITableViewCell {
var show: StubHubEvent!
weak var delegateUIInteraction: ShowCellUIInteractionDelegate?
func setShow(show :StubHubEvent ){
self.show = show
//your other setup
}
//Bind these three action from cell to buttons as a .touchUpInside event
#IBAction func buttonBuyDidTap( _ sender: UIButton) {
self.delegateUIInteraction?.showCell(cell: self, didTapBuyFor: self.show)
}
#IBAction func buttonGraphDidTap( _ sender: UIButton) {
self.delegateUIInteraction?.showCell(cell: self, didTapGraphFor: self.show)
}
#IBAction func buttonReviewDidTap( _ sender: UIButton) {
self.delegateUIInteraction?.showCell(cell: self, didTapReviewFor: self.show)
}
}
//MARK:- ViewController - ShowListingViewController
class ShowListingViewController: UIViewController {
//you ShowListingViewController implementation
}
//MARK:- Extension - ShowCellUIInteractionDelegate
extension ShowListingViewController: ShowCellUIInteractionDelegate {
//execute your logic for the show model object
func showCell(cell: ShowCell, didTapBuyFor show: StubHubEvent){
}
func showCell(cell: ShowCell, didTapGraphFor show: StubHubEvent){
}
func showCell(cell: ShowCell, didTapReviewFor show: StubHubEvent){
}
}
I have a button and a label in a table view (I am using 8 rows )and for some reason when I click the first button I get indexPath nil error, but when I click the second button (2nd row) I get the first row label. When I click the 3rd row button, I get the second row label etc. Why are they misaligned. I want when I click the first row button to get the first row label etc. Please see the code below. Thank you !!
#objc func btnAction(_ sender: AnyObject) {
var position: CGPoint = sender.convert(.zero, to: self.table)
print (position)
let indexPath = self.table.indexPathForRow(at: position)
print (indexPath?.row)
let cell: UITableViewCell = table.cellForRow(at: indexPath!)! as
UITableViewCell
print (indexPath?.row)
print (currentAnimalArray[(indexPath?.row)!].name)
GlobalVariable.addedExercises.append(currentAnimalArray[(indexPath?.row)!].name)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? TableCell else {return UITableViewCell() }
// print(indexPath)
cell.nameLbl.text=currentAnimalArray[indexPath.row].name
// print("\(#function) --- section = \(indexPath.section), row = \(indexPath.row)")
// print (currentAnimalArray[indexPath.row].name)
cell.b.tag = indexPath.row
// print (indexPath.row)
cell.b.addTarget(self, action: #selector(SecondVC.btnAction(_:)), for: .touchUpInside)
return cell
}
Frame math is a worst-case scenario if you have no choice. Here you have a lot of choices.
For example why don't you use the tag you assigned to the button?
#objc func btnAction(_ sender: UIButton) {
GlobalVariable.addedExercises.append(currentAnimalArray[sender.tag].name)
}
A swiftier and more efficient solution is a callback closure:
In TableCell add the button action and a callback property. The outlet is not needed. Disconnect the outlet and connect the button to the action in Interface Builder. When the button is tapped the callback is called.
class TableCell: UITableViewCell {
// #IBOutlet var b : UIButton!
#IBOutlet var nameLbl : UILabel!
var callback : (()->())?
#IBAction func btnAction(_ sender: UIButton) {
callback?()
}
}
Remove the button action in the controller.
In cellForRow assign a closure to the callback property
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// no guard, the code must not crash. If it does you made a design mistake
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableCell
let animal = currentAnimalArray[indexPath.row]
cell.nameLbl.text = animal.name
cell.callback = {
GlobalVariable.addedExercises.append(animal.name)
}
return cell
}
You see the index path is actually not needed at all. The animal object is captured in the closure.
You already pass indexPath.row with button tag. Use the tag as index simply
#objc func btnAction(_ sender: UIButton) {
GlobalVariable.addedExercises.append(currentAnimalArray[sender.tag].name)
}
I want to create multiple choice question app in which i want to display four options with one selectable answer using radio button but I am not able to understand how to do it using array , Any help will be appreciated
!!!
I herreby attached my code --
import UIKit
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var topicLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var dictionary1 = [Int:String]()
var dictionary2 = [Int:Array<String>]()
override func viewDidLoad() {
dictionary1 = [0:"Whether you have experienced Pricking-pain, Desquamation,itching or dry skin sensation during seasonal alternate.", 1:"Whether your skin apt to flush( Redness) in hot humid environment ", 2:"Whether your skin has multiple disernible dilated capillaries.", 3:"whether you have once been diagnosed atopic dermatitis or seborrheic dermatitis."]
dictionary2 = [0:["Never","Seldom","Usually","Always"],1:["Never","Seldom","Usually","Always"],2:["Never","Seldom","Usually","Always"],3:["Yes", "No"]]
titleLabel.text = "Fill Skin Type Survey Form "
titleLabel.textColor = UIColor.black
topicLabel.text = "Are You with sensitive skin type ?"
topicLabel.font = UIFont.boldSystemFont(ofSize: 18)
let homeNib = UINib(nibName: "DemoTableViewCell", bundle: nil)
self.tableView.register(homeNib, forCellReuseIdentifier: "DemoTableViewCell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dictionary1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:DemoTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "DemoTableViewCell", for: indexPath) as! DemoTableViewCell
// FOR FIRST DICTIONARY
cell.questionLabel.text = dictionary1[indexPath.row]
cell.questionLabel.font = UIFont.boldSystemFont(ofSize: 16)
// FOR SECOND DICTIONARY
cell.optionsLabel.text = dictionary2[indexPath.row]?.joined(separator: " ")
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 350.0
I want to display dictionary 2 along with radio button for selecting an option , here is screenshot of my expected output
You can get the support from so many GitHub Libraries:
https://github.com/DavydLiu/DLRadioButton
https://github.com/onegray/RadioButton-ios (No longer actively maintained)
https://github.com/alhazmy13/RadioButtonSwift3
https://github.com/xxi511/radioButton-swift
https://github.com/VenkateshYadavP/PVRadioButton
https://github.com/thegoal/ISRadioButton
Or else if u want to do that programmatically using UIButton lemme know i can share the code with you.
#IBAction func btnRadioCategoryClicked(_ sender: UIButton) {
for button in btnALLTerritory {
if sender.tag == button.tag{
button.isSelected = true;
button.setImage(#imageLiteral(resourceName: "ic_Radio_filled"), for: .normal)
}else{
button.isSelected = false;
button.setImage(#imageLiteral(resourceName: "ic_Radio_Empty"), for: .normal)
}
}
}
You need to take the Group Outlets of all your UIButton and make some logic like this if you prefer to do that programmatically. Or also you can make an array to store the id of Selected button tag. And use like to select and Unselect a UIButton with that logic
Hope this help.
Simple Demo for Radio Button
You can download the demo code from here Demo Of Radio Button
By referring Abhirajsinh solution, I have created two buttons in interface and connected to controller. Make sure that you have changes tag value in attribute for button1(Tag = 0) and button2(Tag = 1). Also remove selected images in interface for each button.
class AddWorkExperienceViewController: UIViewController {
// MARK: - IBOutlets
var btnALLTerritory = [UIButton]()
#IBOutlet weak var doButton: UIButton!
#IBOutlet weak var dontButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
btnALLTerritory = [doButton,dontButton]
}
// MARK: - Actions
#IBAction func btnRadioCategoryClicked(_ sender: UIButton) {
for button in btnALLTerritory {
if sender.tag == button.tag{
button.isSelected = true;
button.setImage(#imageLiteral(resourceName: "radioButton-selected"), for: .normal)
}else{
button.isSelected = false;
button.setImage(#imageLiteral(resourceName: "radioButton-unselected"), for: .normal)
}
}
}
}
If you are using tableview for options then save the state of the each cell radio button in Model Array. Once use click on radio button, change the state of radio button and reload table view. Custom UI will be the best way to implement.
Still very much a Swift noob, I have been looking around for a proper way/best practice to manage row deletions in my UITableView (which uses custom UserCells) based on tapping a UIButton inside the UserCell using delegation which seems to be the cleanest way to do it.
I followed this example: UITableViewCell Buttons with action
What I have
UserCell class
protocol UserCellDelegate {
func didPressButton(_ tag: Int)
}
class UserCell: UITableViewCell {
var delegate: UserCellDelegate?
let addButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add +", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
addSubview(addButton)
addButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -6).isActive = true
addButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
addButton.heightAnchor.constraint(equalToConstant: self.frame.height / 2).isActive = true
addButton.widthAnchor.constraint(equalToConstant: self.frame.width / 6).isActive = true
}
func buttonPressed(_ sender: UIButton) {
delegate?.didPressButton(sender.tag)
}
}
TableViewController class:
class AddFriendsScreenController: UITableViewController, UserCellDelegate {
let cellId = "cellId"
var users = [User]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
cell.delegate = self
cell.tag = indexPath.row
return cell
}
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
where the Users in users are appended with a call to the database in the view controller.
My issues
The button in each row of the Table View is clickable but does not do anything
The button seems to be clickable only when doing a "long press", i.e. finger stays on it for a ~0.5s time
Will this method guarantee that the indexPath is updated and will not fall out of scope ? I.e. if a row is deleted at index 0, will deleting the "new" row at index 0 work correctly or will this delete the row at index 1 ?
What I want
Being able to click the button in each row of the table, which would remove it from the tableview.
I must be getting something rather basic wrong and would really appreciate if a Swift knight could enlighten me.
Many thanks in advance.
There are at least 3 issues in your code:
In UserCell you should call:
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
once your cell has been instantiated (say, from your implementation of init(style:reuseIdentifier:)) so that self refers to an actual instance of UserCell.
In AddFriendsScreenController's tableView(_:cellForRowAt:) you are setting the tag of the cell itself (cell.tag = indexPath.row) but in your UserCell's buttonPressed(_:) you are using the tag of the button. You should modify that function to be:
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
delegate?.didPressButton(self.tag)
}
As you guessed and as per Prema Janoti's answer you ought to reload you table view once you deleted a row as your cells' tags will be out of sync with their referring indexPaths. Ideally you should avoid relying on index paths to identify cells but that's another subject.
EDIT:
A simple solution to avoid tags being out of sync with index paths is to associate each cell with the User object they are supposed to represent:
First add a user property to your UserCell class:
class UserCell: UITableViewCell {
var user = User() // default with a dummy user
/* (...) */
}
Set this property to the correct User object from within tableView(_:cellForRowAt:):
//cell.tag = indexPath.row
cell.user = self.users[indexPath.row]
Modify the signature of your UserCellDelegate protocol method to pass the user property stored against the cell instead of its tag:
protocol UserCellDelegate {
//func didPressButton(_ tag: Int)
func didPressButtonFor(_ user: User)
}
Amend UserCell's buttonPressed(_:) action accordingly:
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
//delegate?.didPressButton(self.tag)
delegate?.didPressButtonFor(self.user)
}
Finally, in your AddFriendsScreenController, identify the right row to delete based on the User position in the data source:
//func didPressButton(_ tag: Int) { /* (...) */ } // Scrap this.
func didPressButtonFor(_ user: User) {
if let index = users.index(where: { $0 === user }) {
let indexPath = IndexPath(row: index, section: 0)
users.remove(at: index)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Note the if let index = ... construct (optional binding) and the triple === (identity operator).
This downside of this approach is that it will create tight coupling between your User and UserCell classes. Best practice would dictate using a more complex MVVM pattern for example, but that really is another subject...
There is a lot of bad/old code on the web, even on SO. What you posted has "bad practice" written all over it. So first a few pointers:
Avoid an UITableViewController at all cost. Have a normal view controller with a table view on it
Delegates should always be weak unless you are 100% sure what you are doing
Be more specific when naming protocols and protocol methods
Keep everything private if possible, if not then use fileprivate. Only use the rest if you are 100% sure it is a value you want to expose.
Avoid using tags at all cost
The following is an example of responsible table view with a single cell type which has a button that removes the current cell when pressed. The whole code can be pasted into your initial ViewController file when creating a new project. In storyboard a table view is added constraint left, right, top, bottom and an outlet to the view controller. Also a cell is added in the table view with a button in it that has an outlet to the cell MyTableViewCell and its identifier is set to "MyTableViewCell".
The rest should be explained in the comments.
class ViewController: UIViewController {
#IBOutlet private weak var tableView: UITableView? // By default use private and optional. Always. For all outlets. Only expose it if you really need it outside
fileprivate var myItems: [String]? // Use any objects you need.
override func viewDidLoad() {
super.viewDidLoad()
// Attach table viw to self
tableView?.delegate = self
tableView?.dataSource = self
// First refresh and reload the data
refreshFromData() // This is to ensure no defaults are visible in the beginning
reloadData()
}
private func reloadData() {
myItems = nil
// Simulate a data fetch
let queue = DispatchQueue(label: "test") // Just for the async example
queue.async {
let items: [String] = (1...100).flatMap { "Item: \($0)" } // Just generate some string
Thread.sleep(forTimeInterval: 3.0) // Wait 3 seconds
DispatchQueue.main.async { // Go back to main thread
self.myItems = items // Assign data source to self
self.refreshFromData() // Now refresh the table view
}
}
}
private func refreshFromData() {
tableView?.reloadData()
tableView?.isHidden = myItems == nil
// Add other stuff that need updating here if needed
}
/// Will remove an item from the data source and update the array
///
/// - Parameter item: The item to remove
fileprivate func removeItem(item: String) {
if let index = myItems?.index(of: item) { // Get the index of the object
tableView?.beginUpdates() // Begin updates so the table view saves the current state
myItems = myItems?.filter { $0 != item } // Update our data source first
tableView?.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) // Do the table view cell modifications
tableView?.endUpdates() // Commit the modifications
}
}
}
// MARK: - UITableViewDelegate, UITableViewDataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myItems?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell {
cell.item = myItems?[indexPath.row]
cell.delegate = self
return cell
} else {
return UITableViewCell()
}
}
}
// MARK: - MyTableViewCellDelegate
extension ViewController: MyTableViewCellDelegate {
func myTableViewCell(pressedMainButton sender: MyTableViewCell) {
guard let item = sender.item else {
return
}
// Delete the item if main button is pressed
removeItem(item: item)
}
}
protocol MyTableViewCellDelegate: class { // We need ": class" so the delegate can be marked as weak
/// Called on main button pressed
///
/// - Parameter sender: The sender cell
func myTableViewCell(pressedMainButton sender: MyTableViewCell)
}
class MyTableViewCell: UITableViewCell {
#IBOutlet private weak var button: UIButton?
weak var delegate: MyTableViewCellDelegate? // Must be weak or we can have a retain cycle and create a memory leak
var item: String? {
didSet {
button?.setTitle(item, for: .normal)
}
}
#IBAction private func buttonPressed(_ sender: Any) {
delegate?.myTableViewCell(pressedMainButton: self)
}
}
In your case the String should be replaced by the User. Next to that you will have a few changes such as the didSet in the cell (button?.setTitle(item.name, for: .normal) for instance) and the filter method should use === or compare some id or something.
try this -
update didPressButton method like below -
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.reloadData()
}
I have a UISwitch in a tableviewcontroller, and when the switch is toggled I want it to change the value of a boolean variable in an array I created inside the view controller, that the cell is related to. Kind of like the Stock Alarm App on IOS, where each cell has a UISwitch, and toggling the switch will turn off each individual alarm. So with the UISwitch, with its selector code, this is inside the cellForRowAtIndexPath method
//switch
let lightSwitch = UISwitch(frame: CGRectZero) as UISwitch
lightSwitch.on = false
lightSwitch.addTarget(self, action: #selector(switchTriggered), forControlEvents: .ValueChanged)
//lightSwitch.addTarget(self, action: "switchTriggered", forControlEvents: .ValueChanged )
cell.accessoryView = lightSwitch
I want it to do this
func switchTriggered(a: Int) {
changeValueOfArray = array[indexPath.row]
}
I don't have the code written for that part yet, but my question is, How can i let the switchTriggered function see the indexPath.row value, without passing it as an argument to the function because I can't because its a selector?
let lightSwitch = UISwitch(frame: CGRectZero) as UISwitch
lightSwitch.on = false
lightSwitch.addTarget(self, action: #selector(switchTriggered), forControlEvents: .ValueChanged)
lightSwitch.tag = indexpath.row
cell.accessoryView = lightSwitch
Let save your boolean value in Array
func switchTriggered(sender: UISwitch) {
sender.on ? array[sender.tag]=1 : array[sender.tag]=0
}
}
The basic idea is that you can capture the cell for which the switch was flipped and then use tableView.indexPath(for:) to translate that UITableViewCell reference into a NSIndexPath, and you can use its row to identify which row in your model structure needs to be updated.
The constituent elements of this consist of:
Create a model object that captures the information to be shown in the table view. For example, let's imagine that every cell contains a name of a Room and a boolean reflecting whether the light is on:
struct Room {
var name: String
var lightsOn: Bool
}
Then the table view controller would have an array of those:
var rooms: [Room]!
I'd define a UITableViewCell subclass with outlets for the label and the switch. I'd also hook up the "value changed" for the light switch to a method in that cell. I'd also set up a protocol for the cell to inform its table view controller that the light switch was flipped:
protocol RoomLightDelegate: class {
func didFlipSwitch(for cell: UITableViewCell, value: Bool)
}
class RoomCell: UITableViewCell {
weak var delegate: RoomLightDelegate?
#IBOutlet weak var roomNameLabel: UILabel!
#IBOutlet weak var lightSwitch: UISwitch!
#IBAction func didChangeValue(_ sender: UISwitch) {
delegate?.didFlipSwitch(for: self, value: sender.isOn)
}
}
I'd obviously set the base class for the cell prototype to be this UITableViewCell subclass and hook up the #IBOutlet references as well as the #IBAction for the changing of the value for the switch.
I'd then have the UITableViewDataSource methods populate the cell on the basis of the Room properties:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rooms.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SwitchCell", for: indexPath) as! RoomCell
let room = rooms[indexPath.row]
cell.roomNameLabel.text = room.name
cell.lightSwitch.setOn(room.lightsOn, animated: false)
cell.delegate = self
return cell
}
Note, the above cellForRowAtIndexPath also specifies itself as the delegate for the cell, so we'd want to implement the RoomLightDelegate protocol to update our model when the light switch is flipped:
extension ViewController: RoomLightDelegate {
func didFlipSwitch(for cell: UITableViewCell, value: Bool) {
if let indexPath = tableView.indexPath(for: cell) {
rooms[indexPath.row].lightsOn = value
}
}
}
Now, I don't want you to worry about the details of the above. Instead, try to capture some of the basic ideas:
Bottom line, to your immediate question, once you know which cell was was updated, you can inquire with the UITableView to determine what NSIndexPath that UITableViewCell reference corresponds to, using tableView.indexPath(for:).
Swift 3 Update:
let lightSwitch = UISwitch(frame: CGRect.zero) as UISwitch
lightSwitch.isOn = false
lightSwitch.addTarget(self, action: #selector(switchTriggered), for: .valueChanged)
lightSwitch.tag = indexPath.row
cell?.accessoryView = lightSwitch