Pass value to next view when label in Table view cell is tapped - ios

would love to pass the value postArray[indexpath.row].creatorId when the label inside a tableview cell is tapped so it can be passed onto the next view controller so i can load the profile of that particular creator/user. I used custom cells, so how do i get the creator id based on the location of the label(username) selected.
//custom cell
class PostCell : UITableViewCell
{
#IBOutlet weak var timeAgoLabel: UILabel!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var postImageView: UIImageView!
#IBOutlet weak var captionLabel: UILabel!
#IBOutlet weak var postStatsLabel: UILabel!
}
//do something when label is tapped
#objc func tapFunction(sender:UITapGestureRecognizer) {
//userClicked = creatorData
print(userClicked)
appDelegate.profileView()
print("tap working")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0{
return 1
}else{
return postsArray.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//var returnCell: UITableViewCell!
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! statusCell
profilePicture = UserDefaults.standard.object(forKey: "userPic") as? String
if profilePicture != nil {
//load profile picture from library
let urlString = "https://test.com/uploads/profile-picture/"+(profilePicture)!
let profileURL = URL(string: urlString)
cell.statusProfilePic?.downloadedFrom(url: profileURL!)
} else {
print("you have no profile picture set")
}
return cell
} else {
if postsArray[indexPath.row].photos != nil{
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
if postsArray[indexPath.row].comments != nil {
comments = postsArray[indexPath.row].comments?.count
} else {
comments = 0
}
if postsArray[indexPath.row].like_list != nil {
likes = postsArray[indexPath.row].like_list?.count
}else{
likes = 0
}
//assign post id to PostID
postID = postsArray[indexPath.row].post_id
//make username clickable!
let tap = UITapGestureRecognizer(target: self, action: #selector(NewsfeedTableViewController.tapFunction))
cell.usernameLabel.isUserInteractionEnabled = true
cell.usernameLabel.addGestureRecognizer(tap)
cell.usernameLabel.text = postsArray[indexPath.row].fullname
cell.timeAgoLabel.text = postsArray[indexPath.row].data_created
cell.captionLabel.text = postsArray[indexPath.row].content
cell.timeAgoLabel.text = postsArray[indexPath.row].modified
//15 Likes 30 Comments 500 Shares
cell.postStatsLabel.text = "\(likes!) Likes \(comments!) Comments"
//load profile picture from library
let urlString = "https://test.com/uploads/profile-picture/"+(postsArray[indexPath.row].profile_pic_filename)!
let profileURL = URL(string: urlString)
cell.profileImageView.downloadedFrom(url: profileURL!)
//iterate through posts images images array
//load post picture from server library
var postImageName : String?
if postsArray[indexPath.row].photos != nil{
let postImage = postsArray[indexPath.row].photos
for postsImage in postImage!{
postImageName = postsImage.filename!
}
let urlPostImageString = "https://test.com/uploads/post-picture/"+(postImageName)!
let postsImageUrl = URL(string: urlPostImageString)
cell.postImageView.downloadedFrom(url: postsImageUrl!)
} else {
print("Post has no picture")
}
//return cell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "NoImageCell", for: indexPath) as! NoImageTableViewCell
if postsArray[indexPath.row].comments != nil {
comments = postsArray[indexPath.row].comments?.count
} else {
comments = 0
}
if postsArray[indexPath.row].like_list != nil {
likes = postsArray[indexPath.row].like_list?.count
} else {
likes = 0
}
//make username clickable!
let tap = UITapGestureRecognizer(target: self, action: #selector(NewsfeedTableViewController.tapFunction))
cell.noImageUsername.isUserInteractionEnabled = true
cell.noImageUsername.addGestureRecognizer(tap)
cell.noImageUsername.text = postsArray[indexPath.row].fullname
cell.noImageTime.text = postsArray[indexPath.row].data_created
cell.noImagePost.text = postsArray[indexPath.row].content
cell.noImageTime.text = postsArray[indexPath.row].modified
//15 Likes 30 Comments 500 Shares
cell.noImageLikeAndComment.text = "\(likes!) Likes \(comments!) Comments"
//load profile picture from library
let urlString = "https://test.com/uploads/profile-picture/"+(postsArray[indexPath.row].profile_pic_filename)!
let profileURL = URL(string: urlString)
cell.noImageProfilePic.downloadedFrom(url: profileURL!)
return cell
}
}
}

Use this for example.
Implement didSelectRow() method and in it write something like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// this method works, when you taped cell. write here code of you need. Next code only example, which set user info to some VC and push it:
let controller = UserController as? UserController
if let controller = controller {
controller.user = users[indexPath.row]
self.navigationController?.pushViewController(controller, animated: true)
}
}
add this to your Cell's class:
func setTap() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapRecognized))
self.label.addGestureRecognizer(tap)
tap.numberOfTapsRequired = 1
}
#objc func tapRecognized(sender: UITapGestureRecognizer) {
// here your code of tap on label
print("label tapped")
}
Check on storyBoard is your label isUserInteractionEnabled? - set it to true. Inside tapRecodnized() method do what are you need. And you need to call method setTap() in your cell's method, which you call in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell.
Update
Simple example. this code know what are you tapped. if you tap cell, but not label, add code of push some controller, else code of push another controller.
Cell's Class:
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
var mainController: ViewController?
func setText(text: String) {
setTap()
label.text = text
}
func setTap() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapRecognized))
self.label.addGestureRecognizer(tap)
tap.numberOfTapsRequired = 1
}
#objc func tapRecognized(sender: UITapGestureRecognizer) {
if let mainController = mainController {
print("label tapped")
mainController.pushSomeVc(cell: self)
}
}
}
Code of main Class:
class ViewController: UIViewController {
#IBOutlet weak var myTableView: UITableView!
var array = ["1", "2", "3", "4", "5", "6"]
override func viewDidLoad() {
super.viewDidLoad()
}
func pushSomeVc(cell: MyTableViewCell) {
let row = myTableView.indexPath(for: cell)?.row
if let row = row {
// write here code of push controller, when label tapped. row property for get some user from array
print("push some vc with \(row)")
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "cell") as? MyTableViewCell
if let cell = cell {
cell.setText(text: array[indexPath.row])
cell.mainController = self
}
return cell ?? UITableViewCell()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
// write here code of push controller with comments
print("cell tapped: \(indexPath.row)")
}
}
I tested this code and it's work perfect

Related

Is it wrong to add action to button in tableViewCell with tag?

I have a UItableViewCell with a button inside it, I set the tag of the button and add the action of the button in my ViewController using the tag.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
let cellData = billHistories[indexPath.row]
cell.setup(with: cellData)
cell.retryButton.tag = indexPath.row
return cell
}
#IBAction func billHistoryRetryButtonDidTap(_ sender: UIButton) {
let index = sender.tag
if let id = billHistories[index].transactionInfo?.billUniqueID {
hidePayIdGeneralTextField()
billIdTextField.text = id.toNormalNumber()
inquiryGeneralBillRequest()
}
}
I want to know is it wrong for any reason? someone told me it is not good because it uses lots of memory to use tags.
Will it work? yes, but as mentioned above, this is not the best approach, I'd avoid using tags unless this is just for some POC. There are better approaches to handle it.
The first I'd suggest is using delegation to inform back to the controller, here's an example:
class BillHistoryTableViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
let cellData = billHistories[indexPath.row]
cell.setup(with: cellData)
cell.index = indexPath.row
cell.delegate = self
return cell
}
}
extension BillHistoryTableViewController: BillHistoryTableViewCellDelegate {
func didTapButton(index: Int) {
print("tapped cell with index:\(index)")
if let id = billHistories[index].transactionInfo?.billUniqueID {
hidePayIdGeneralTextField()
billIdTextField.text = id.toNormalNumber()
inquiryGeneralBillRequest()
}
}
}
protocol BillHistoryTableViewCellDelegate: AnyObject {
func didTapButton(index: Int)
}
class BillHistoryTableViewCell: UITableViewCell {
weak var delegate: BillHistoryTableViewCellDelegate?
var cellData: CellData?
var index: Int?
func setup(with cellData: CellData) {
self.cellData = cellData
}
#IBAction func buttonPressed(_ sender: UIButton) {
guard let index = index else {
return
}
delegate?.didTapButton(index: index)
}
}
Another approach that I prefer lately is using Combine's PassThroughSubject, it requires less wiring and delegate definitions.
import Combine
class BillHistoryTableViewController {
var cancellable: AnyCancellable?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BillHistoryTableViewCell", for: indexPath) as! BillHistoryTableViewCell
let cellData = billHistories[indexPath.row]
cell.setup(with: cellData)
cell.index = indexPath.row
cancellable = cell.tappedButtonSubject.sink { [weak self] index in
guard let self = self else { return }
print("tapped cell with index:\(index)")
if let id = self.billHistories[index].transactionInfo?.billUniqueID {
self.hidePayIdGeneralTextField()
self.billIdTextField.text = id.toNormalNumber()
self.inquiryGeneralBillRequest()
}
}
return cell
}
}
class BillHistoryTableViewCell: UITableViewCell {
var tappedButtonSubject = PassthroughSubject<Int, Never>()
var cellData: CellData?
var index: Int?
func setup(with cellData: CellData) {
self.cellData = cellData
}
#IBAction func buttonPressed(_ sender: UIButton) {
guard let index = index else {
return
}
tappedButtonSubject.send(index)
}
}
You can make it even shorter by injecting the index with the cellData, e.g:
func setup(with cellData: CellData, index: Int) {
self.cellData = cellData
self.index = index
}
but from what I see in your example, you don't even need the index, you just need the CellData, so if we'll take the Combine examples these are the main small changes you'll have to make:
var tappedButtonSubject = PassthroughSubject<CellData, Never>()
tappedButtonSubject.send(cellData)
and observing it by:
cancellable = cell.tappedButtonSubject.sink { [weak self] cellData in
if let id = cellData.transactionInfo?.billUniqueID {
//
}
}

Is there a way to get the id of a UITableViewCell?

my problem: I want to open some kind of Profil if a user pushes a Button in a Table-View Cell. The Cells Data is downloaded from Parse.
The idea is based on Instagram, if you click on the username-button on Insta the profile from the user who posted the image will open. I want to create the same code, but i can't create the code to get the user. Can you help me?
Heres some code:
import UIKit
import Parse
class HomeController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let reuseIdentifer = "FeedCell"
var delegate: HomeControllerDelegate?
var newCenterController: UIViewController!
let tableView = UITableView()
//Für Parse:
var users = [String: String]()
var comments = [String]()
var usernames = [String]()
var lastnames = [String]()
var imageFiles = [PFFileObject]()
var wischen: UISwipeGestureRecognizer!
var wischen2: UISwipeGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
getData()
configureNavigationBar()
configurateTableView()
wischen = UISwipeGestureRecognizer()
wischen.addTarget(self, action: #selector(handleMenuToggle))
wischen.direction = .right
wischen.numberOfTouchesRequired = 1
view.addGestureRecognizer(wischen)
wischen2 = UISwipeGestureRecognizer()
wischen2.addTarget(self, action: #selector(handleMenuToggle))
wischen2.direction = .left
wischen2.numberOfTouchesRequired = 1
view.addGestureRecognizer(wischen2)
}
#objc func handleMenuToggle() {
delegate?.handleMenuToggle(forMenuOption: nil)
}
#objc func showProfile() {
let vc: AProfileViewController!
vc = AProfileViewController()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
func configureNavigationBar() {
navigationController?.navigationBar.barTintColor = .darkGray
navigationController?.navigationBar.barStyle = .black
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "Noteworthy", size: 22)!, NSAttributedString.Key.foregroundColor: UIColor.white]
//navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navigationItem.title = "Mobile Job Board"
navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_menu_white_3x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(handleMenuToggle))
navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_mail_outline_white_2x").withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(showCreateNewArticle))
}
//MARK: Table View
//skiped table view configuration
}
// - MARK: Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! FeedCell
imageFiles[indexPath.row].getDataInBackground { (data, error) in
if let imageData = data {
if let imageToDisplay = UIImage(data: imageData) {
cell.postImage.image = imageToDisplay
}
}
}
cell.descriptionLabel.text = comments[indexPath.row]
cell.userButton.setTitle("\(usernames[indexPath.row]) \(lastnames[indexPath.row])", for: UIControl.State.normal)
cell.userButton.addTarget(self, action: #selector(showProfile), for: .touchUpInside)
return cell
}
//skiped
}
Thanks a lot!
Tom
The issue here is that your button works on a selector and it has no idea about the sender or where it was called from.
I would do this by creating a custom table view cell (e.g. FeedCell) which allows you to set a delegate (e.g. FeedCellDelegate). Set your class as the delegate for the cell and pass into the cell it's current indexPath. You can then return the indexPath in the delegate call.
Example: Note that code has been removed for simplicity and this code has not been tested. This is simply to guide you in the right direction.
View Controller
import UIKit
class HomeController: UIViewController {
// stripped additional information for example
func showProfile(_ username: String) {
let vc: AProfileViewController!
vc = AProfileViewController()
vc.username = username
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
}
extension HomeController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifer, for: indexPath) as! FeedCell
cell.delegate = self
cell.descriptionLabel.text = comments[indexPath.row]
cell.userButton.setTitle("\(usernames[indexPath.row]) \(lastnames[indexPath.row])", for: UIControl.State.normal)
cell.setIndex(indexPath)
return cell
}
}
extension HomeController: FeedCellDelegate {
func didPressButton(_ indexPath: IndexPath) {
let userName = usernames[indexPath.row]
showProfile(username)
}
}
Feed Cell
import UIKit
protocol FeedCellDelegate {
didPressButton(_ indexPath: IndexPath)
}
class FeedCell: UICollectionViewCell {
var delegate: FeedCellDelegate?
var indexPath: IndexPath
#IBOutlet weak var userButton: UIButton
setIndex(_ indexPath: IndexPath) {
self.indexPath = indexPath
}
#IBAction userButtonPressed() {
if(delegate != nil) {
delegate?.didPressButton(indexPath)
}
}
}
You can generically and in a type safe way get the parent responder of any responder with:
extension UIResponder {
func firstParent<T: UIResponder>(ofType type: T.Type ) -> T? {
return next as? T ?? next.flatMap { $0.firstParent(ofType: type) }
}
}
So:
Get the parent tableviewCell of your button in the target action function
Ask your tableview for the index path
Use the index path.row to index into your users array:
#objc func showProfile(_ sender: UIButton) {
guard let cell = firstParent(ofType: UITableViewCell.self),
let indexPath = tableView.indexPath(for: cell) else {
return
}
let user = users[indexPath.row]
... do other stuff here ...
}

In swift, how to manage two buttons in same custom tableview cell?

I am trying to manage two buttons in same custom tableview cell.
Added two buttons named Yes and No. If yes button is selected the No button will be inactive and Yes button became active.
Here is the image what I need
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell") as! TableViewCell
cell.yesButton.tag = 101
cell.noButton.tag = 102
cell.yesButton.addTarget(self, action: #selector(buttonClicked(sender:)), for: UIControl.Event.touchUpInside)
cell.noButton.addTarget(self, action: #selector(buttonClicked(sender:)), for: UIControl.Event.touchUpInside)
return cell
}
#objc func buttonClicked(sender: AnyObject) {
let buttonPosition = (sender as AnyObject).convert(CGPoint.zero, to: tableList)
let indexPath = tableList.indexPathForRow(at: buttonPosition)
if sender.tag == 101 {
if indexPath != nil {
print("Cell indexpath = \(String(describing: indexPath?.row))")
}
}
if sender.tag == 102 {
if indexPath != nil {
print("Cell indexpath = \(String(describing: indexPath?.row))")
}
}
}
Create a model to main the state of yesButton and noButton for each tableViewCell, i.e.
class Model {
var isYesSelected = false
var isNoSelected = false
}
Create a custom UITableViewCell with Outlets of yesButton and noButton.
Create a single #IBAction for both the buttons and handle their UI based on which button is tapped.
Also, use a buttonTapHandler to identify the row in which the button is tapped. It will be called everytime a button is tapped. We'll be setting this when creating the instance of TableViewCell in tableView(_:cellForRowAt:).
class TableViewCell: UITableViewCell {
#IBOutlet weak var yesButton: UIButton!
#IBOutlet weak var noButton: UIButton!
var buttonTapHandler: (()->())?
var model: Model?
override func prepareForReuse() {
super.prepareForReuse()
yesButton.backgroundColor = .gray
noButton.backgroundColor = .gray
}
func configure(with model: Model) {
self.model = model
self.updateUI()
}
#IBAction func onTapButton(_ sender: UIButton) {
model?.isYesSelected = (sender == yesButton)
model?.isNoSelected = !(sender == yesButton)
self.updateUI()
}
func updateUI() {
yesButton.backgroundColor = (model?.isYesSelected ?? false) ? .green : .gray
noButton.backgroundColor = (model?.isNoSelected ?? false) ? .green : .gray
}
}
UITableViewDataSource's tableView(_:cellForRowAt:) method goes like,
let numberOfCells = 10
var models = [Model]()
override func viewDidLoad() {
super.viewDidLoad()
(0..<numberOfCells).forEach { _ in
self.models.append(Model())
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! TableViewCell
cell.configure(with: models[indexPath.row])
cell.buttonTapHandler = {
print(indexPath.row)
}
return cell
}
To get the totalPoints, count the models with isYesSelected = true, i.e.
let totalPoints = models.reduce(0) { (result, model) -> Int in
if model.isYesSelected {
return result + 1
}
return 0
}
print(totalPoints)
Get that Button using your Tag like below and after that, you can change the value as per you want.
var tmpButton = self.view.viewWithTag(tmpTag) as? UIButton
Simple 3 step process...!!
Define Model Class
Prepare tableView Cell & handle actions
Set up tableView in view controller
Let's start implementation:
1) Define Model Class
In UI, we have a information like question & it's answer (Yes/No). So design model respectively.
//MARK:- Class Declaration -
class Question {
let questionText: String
var answerState: Bool?
init(question: String) {
self.questionText = question
}
}
2. Prepare tableView Cell & handle actions
Create a custom tableView cell with Question Label, Yes Button & No Button. Link that view with respected #IBOutlets & #IBActions.
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var yesButton: UIButton!
#IBOutlet weak var noButton: UIButton!
var question: Question?
var toggle: Bool? {
didSet {
question?.answerState = toggle
//Do buttons operations like...
if let isToggle = toggle {
yesButton.backgroundColor = isToggle ? .green : .gray
noButton.backgroundColor = isToggle ? .gray : .green
} else {
yesButton.backgroundColor = .gray
noButton.backgroundColor = .gray
}
}
}
func prepareView(forQuestion question: Question) {
self.question = question
questionLabel.text = question.questionText
toggle = question.answerState
}
//Yes Button - IBAction Method
#IBAction func yesButtonTapped(_ sender: UIButton) {
toggle = true
}
//No Button - IBAction Method
#IBAction func noButtonTapped(_ sender: UIButton) {
toggle = false
}
}
3. Set up tableView in view controller
class ViewController: UIViewController {
//Prepare questions model array to design our tableView data source
let arrQuestions: [Question] = [Question(question: "Do you speak English?"), Question(question: "Do you live in Chicago?")]
}
//MARK:- UITableView Data Source & Delegate Methods -
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrQuestions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as? TableViewCell else {
return UITableViewCell()
}
tableViewCell.prepareView(forQuestion: arrQuestions[indexPath.row])
return tableViewCell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
}
}
Create basic tableView and configure dataSource functions
Create tableView cell with two buttons
Create cell class with buttons outlets and actions
Result of this code
Enjoy!

change variables from an other view controller when a textField is changed

I'm trying to change the values of a variable in two different view controllers from the value of a textField but I don't understand how to use the delegate so that it works.
My Storyboard:
My Code:
MainView:
class GameCreatingViewController: UIViewController {
var newGame = Game()
override func viewDidLoad() {
super.viewDidLoad()
newGame = Game()
newGame.playerBook.NumberOfPlayers = 2
if let vc = self.children.first(where: { $0 is PlayersTableViewController }) as? PlayersTableViewController {
vc.currentGame = self.newGame
vc.tableView.reloadData()
}
if let vc = self.children.first(where: { $0 is GameViewController }) as? GameViewController {
vc.currentGame = self.newGame
}
}
func changeName(name: String, number: Int) {
self.newGame.playerBook.players[number].name = name
}
}
tableViewController:
class PlayersTableViewController: UITableViewController, UITextFieldDelegate {
var currentGame = Game()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}
func changeName(name: String, number: Int) {
self.currentGame.playerBook.players[number].name = name
}
}
The Cell:
protocol changeNameDelegate: class {
func changeName(name: String, number: Int)
}
class PlayerNameTableViewCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: changeNameDelegate? = nil
#IBOutlet weak var playerName: UITextField!
var numberOfPlayer: Int = Int()
#IBAction func changeName(_ sender: UITextField) {
delegate?.changeName(name: sender.text!, number: numberOfPlayer)
}
}
It seems like the action from the button executes but the fonctions from the other viewcontrollers don't.
Use the delegate to notify the other viewController.
Make sure isn't nil.
Usually protocols name the first letter is capitalized.
A good practice is to implement protocols in extensions.
Implement the changeNameDelegate protocol.
class PlayersTableViewController: UITableViewController, UITextFieldDelegate, changeNameDelegate {
And in the cell configuration set the delegate.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "playerCell", for: indexPath) as? PlayerNameTableViewCell else {fatalError("Wrong type of cell")}
// Configure the cell...
cell.playerName.delegate = self
cell.delegate = self // This line is missing.
let row = indexPath[1]+1
cell.numberOfPlayer = row
return cell
}

UISwitch state in Tableviewcell resets when user scrolls - Swift

I've searched for a solutions on this issue but none seem to work for my use case.
I have a table inside a viewcontroller and the issue I am facing is that when scrolling the UISwitch state is reset to OFF. I understand table cells are reused, but how do I implement a solution that will restore the state of UISwitch when a user scrolls based on my code below
import UIKit
class StirrViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var mylabel: UILabel!
var myString = String()
#IBAction func stirrBtn(_ sender: AnyObject) {
}
var timeSelected = String()
var selectedTimeArr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
mylabel.text = myString
self.timeSelected = myString
}
func switchChanged(_ sender : UISwitch!){
print("table row switch Changed \(sender.tag)")
print("The switch is \(sender.isOn ? "ON" : "OFF")")
let kValue = (sender.tag + 1)
let keyValue = String(kValue)
if sender.isOn {
recipeSettings.boolStirrSwitch[keyValue] = true
recipeSettings.switchedOnArr.append(keyValue)
} else {
recipeSettings.boolStirrSwitch[keyValue] = false
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let stringNum = Int(self.timeSelected)
recipeSettings.recipeTimeSet2 = stringNum!
return(stringNum)!
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UITableViewCell
//here is programatically switch make to the table view
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
switchView.tag = indexPath.row // for detect which row switch Changed
switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
cell.accessoryView = switchView
// Process data displayed in rows(minutes)
let endTime = Int(self.timeSelected)
let startTime = Int(1)
// Recipe time array
let timeArray: [Int] = Array(startTime...endTime!)
let stringTimeArr = timeArray.map{String($0)}
// Save time array to global variable
recipeSettings.recipeTimeSetArr = stringTimeArr
// Create a boolean Array to hold all default false booleans
let defBool: Bool = false
var defBoolArr: [Bool] = []
// Fill the array with the defaults boolean
for _ in 0..<stringTimeArr.count{defBoolArr.append(defBool)}
// Map the array to global dictionary containing the Time in an array and default "false" value
for i in 0..<stringTimeArr.count {
recipeSettings.boolStirrSwitch[stringTimeArr[i]] = defBoolArr[i]
}
// Add the minutes to cell table
cell.textLabel?.text = stringTimeArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
As you can see in my code I do save the state of each UI switch in a global variable dictionary. How can I solve the issue of UISwitch changing states based on this code? All help is appreciated. Thanks in advance
var switchState = [String : Bool]()
your recipeSettings.boolStirrSwitch should be decleard like that.
As you are using timeSelected as numberOfRowsInSection as showing
your cell.textLabel from that so you don't need extra stringTimeArr
for that.
All the processing you do in cellForRowAt it will happen again and
again table cells are reused so for setting up data do it in another
function then reload TableView.
Solution for your problem should be look like that.
import UIKit
class StirrViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
//make tableView IBOutlet for reloading data
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var mylabel: UILabel!
var myString = String()
#IBAction func stirrBtn(_ sender: AnyObject) {
}
var timeSelected = String()
var selectedTimeArr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
mylabel.text = myString
self.timeSelected = myString
self.setdefaultSwitchState()
}
//recipeSettings.boolStirrSwitch should be decleard like that
var switchState = [String : Bool]()
//setDeaultSwitchState
func setdefaultSwitchState(){
if let timeSelected = Int(self.timeSelected){
for value in 0..<timeSelected{
switchState["\(value)"] = false
//or
//recipeSettings.boolStirrSwitch["\(value)"] = false
}
}
self.tableView.reloadData()
}
#objc func switchChanged(_ sender : UISwitch!){
print("table row switch Changed \(sender.tag)")
print("The switch is \(sender.isOn ? "ON" : "OFF")")
let kValue = (sender.tag + 1)
let keyValue = String(kValue)
if sender.isOn {
switchState[keyValue] = true
} else {
switchState[keyValue] = false
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let stringNum = Int(self.timeSelected)
recipeSettings.recipeTimeSet2 = stringNum!
return(stringNum)!
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UITableViewCell
//here is programatically switch make to the table view
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
switchView.tag = indexPath.row // for detect which row switch Changed
switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
cell.accessoryView = switchView
cell.textLabel?.text = "\(indexPath.row + 1)"
if let switchState = switchState["\(indexPath.row)"] {
if switchState{
switchView.isOn = true
}else{
switchView.isOn = false
}
}else{
switchView.isOn = false
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

Resources