I created a custom class named myCustomCell and it inherits UITableViewCell. I declared the two variables in the class. It gave me crashing when the tableview is called. so, I added "guard" in tableview(_:cellForRowAT). I don't see any error but I don't see anything in cell. Would anyone help me with it?
import UIKit
class tableviewtest: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell:myCustomCell = tableview.dequeueReusableCell(withIdentifier: cellIdentifier) as? myCustomCell else {
return UITableViewCell()
}
if searching {
cell.myLableCell1?.text = searchArr[indexPath.row]
targetStringItem = searchArr[indexPath.item]
if let indexForSelectedEng = eng.firstIndex(of: targetStringItem) {
showTheIndex = indexForSelectedEng
cell.myLableCell2?.text = String(showTheIndex)
}
}
else {
cell.myLableCell1?.text = copiedArray[indexPath.row]
targetStringItem = copiedArray[indexPath.item]
if let indexForSelectedEng = eng.firstIndex(of: targetStringItem) {
showTheIndex = indexForSelectedEng
cell.myLableCell2?.text = String(showTheIndex)
}
}
return cell
}
class myCustomCell:UITableViewCell {
#IBOutlet weak var myLableCell1: UILabel!
#IBOutlet weak var myLableCell2: UILabel!
}
It's a classic design mistake and reveals the (practical) uselessness of the conditional downcast.
The class of the custom cell in Interface Builder must be set to myCustomCell and the Identifier of the cell must be set to the proper value, too. Then you can remove the guard expression.
And please name classes and structs with starting uppercase letter.
Side note:
There is a lot of redundant code, the method can be reduced to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! myCustomCell
targetStringItem = searching ? searchArr[indexPath.row] : copiedArray[indexPath.row]
cell.myLableCell1?.text = targetStringItem
if let indexForSelectedEng = eng.firstIndex(of: targetStringItem) {
showTheIndex = indexForSelectedEng
cell.myLableCell2?.text = String(showTheIndex)
}
return cell
}
Related
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
}
I have a UITableView, Each UITableViewCell has its own Async task (Webservice call) , the cell should be notified when its task finishes in order to update the labels, the task is called every 30seconds. I don't want to realod the whole UITableView everytime.
Here is what I have done so far:
class ViewModel {
var name, result: String
var url: String
init () {
let timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true, block: self.startUpdating())
}
func startUpdating() {
let dispatchQueue = DispatchQueue(label: "startUpdating", qos:.utility)
dispatchQueue.async{
self.callWebservice()
// how can i notify my cell about the new changes
}
}
func callWebservice(){
//call web service and update name and result
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let vm = viewModels[indexPath.row]
cell.textLabel = vm.name
cell.detailTextLabel = vm.result
return cell
}
You can update one particular cell instead of reloading the whole TableView.
yourTableView.beginUpdates()
yourTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
yourTableView.endUpdates()
A smart solution is Key Value Observing.
The model must be a subclass of NSObject
The observed properties must be marked as #objc dynamic
class ViewModel : NSObject {
#objc dynamic var name, result: String
...
In Interface Builder set the style of the cell to custom, add two labels and set AutoLayout constraints if needed
Create a new UITableViewCell subclass, name it ResultCell and add the two labels and the two observation properties
class ResultCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var resultLabel: UILabel!
var nameObservation : NSKeyValueObservation?
var resultObservation : NSKeyValueObservation?
}
In Interface Builder set the class of the custom cell to ResultCell and connect the labels to the custom cell
In the controller in cellForRow add the observers
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ResultCell
let vm = viewModels[indexPath.row]
cell.nameLabel!.text = vm.name
cell.resultLabel!.text = vm.result
cell.nameObservation = vm.observe(\.name, changeHandler: { (model, _) in
cell.nameLabel!.text = model.name
})
cell.resultObservation = vm.observe(\.result, changeHandler: { (model, _) in
cell.resultLabel!.text = model.result
})
return cell
}
And you have to remove the observers in didEndDisplaying
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let resultCell = cell as! ResultCell
resultCell.nameObservation = nil
resultCell.resultObservation = nil
}
The benefit is that any change of name and result in the model will update the cell when it's on screen.
I want to reuse a UITableViewCell in my app, but I get this error: Unexpectedly found nil while implicitly unwrapping an Optional value.
I find that this is because the UI things in UITableViewCell is nil, so my app crashed.
My UITableViewCell code is like this:
class WordListCell: UITableViewCell {
#IBOutlet weak var wordListCoverImage: UIImageView!
#IBOutlet weak var wordListName: UILabel!
#IBOutlet weak var wordListInfo: UILabel!
var wordList: WordList? {
didSet {
updateUI()
}
}
private func updateUI() {
wordListName.text = wordList?.name
wordListInfo.text = wordList?.description
wordListCoverImage = UIImage()
}
}
I create it in the storyboard and link the outlet to the code in the other TableView.
But this time, I want to reuse the cell in a new TableView which is all created by code, so I don't know how to initialize the UI things.
The new UITableView code is like this:
tableView.delegate = self
tableView.dataSource = self
tableView.register(WordListCell.self, forCellReuseIdentifier: "wordListCell")
//the delegate
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wordLists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let wordList = wordLists[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "wordListCell", for: indexPath)
if let wordListCell = cell as? WordListCell {
wordListCell.wordList = wordList
}
return cell
}
Please tell me how to reuse the cell.Thanks!
Okay so I think what you are doing wrong is when you create a custom tableView cell, you are not assigning a UIImage. So instead try doing this wordListCoverImage = UIImage(named: wordList.imageName).
Now also in your tableView class inside viewDidLoad() apart from adding
tableView.delegate = self
tableView.dataSource = self
tableView.register(WordListCell.self, forCellReuseIdentifier: "wordListCell")
Then at let cell = tableView.dequeueReusableCell(withIdentifier: "wordListCell", for: indexPath) downcast it as a custom cell class like so.
let cell = tableView.dequeueReusableCell(withIdentifier: "wordListCell", for: indexPath) as! WordListCell
And then finaly under that set the cell.delegate = self
I hope this helps!
Edited:
I'm trying to insert two arrays into one tableview with two cell labels. But not really sure how I can convert it all and make it work. Here is my code so far.
In the top of the main ViewController (Where the tableView is)
class ScoreBoardViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
var countedArray: [String] = []
var nameArray: [String] = []
#IBOutlet weak var tableView: UITableView!
var list: [pointsTxt] = []
Additional info: I've added the self.tableView.delegate = self
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let listPath = list[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "pointsCell") as! ScoreCell
cell.setCell(list: listPath)
return cell
}
func createArray() -> [pointsTxt]
{
var tempTxt: [pointsTxt] = []
let txt = pointsTxt(person: nameArray, points: countedArray)
tempTxt.append(txt)
self.list = tempTxt
self.tableView.reloadData()
return list
}
Class for the cell
class ScoreCell: UITableViewCell
{
#IBOutlet weak var person: UILabel!
#IBOutlet weak var points: UILabel!
func setCell(list: pointsTxt)
{
person.text = list.person
points.text = list.points
}
}
ERROR: Cannot assign value of type '[String]' to type 'String?'
Class for the array
class pointsTxt
{
var person: [String] = []
var points: [String] = []
init(person: [String] = [], points: [String] = [])
{
self.person = person
self.points = points
}
}
I hope you understand what I need. Thanks in advance!
You have to follow the following steps when creating a custom tableViewCell:
Subclass UITableViewCell
Add your Outlets to that subclass
register your subclass to the TableView
code Example for your rowAtIndexPath function:
.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < nameArray.count {
// load from customerDetails array
let names = nameArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "listCell", for: indexPath) as! UITableViewSubclassCell
cell.person.text = names
return cell
} else {
// load from customerDetails2 array
let points = countedArray[indexPath.row - nameArray.count]
let cell = tableView.dequeueReusableCell(withIdentifier: "listCell", for: indexPath) as! UITableViewSubclassCell
cell.person.text = points
return cell
}
}
The issue is as follows: I have a tableview with a custom cell. That cell contains a label and a UISwitch. I have set the label.text value to an array, but the UISwitch is getting reused.
Example: If I toggle the switch in the first row, the 5th row gets enabled, and if I scroll it continues to reuse the cells and cause issue.
Video : https://vimeo.com/247906440
View Controller:
class ViewController: UIViewController {
let array = ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.label.text = array[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
}
Custom Cell:
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var toggleSwitch: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
I realize there isn't code trying to store this data because I haven't been successful. Any ideas would be helpful. The project currently uses the MVC model and I believe that is the answer but just need some help.
I would recommend to you create cellViewModel class and keep array of it instead of just string. You cellViewModel may look like,
class CellViewModel {
let title: String
var isOn: Bool
init(withText text: String, isOn: Bool = false /* you can keep is at by default false*/) {
self.title = text
self.isOn = isOn
}
Now, build array of CellViewModel
let array =["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
var cellViewModels = [CellViewModel]()
for text in array {
let cellViewModel = CellViewModel(withText: text)
cellViewModels.append(cellViewModel)
}
Change your tableVieDelegate function to :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let cellViewModel = cellViewModels[indexPath.row]
cell.label.text = cellViewModel.title
cell.toggleSwitch.isOn = cellViewModel.isOn
cell.delegate = self
return cell
}
In you Custom Cell class, add this protocol :
protocol CellActionDelegate: class {
func didChangeSwitchStateOnCell(_ cell: CustomTableViewCell)
}
Add delegate as property in your custom cell,
weak var delegate: CellActionDelegate?
Also, on switch change, add this line,
delegate?.didChangeSwitchStateOnCell(self)
Now, your viewController should register and listen to this delegate :
I have added line cellForRowAtIndexPath to register for delegates. To listen this delegate, add this function in your VC.
func didChangeSwitchStateOnCell(_ cell: CustomTableViewCell) {
let indexPath = tableView.indexPath(for: cell)
cellViewModels[indexPath.row].isOn = cell.toggleSwitch.isOn
}
start creating a model for example :
struct item {
var id: String
var name: String
var isActivated: Bool
init(id: String, name: String, isActivated: Bool) {
self.id = id
self.name = name
self.isActivated = isActivated
}
}
let item1 = item(id: "1", name: "One", isActivated: false)
let item2 = ...........
let item3 = ...........
let items [item1, item2, item3]
With that you can trigger the boolean if it's activated or not.
You will also have to take a look to https://developer.apple.com/documentation/uikit/uitableviewcell/1623223-prepareforreuse I think.