Implement delegate method in Table View - swift

IĀ“have a table view with a custom cell. In this cell I have two buttons that are used for counting. One pluss button, and one minus button. These buttons should increment and decrease a counterLabel with 1.
I have done this in a project I did a long time ago, but then I used extensions, witch I don't do in this project. My problem is that I cannot figure out how to implement the delegate method that I used in the previous project.
I will show the code I'm trying, as an extension. This gives the error "Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'".
How should I correctly implement the delegate?
Main View Controller
import UIKit
class MainViewController: UIViewController, UITableViewDelegate {
let staticList: [ListItem] =
[
ListItem(item1: "item1", item2: "item2", item3: "item3")]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return staticList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell
cell = tableView.dequeueReusableCell(withIdentifier: "MenuCell", for: indexPath)
if let customCell = cell as? MenuCell
{
let itemIndex = indexPath.row
let listItem = staticList[itemIndex]
customCell.item1Label.text = listItem.item1
customCell.item2Label.text = listItem.item2
customCell.item3Label.text = listItem.item3
customCell.counterLabel.text = "\(listItem.count)"
}
return cell
}
}
#IBOutlet weak var mainTableView: UITableView!
}
extension MainViewController: CountDelegate {
func didAddItemCell(cell: MainCell) {
let indexPath = tableView.indexPath(for: cell)!
staticList[indexPath.row].count += 1
cell.countLabel.text = "\(staticList[indexPath.row].count)"
}
func didSubtractItemCell(cell: MenuCell) {
let indexPath = tableView.indexPath(for: cell)!
if staticList[indexPath.row].count > 0 {
staticList[indexPath.row].count -= 1
cell.countLabel.text = "\(staticList[indexPath.row].count)"
}
}
}
Table View Cell file
import Foundation
import UIKit
protocol CountDelegate: NSObjectProtocol {
func didAddItemCell(cell: MainCell)
func didSubtractItemCell(cell: MainCell)
}
class MenuCell : UITableViewCell
{
#IBOutlet weak var item1Label: UILabel!
#IBOutlet weak var item2Label: UILabel!
#IBOutlet weak var item3Label: UILabel!
#IBOutlet weak var counterLabel: UILabel!
#IBOutlet weak var minusButton: UIButton!
#IBOutlet weak var plusButton: UIButton!
weak var delegate: CountDelegate?
#IBAction func minusButton(_ sender: UIButton)
{
delegate?.didAddItemCell(cell: self)
}
#IBAction func plusButton(_ sender: UIButton)
{
delegate?.didSubtractItemCell(cell: self)
}
}

The numberOfRowsInSection and cellForRowAt methods are not part of the UITableViewDelegate protocol but the UITableViewDataSource protocol. You therefore need to change add that protocol like this:
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
You will also need to implement this as well:
func numberOfSections(in tableView: UITableView) -> Int {
return 1 // Or whatever it should be
}

Related

Swift UITableViewCell not showing Labels

I'm trying to create a custom cell view with some labels, I have created a subclass of UITableViewCell and connected the labels to it. Inside cellForRowAt method I dequeued the cell and cast it as YourCell subclass to access the labels and set text.
Setting class of cell to YourCell
However when running the app, nothing is showing up.
class YourCell: UITableViewCell {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let array1 = ["Name1", "Name2","Name3","Name3"]
let array2 = ["1","2","3","4"]
let array3 = ["18","17","11","9"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! YourCell
cell.label1.text = array1[indexPath.row]
cell.label2.text = array2[indexPath.row]
cell.label3.text = array3[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array2.count
}
}
You have conformed the UITableViewDelegate protocol correctly, the only thing you are missing is to set up your datasource by including this line in your viewDidLoad.
tableview.dataSource = self

Change the UILabel when a static variable changes

I have a CartItem struct where I store the items that I show in a UITableView My UITableView is in CartViewController. I use custom cell for my table view. Here is my custom cell:
class CartItemCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var productPrice: UILabel!
#IBOutlet weak var productTotalPrice: UILabel!
#IBOutlet weak var stepper: UIStepper!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
var cellIndex: Int?
var product: CartItem?{
didSet{
// change the cell in table view when first
// initialized and when stepper value is changed
.
.
.
}
#IBAction func stepperValueChanged(_ sender: UIStepper) {
CartItem.cartItems[self.cellIndex!].number = Int(sender.value)
self.product?.number = CartItem.cartItems[self.cellIndex!].number
}
}
In CartViewController I have totalPrice UILabel which gets its value from CartItem.totalPrice static variable. CartItem.totalPrice's value is calculated and changed every time UIStepper in my UITableViewCell is clicked. Here is the summary of CartViewController
class CartViewController: UIViewController{
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var cartLabel: UILabel!
#IBOutlet weak var totalPrice: UILabel!
var items = CartItem.cartItems
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "CartItemCell", bundle: nil), forCellReuseIdentifier: "ReusableCell")
self.totalPrice.text = String(Int(CartItem.totalPrice))
tableView.reloadData()
}
}
extension CartViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! CartItemCell
cell.product = items[indexPath.item]
cell.cellIndex = indexPath.item
return cell
}
}
I want totalPrice label in the CartViewController changed every time CartItem.totalPrice is changed. I change the value of the CartItem.totalPrice in my CartItemCell
Thank you
Use delegate or closures to update value from tableviewcell to viewController
Protocol CartItemProtocol: AnyObject{
func valueUpdated()
}
class CartItemCell: UITableViewCell {
#IBOutlet weak var productImage: UIImageView!
#IBOutlet weak var productName: UILabel!
#IBOutlet weak var productPrice: UILabel!
#IBOutlet weak var productTotalPrice: UILabel!
#IBOutlet weak var stepper: UIStepper!
weak var delegate: CartItemProtocol?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
var cellIndex: Int?
var product: CartItem?{
didSet{
// change the cell in table view when first
// initialized and when stepper value is changed
.
.
.
}
#IBAction func stepperValueChanged(_ sender: UIStepper) {
CartItem.cartItems[self.cellIndex!].number = Int(sender.value)
self.product?.number = CartItem.cartItems[self.cellIndex!].number
self.delegate.valueUpdated()
}
}
And set delegate in cellforRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! CartItemCell
cell.product = items[indexPath.item]
cell.cellIndex = indexPath.item
cell.delegate = self
return cell
}
And Update label in delegate conformation
extension CartViewController:CartItemProtocl{
self.totalPrice.text = String(Int(CartItem.totalPrice))
}

Second Level UITableView not working in SWIFT

I've got a UITableView like so:
import UIKit
import SDWebImage
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var ConversationList: UITableView!
let apiService = APIService()
var conversations: [Conversation] = []
var selectedConversation: Int?
override func viewDidLoad() {
super.viewDidLoad()
ConversationList.separatorStyle = .none
ConversationList.dataSource = self
ConversationList.delegate = self
// load Conversations
self.apiService.getConversations(completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
print("NUMBER OF CONVERSATIONS: ", conversations.count)
self.conversations = conversations
self.ConversationList.reloadData()
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.conversations.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatViewCell", for: indexPath) as! ChatViewCellController
cell.UserName.text = self.conversations[indexPath[1]].participants![1].username
let imgURL = URL(string: self.conversations[indexPath[1]].participants![1].profileimage!)
cell.UserLogo.sd_setImage(with: imgURL, placeholderImage: UIImage(named: "icon.turq.png"))
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("selected row number ", indexPath)
self.selectedConversation = indexPath[1]
self.performSegue(withIdentifier: "ChatToChatDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destination = segue.destination as! ChatDetailViewController
destination.name = self.conversations[self.selectedConversation!].participants![1].username!
destination.img = self.conversations[self.selectedConversation!].participants![1].profileimage!
}
}
The UITableViewCell sits in a separate, simple class like so:
import UIKit
class ChatViewCellController: UITableViewCell {
#IBOutlet weak var UserLogo: UIImageView!
#IBOutlet weak var UserName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.UserLogo.clipsToBounds = true
self.UserLogo.layer.cornerRadius = self.UserLogo.frame.size.width / 2
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
As shown above, selecting a specific cell (i.e conversation) in the UITableView loads another UIViewController via segue. That UIViewController then contains another UITableView:
import UIKit
import SDWebImage
class ChatDetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var UserLogo: UIImageView!
#IBOutlet weak var UserName: UILabel!
#IBOutlet weak var MessageList: UITableView!
var name: String = ""
var img: String = ""
let apiService = APIService()
var messages: [ChatMessage] = []
override func viewDidLoad() {
super.viewDidLoad()
// prepare header section:
self.MessageList.separatorStyle = .none
let imgURL = URL(string: self.img)
self.UserLogo.sd_setImage(with: imgURL, placeholderImage: UIImage(named: "icon.turq.png"))
self.UserLogo.clipsToBounds = true
self.UserLogo.layer.cornerRadius = self.UserLogo.frame.size.width / 2
self.UserName.text = self.name
// TODO: load messages
self.MessageList.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MessageViewCell", for: indexPath) as! ChatMessageViewCellController
cell.ChatMessageText.text = "foo"
return cell
}
}
The UITableViewCell for the above UITableView is again sitting in a separate, simple class:
import UIKit
class ChatMessageViewCellController: UITableViewCell {
#IBOutlet weak var ChatMessageBubble: UIView!
#IBOutlet weak var ChatMessageText: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
print("I'm awake")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Everything works fine up until a certain point. The first Table View loads and shows all Chats. Clicking on a Chat loads the new View with the header section, showing the Logo and Name of the Chat partner at the top of the screen and a Table underneath it. However, that table does not contain anything. To my understanding, with this code, it should show 10 rows saying "foo" and print 10 times "I'm awake" to the console.
What am I missing or doing wrong here?
In your second ViewController, you are not setting UITableViewDelegate and UITableViewDataSource. What you need to add to viewDidLoad:
MessageList.delegate = self
MessageList.dataSource = self
Bonus, for future change self.selectedConversation = indexPath[1] in first view controller to prevent bug - it will always take second model, even if you have only one.
Also, try to avoid upper case for attributes, e.g. MessageList -> messageList.

Why my Data model aren't upload in my tableviewcontroller

I don't know why I can't view my pets in the table.
Few days ago the app is working perfect but when I updated the last version of Xcode the app crashed, I can't see the data whet the App run.
I new developer in swift
Help me, please.
------------------------------Model------------------------
import UIKit
struct InfoPet{
let PetPicture: String
let PetBackground: String
let PetName: String
let PetAge: String
static func InfoPets() -> [InfoPet]{
let pet1 = InfoPet(PetPicture: "Drako", PetBackground: "Background Can", PetName: "Drako", PetAge: "2 years old")
let pet2 = InfoPet(PetPicture: "Lucky", PetBackground: "Background Cat", PetName: "Lucky", PetAge: "3 years old")
return [pet1, pet2]
}
}
-----------------------TableViewController----------------
class PetsTableViewController: UITableViewController {
// MARK: - Global Var
var pets: [InfoPet] = InfoPet.InfoPets()
// MARK: - UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pets.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PetCell", for: indexPath) as! PetsTableViewCell
let pet = pets[indexPath.row]
cell.pet = pet
return cell
}
// MARK: - UITableViewDalegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
-----------------------tableViewCell-----------------------
class PetsTableViewCell: UITableViewCell {
#IBOutlet weak var ImgPhotoPet: UIImageView!
#IBOutlet weak var ImgBackgroundPet: UIImageView!
#IBOutlet weak var LblNamePet: UILabel!
#IBOutlet weak var LblAgePet: UILabel!
var pet: InfoPet!{
didSet{
updateUI()
}
}
func updateUI() {
ImgBackgroundPet.image = UIImage(named: pet.PetBackground)
ImgPhotoPet.image = UIImage(named: pet.PetPicture)
ImgPhotoPet.layer.cornerRadius = 10.0
ImgPhotoPet.layer.masksToBounds = true
LblNamePet.text = pet.PetName
LblAgePet.text = pet.PetAge
}
}
Add Cell Identifier as "PetCell"
and add viewDidLoad method in PetsTableViewController class.
create IBOutlet of the table view.
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
tableview.delegate = self
}

How do I call a different function for each TextField in a UITableView (Swift)?

I have a UITableView and my prototype cell consists of a label and a TextField. I also have a class MyClass that contains functions func1, func2, fun3, ... I have several ViewControllers that use the same tableViewCell prototype. Each viewController will have an instance of MyClass, called inst1, inst2, and inst3. When I enter text into FirstViewController's TableView I want each row to call a function from the instance of MyClass that corresponds to the row.
So when I enter text into row 1 on the FirstViewController I want to pass the data entered into the textField into func1 of inst1. When data is entered into row 2 of FirstViewController I want the data in the textfield to be passed into func2 of inst1. And so on and so forth down the rows.
I am very new to this and would really appreciate some help figuring out how to do this. Let me know if that doesn't make sense and I can try to rephrase it. I really need help with this. Thanks in advance!
*Updated question to show my code
Below is my Code:
FirstViewController.swift
extension FirstViewController: MyCellDelegate {
func MyCell(_ cell: UITableViewCell, didEnterText text: String) {
if let indexPath = tableView.indexPath(for: cell) {
if (indexPath.hashValue == 0) {
inst1.func1(one: text)
}
if (indexPath.hashValue == 1) {
inst1.func2(two: text)
}
}
totalText.text = inst1.getMyTotal()
}
}
import UIKit
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let inst1 = MyClass()
#IBOutlet weak var totalText: UILabel!
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 11
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell") as! TableViewCell
let text = cell.cellData[indexPath.row]
cell.myTextField.tag = indexPath.row
cell.delegate = self
cell.myLabel.text = text
cell.myTextField.placeholder = text
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
TableViewCell.swift
import UIKit
protocol MyCellDelegate: class {
func MyCell(_ cell: UITableViewCell, didEnterText text: String)
}
class TableViewCell: UITableViewCell {
weak var delegate: MyCellDelegate?
public var cellData: [String] = ["1","2","3","4","5","6","7","8","9","10","11"]
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
}
}
When I set a breakpoint in the FirstViewController extension it never runs that code.
In WillDisplayCell add the tag to the UITextField. Also create a protocol to notify the Corrosponding viewController and set itself as the delegate here.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")
cell.textField.tag = indexPath.row
cell.delegate = self
}
The protocol in your cell class will look something like this
protocol MyCellDelegate: class {
func MyCell(_ cell: UITableViewCell, didEnterText text: String)
}
class MyCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: MyCellDelegate?
override fun awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
//All the remaining code goes here
func textFieldShouldReturn(_ textField: UITextField) -> Bool { //delegate method
textField.resignFirstResponder()
delegate?.MyCell(self, didEnterText: textField.text! )
return true
}
}
Now again in your FirstViewController which has conformed to be its delegate do this
extension FirstViewController: MyCellDelegate {
func MyCell(_ cell: UITableViewCell, didEnterText text: String) {
if let indexPath = tableView.indexPathForCell(cell) {
// call whichever method you want to call based on index path
}
}