I am relatively new to coding in Swift and I'm updating an app that is currently in the app store. I am currently creating an array for contents in a cell for a tableView that I made in a xib file. Here is what it looks like:
`struct callData {
let cell : Int?
let hotlineName : String?
let phoneNumber : String?
let callBtn : UIButton?
let iconImg : UIImage?
init(cell:Int,hotlineName:String,phoneNumber:String, callBtn:UIButton, iconImg:UIImage) {
self.cell = cell
self.hotlineName = hotlineName
self.phoneNumber = phoneNumber
self.callBtn = callBtn
self.iconImg = iconImg
}
}
class _ViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var arrayOfCallData = [callData(
cell: 0,
hotlineName:"",
phoneNumber:"",
callBtn:"",
iconImg: #imageLiteral(resourceName: "smartphone")
)]
`
I'm not sure how to insert a button (callBtn) into an array (arrayOfCallData) without it providing plenty of errors. The purpose of it is to call the number from within the string from the app but I'm not sure how to implement an action for the button to call.
here is an example of the code for calling from within the app:
let url = NSURL(string: "tel://8004424673")!
UIApplication.shared.open(url as URL)
I want to be able to incorporate this into the array (callBtn) so that I can create multiple buttons that can call different numbers.
In your callData, save your phone number as callBtnAction and you can fetch it back in selector method.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cellIdendifier: String = "CallDataCell"
let cellData = arrayOfCallData[indexPath.row]
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdendifier, for: indexPath)
let button = UIButton.init()
button.frame = CGRect.zero //Set your frame here
button.setTitle(cellData.callBtnText, for: UIControlState.normal)
button.tag = indexPath.row
button.addTarget(self, action: Selector(("buttonClicked:")), for: UIControlEvents.touchUpInside)
cell.addSubview(button)
return cell
}
func buttonClicked(sender:UIButton) {
let selectedRow = sender.tag
let callData = arrayOfCallData[selectedRow]
let action = callData.callBtnAction
print(action)
}
The cells should own the views (e.g. buttons) and your data should be used to decorate those views. So if you remove the UI concerns from your data e.g. replacing the UIButton reference with a button title string, you can apply that data to the cell views in the cellForRowAt method.
Related
In the code below I'm populating my table with some data. The switches are off which they don't have to be. In the storyboard I defined it as On.
Cell:
var switchHandler: ((Bool)->Void)?
#IBAction func switchChanged(_ sender: UISwitch) {
self.switchHandler?(sender.isOn)
}
View controller:
var selectedCells = Set<IndexPath>()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell") as? SmsTableViewCell
cell?.PhonNumberLbl.text = data![indexPath.section].contacts[indexPath.row]?.phoneNumber
cell?.NameLbl.text = data![indexPath.section].contacts[indexPath.row]?.name
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
cell?.selectedTF.isOn = self.selectedCells.contains(indexPath)
cell?.switchHandler = { (switchState) in
if switchState {
self.selectedCells.insert(indexPath)
} else {
self.selectedCells.remove(indexPath)
}
}
return cell!
}
Model:
typealias smsModelList = [SmsModel]
struct SmsModel:Codable {
var unitNo:Int?
var unitPlaque:String?
var billText:String?
var contacts:[ContactsModel?]
}
typealias contactlistmodel = [ContactsModel]
struct ContactsModel:Codable
{
var id :Int?
var selected :Bool?
var phoneNumber : String?
var name : String?
}
Does anybody see somthing wrong which turns off the switch?
First of all as you force unwrap the cell anyway do it in the dequeue line to avoid the unnecessary amount of question marks and use the API to return a non-optional cell
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell", for: indexPath) as! SmsTableViewCell
To fix your issue update the selected property of the ContactsModel struct directly and forget the extra selectedCells array. Further declare – at least – selected as non-optional, practically there is no maybe state. And declare also all data source arrays (data / contacts) as non-optional, cellForRow is called only if there is an item at the particular indexPath by default.
struct ContactsModel : Codable {
...
var selected : Bool
...
}
...
let cell = tableView.dequeueReusableCell(withIdentifier: "SmsCell", for: IndexPath) as! SmsTableViewCell
let contact = data[indexPath.section].contacts[indexPath.row]
cell.PhonNumberLbl.text = contact.phoneNumber
cell.NameLbl.text = contact.name
cell.selectedTF.isOn = contact.selected
cell.switchHandler = { [unowned self] switchState in
// as the structs are value types you have to specify the full reference to the data source array
self.data[indexPath.section].contacts[indexPath.row].selected = switchState
}
Consider to use classes rather than structs in this case then you can shorten the closure
cell.switchHandler = { switchState in
contact.selected = switchState
}
You use both
cell?.selectedTF.isOn = (data![indexPath.section].contacts[indexPath.row]?.selected)!
cell?.selectedTF.isOn = self.selectedCells.contains(indexPath)
so isOn property of the switch is controlled from 2 sides , so you have to decide which line that should be commnented , plus don't depend on storyboard prototype cell setup as because of cell reusing it' ll be changed , if you want to make them all on by default then change the var selectedCells to contain all possible indexPaths and comment the other one
I have a UITableView that has sections (Category0, Category1,..), and every row of a specific section is a UITableView that has one section which is the question (Question1,..) and rows which are the options to be answered (option1, option2,..).
The problem is when I click on a button in a specific category and a specific question (Category0, question1, option0) see screenshot1,
immediately another buttons in another categories are clicked (Category1, question2, option0) see screenshot2,
and (Category4, question1, option0) see screenshot3.
the code below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? insideTableViewCell
cell?.answerlabel.text = "option \(indexPath.row)"
cell?.initCellItem(id: (myObject?.id)! , answer: (myObject?.answerArray![indexPath.row] as? String)!)
return cell!
}
In a custom UITableViewCell which is insideTableViewCell:
func initCellItem(id: Int , answer: String) {
radioButton.setImage( imageLiteral(resourceName: "unchecked"), for: .normal)
radioButton.setImage( imageLiteral(resourceName: "checked"), for: .selected)
radioButton.tag = id
radioButton.setTitle(answer, for: UIControlState.disabled)
radioButton.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
}
#objc func radioButtonTapped(_ radioButton: UIButton) {
print(radioButton.tag)
print(radioButton.title(for: UIControlState.disabled) as Any)
let answer = radioButton.title(for: UIControlState.disabled) as Any
let StrId = String(radioButton.tag)
defaults.set(answer, forKey: StrId)
let isSelected = !self.radioButton.isSelected
self.radioButton.isSelected = isSelected
if isSelected {
deselectOtherButton()
}
}
func deselectOtherButton() {
let tableView = self.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
let section = tappedCellIndexPath.section
let rowCounts = tableView.numberOfRows(inSection: section)
for row in 0..<rowCounts {
if row != tappedCellIndexPath.row {
let cell = tableView.cellForRow(at: IndexPath(row: row, section: section)) as! insideTableViewCell
cell.radioButton.isSelected = false
}
}
}
You haven't posted code still guessing.
You can create model object like
class QuestionData {
var strQuestion:String? // This may contains Question
var strOptions:[String]? // It may contains options titles of your buttons
var selectedAnswerIndex:Int? // When any button tapped
}
And you should create category models like
class Categories {
var categoryTitle:String?
var questions:[QuestionData] = []
}
you can use this Categories class as main source of your dataSource array
var arrayDataSource = [Categories]()
And fill this with your original data.
now whenever any button tapped you can use selectedAnswerIndex:Int to store current selected option for question. and if it is null then user has not selected any option yet.
I have created class so it is reference type you can directly set the value without worry
Hope it is helpful to you
There has some and simple code I think it will help you :- if it is not sutable for you pls don't mind :-
if (!btnGreen3.isSelected)
{
btnGreen3.isSelected = !btnGreen3.isSelected
}
btnBlue3.isSelected = false
btnBlack3.isSelected = false
You need to save the states of every cell.
The reason is you are using dequereuseable cell with identifier when you scroll it switch to another cell.
So make Array or Dictionary where save the state of every selected and unselected Rows.
I am implementing a Question&Answer program in Swift, using Firebase. I want to have a like button in my tableViewCell. However, I am having problem because the post's data is in the tableView class and I can make changes on the like button in tableViewCell class. I need a data transfer between these two. Can anyone help me with this?
If it is going to help you to understand my problem, code in my table view:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let answerCell = tableView.dequeueReusableCell(withIdentifier: "AnswerCell") as! AnswerCell
let answer = answers[indexPath.row]
answerCell.answerText.text = answer.answerText
answerCell.username.text = "~ \(answer.username)"
answerCell.numberOfLikes.text = "\(answer.numberOflikes) liked this answer."
answerCell.answerText.numberOfLines = 0
return answerCell
}
I want to have a code like this in my table view cell. However, I cannot access the answer object's data.
#IBAction func likeButtonClicked(_ sender: UIButton) {
ref = Database.database().reference()
ref.child("answerLikes").child((answer.id).observeSingleEvent(of: .value, with: {
(snapshot) in
if let _ = snapshot.value as? NSNull {
sender.setImage(UIImage(named: "filledHeart.png"), for: .normal)
} else {
answerLikes = [Auth.auth().currentUser?.uid : true]
ref.child("answerLikes").child(answer.id).updateChildValues(answerLikes)
sender.setImage(UIImage(named: "emptyHeart.png"), for: .normal)
}
})
}
UPDATED
This is solution where you asked "I cannot access the answer object's data"
You can create a method in answerCell which receive an answer object.
func recieveAnswerObject(answer : Answer){
// here you will get answer object
// here i am assuming Answer is your model class
}
// now time to call it from your main UIViewController class to send answer object
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let answerCell = tableView.dequeueReusableCell(withIdentifier: "AnswerCell") as! AnswerCell
let answer = answers[indexPath.row]
// from here we send answer object
answerCell.recieveAnswerObject(answer)
answerCell.answerText.text = answer.answerText
answerCell.username.text = "~ \(answer.username)"
answerCell.numberOfLikes.text = "\(answer.numberOflikes) liked this answer."
answerCell.answerText.numberOfLines = 0
return answerCell
}
Now for the part where you asked how to data transfer between these two class
First way to achieve this
1.Make a delegate in AnswerCell class and confirm it to your Controller where you have written your tableView code.
2.When button is clicked fire a delegate and you will get callback in your mainViewController.
Second way to achieve this
1.Make an IBOulet for likeButtonClicked in AnswerCell class.
2.Dont't make IBAction.
3.Your code will look something like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let answerCell = tableView.dequeueReusableCell(withIdentifier: "AnswerCell") as! AnswerCell
// action added to button
answerCell.likeButtonClicked.addTarget(self, action: #selector(likeButtonClicked), for: .touchUpInside)
// updated set tag to button
answerCell.likeButtonClicked.tag = indexPath.row
let answer = answers[indexPath.row]
answerCell.answerText.text = answer.answerText
answerCell.username.text = "~ \(answer.username)"
answerCell.numberOfLikes.text = "\(answer.numberOflikes) liked this answer."
answerCell.answerText.numberOfLines = 0
return answerCell
}
and write your below code in your tableview class
#IBAction func likeButtonClicked(_ sender: UIButton) {
//updated now tag is row position of button in cell and is equal to (indexPath.row) of that particular button.tag is equal to
let tag = sender.tag
// here indexPath.row can be replace by tag so
//let answer = answers[indexPath.row] == let answer = answers[tag]
// updated till here
ref = Database.database().reference()
ref.child("answerLikes").child((answer.id).observeSingleEvent(of: .value, with: {
(snapshot) in
if let _ = snapshot.value as? NSNull {
sender.setImage(UIImage(named: "filledHeart.png"), for: .normal)
} else {
answerLikes = [Auth.auth().currentUser?.uid : true]
ref.child("answerLikes").child(answer.id).updateChildValues(answerLikes)
sender.setImage(UIImage(named: "emptyHeart.png"), for: .normal)
}
})
}
I have a UIButton inside a UITableViewCell where the image changes once the button is tapped. Though the selected buttons get selected as intended, once the UITableView scrolls, the selected images disappear since the cells are reused.
I'm having trouble writing the logic. Please help.
My code is below, in Swift 3.
CellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
//Button_Action
addSongButtonIdentifier(cell: cell, indexPath.row)
}
This is where the cell is created:
func addSongButtonIdentifier(cell: UITableViewCell, _ index: Int) {
let addButton = cell.viewWithTag(TABLE_CELL_TAGS.addButton) as! UIButton
//accessibilityIdentifier is used to identify a particular element which takes an input parameter of a string
//assigning the indexpath button
addButton.accessibilityIdentifier = String (index)
// print("visible Index:",index)
print("Index when scrolling :",addButton.accessibilityIdentifier!)
addButton.setImage(UIImage(named: "correct"), for: UIControlState.selected)
addButton.setImage(UIImage(named: "add_btn"), for: UIControlState.normal)
addButton.isSelected = false
addButton.addTarget(self, action: #selector(AddToPlaylistViewController.tapFunction), for:.touchUpInside)
}
The tap function:
func tapFunction(sender: UIButton) {
print("IndexOfRow :",sender.accessibilityIdentifier!)
// if let seporated by a comma defines, if let inside a if let. So if the first fails it wont come to second if let
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]//toggle the state when tapped multiple times
}
sender.isSelected = !sender.isSelected //image toggle
print(" Array Data: ", self.sateOfNewSongArray)
selectedSongList.removeAll()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element{
print("true:", index)
selectedSongList.append(songDetailsArray[index])
print("selectedSongList :",selectedSongList)
}
}
}
There is logical error in func addSongButtonIdentifier(cell: UITableViewCell, _ index: Int) function at line addButton.isSelected = false
it should be addButton.isSelected = self.sateOfNewSongArray[index]
Since, cellForRowAtIndexpath method is called every time table is scrolled, it's resetting selected state of 'addButton'
You need to have array where you store which indexes are selected like selectedSongList array that you have. Then in your cellForRow method you need to use bool proparty from this array to give selected or deselected state to your button or in your addSongButtonIdentifier method selected state need to be
addButton.isSelected = selectedSongList.contains(indexPath.row)
Create a Model class for filling UITableView and take UIImage varaivals in that model, which will hold the current image for cell. On click on button action just change the UIImage variable with current image.
Best approach will be using a model class and keeping the track of each indiviual element in cell. But let me give you a quick fix.
Create a custom class of Button any where like this.
class classname: UIButton {
var imageName: String?
}
Go in your storyboard change the class from UIButton to classname
In your tableViewCellForIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let addButton = cell.viewWithTag(TABLE_CELL_TAGS.addButton) as! classname
if let imgName = addButton.imageName {
addButton.setImage(UIImage(named: imgName), for: UIControlState.normal)
} else {
addButton.setImage(UIImage(named: "add_btn"), for:UIControlState.normal)
}
addButton.addTarget(self, action: #selector(AddToPlaylistViewController.tapFunction), for:.touchUpInside)
return cell
}
Now let's move to your tapbutton implementation
func tapFunction(sender: classname) {
print("IndexOfRow :",sender.accessibilityIdentifier!)
// if let seporated by a comma defines, if let inside a if let. So if the first fails it wont come to second if let
if let rowIndexString = sender.accessibilityIdentifier, let rowIndex = Int(rowIndexString) {
self.sateOfNewSongArray[rowIndex] = !self.sateOfNewSongArray[rowIndex]//toggle the state when tapped multiple times
}
sender.imageName = sender.imageName == "correct" ? "add_btn" : "correct" //image toggle
sender.setImage(UIImage(named: sender.imageName), for:UIControlState.normal)
print(" Array Data: ", self.sateOfNewSongArray)
selectedSongList.removeAll()
for (index, element) in self.sateOfNewSongArray.enumerated() {
if element{
print("true:", index)
selectedSongList.append(songDetailsArray[index])
print("selectedSongList :",selectedSongList)
}
}
}
My TableView features custom Cells which have a button to display corresponding detailed info in another view.
This thread here got me started and I tried to implement the approach with the delegate inside the customCell:
How to access the content of a custom cell in swift using button tag?
What I want to achieve is that when I click on the button it reads the name of the cell and passes it on to the next controller. However it seems that I cannot pass the name with the delegate method and its field is nil.
How can I get the specific content of a cell when clicking on its button?
This is what I did so far:
In the class creating my own cell I set delegate:
protocol CustomCellDelegate {
func cellButtonTapped(cell: DemoCell)
}
(........)
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
In the TableViewController I have the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("FoldingCell",
forIndexPath: indexPath) as! DemoCell
cell.delegate = self
//TODO: set all custom cell properties here (retrieve JSON and set in cell), use indexPath.row as arraypointer
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
cell.schoolNameClosedCell.text = itemForThisRow["name"] as! String
cell.schoolNameOpenedCell.text = itemForThisRow["name"] as! String
self.schoolIdHelperField = itemForThisRow["name"] as! String
cell.schoolIntroText.text = itemForThisRow["name"] as! String
//call method when button inside cell is tapped
cell.innerCellButton.addTarget(self, action: #selector(MainTableViewController.cellButtonTapped(_:)), forControlEvents: .TouchUpInside)
cell.school_id = itemForThisRow["name"] as! String
// cell.schoolIntroText.text = "We from xx University..."
return cell
}
And finally the target method when the button inside the cell is clicked
func cellButtonTapped(cell: DemoCell) {
print("the school id: ")
print(cell.schoolNameOpenedCell) //this line throws an error EXC_BAD_ACCESS 0x0
}
Firstly, the object innerCellButton is not a Cell, it's a button. The simple way to solve your problem is, just refer the index of the button. Please find the below method.
func cellButtonTapped(AnyObject: sender) {
let resultList = self.items["result"] as! [[String: AnyObject]]
//Get the tag value of the selected button.
//Button tag should be matching with the corresponding cell's indexpath.row
let selectedIndex = sender.tag
let itemForThisRow = resultList[selectedIndex]
print("the school id: \(itemForThisRow[\"name\"])")
}
* And set each button's tag as indexPath.row *
E.g.,
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue your cell and other code goes here.
// set the button's tag like below.
cell.innerCellButton.tag = indexPath.row
return cell
}
Close. I wouldn't use Suresh's method since it does not help find the IndexPath, which includes section and row.
First, I would recommend a model object for your table view data source. Learn more about the MVC pattern as well as parsing a JSON response to an object with mapping. However, this would give you the data you want.
func cellButtonTapped(cell: UITableViewCell) {
let indexPath = tableView.indexPathForCell(cell)
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
let name = itemForThisRow["name"] as! String
}