Specifying Segue for UITableViewCell - ios

I have a collection of items I'm putting into a menu for the time being while I work out my layout planning but I am having trouble with figuring out how to work the DidSelectRow with the indexPath.row -
I have the following code:
import UIKit
class DispatchViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var videos: [DispatchMenuItem] = []
override func viewDidLoad() {
super.viewDidLoad()
videos = createArray()
self.tableView.rowHeight = 65.0
tableView.dataSource = self
}
func createArray() -> [DispatchMenuItem] {
let video1 = DispatchMenuItem(image: #imageLiteral(resourceName: "truckUnloadIcon"), title: "Open Manifests")
let video2 = DispatchMenuItem(image: #imageLiteral(resourceName: "podIcon"), title: "Customer Pickup")
return [video1, video2]
}
}
extension DispatchViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let video = videos[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "DispatchMenuItemCell") as! DispatchMenuItemCell
cell.setVideo(video: video)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
print(indexPath)
if indexPath.row == 1{
self.performSegue(withIdentifier: "goToOpenManifests", sender: nil)
}else if indexPath.row == 2{
self.performSegue(withIdentifier: "goToCustomerPickupForm", sender: nil)
}
}
}
You can see near the bottom my DidSelectRow, but it doesn't want to print out the indexPath.row so I am assuming that the row being selected is somehow now being passed along through the extension.
Are there any suggestions? I've usually done this through just a UITableViewController and have never had this issue, but somewhere here I seem to be getting stuck.

It will not print out the indexPath as you have not set the delegate. Implementing just the method is not sufficient, tableView needs to know who is going to implement the delegate methods.
There is a difference between delegate and datasource.
tableView.dataSource will help you to fetch numberofSections, numberofRowsInASection, TableCell Object
tableView.delegate will help you to manage the selection and deselection.
override func viewDidLoad() {
super.viewDidLoad()
videos = createArray()
self.tableView.rowHeight = 65.0
tableView.dataSource = self
//Add this
tableView.delegate = self
}

You just need to add below the first line of code in viewDid load method and improve your code like below.
tableView.delegate = self
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
print(indexPath)
if indexPath.row == 0 {
self.performSegue(withIdentifier: "goToOpenManifests", sender: nil)
}else if indexPath.row == 1 {
self.performSegue(withIdentifier: "goToCustomerPickupForm", sender: nil)
}
}

Related

app crashes when cell is clicked and segueing to a different view controller

I have a view controller with a table view and a search bar. I'd like to send a user to one of the three other view controllers based on the text in the cell.
SearchViewController:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (searchBar.text == search[indexPath.row].cleanName) {
performSegue(withIdentifier: "songs", sender: self)
} else if (searchBar.text != search[indexPath.row].cleanName) {
performSegue(withIdentifier: "artist", sender: self)
} else {
performSegue(withIdentifier: "albums", sender: self)
}
}
Whenever I select the cell in the first view controller, my app crashes and I get Thread 1: signal SIGABRT 'unable to dequeue a cell with identifier SearchTableViewCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard' This error says that I must register a nib for my song view controller but I already did.
SearhesViewController:
class SearchesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchesBar: UISearchBar!
var searchActive: Bool = false
var search = [Search]()
let filePath = "http://127.0.0.1/musicfiles"
var songs = [Songs]()
var artists = [Artist]()
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "SearchTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "SearchTableViewCell")
tableView.delegate = self
tableView.dataSource = self
searchesBar.delegate = self
retriveData()
print(search)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
songs = songs.filter({ (songName) -> Bool in
return songName.songname.lowercased().range(of: searchText.lowercased()) != nil
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchActive) {
return search.count
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchTableViewCell", for: indexPath) as! SearchTableViewCell
cell.mainLabel!.text = search[indexPath.row].cleanName
cell.secondLabel!.text = songs[indexPath.row].artistId
cell.cellImage!.image = UIImage(named: songs[indexPath.row].cover)
return cell;
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let searchSong = search[indexPath.row].cleanName
let searchArtist = search[indexPath.row].artistid
let fileURLString = "\(filePath)\(searchSong)\(searchArtist)"
print(fileURLString)
}
The problem is that there is no such nib. Therefore your call to
let nib = UINib(nibName: "SearchTableViewCell", bundle: nil)
yields nil, and your call to
tableView.register(nib, forCellReuseIdentifier: "SearchTableViewCell")
does nothing. You are saying the words, but you are not actually registering any nib. Hence the crash.
Make sure your cell is properly configured on a Storyboard:
Go to your cell on storyboard
In Identity Inspector, the cell must be set to Custom Class
SearchTableViewCell
In Attibutes Inspector, the cell Identifier must be set to SearchTableViewCell

Issues displaying a table view cell from one view controller to another

Right now I am trying to move information from my goal cell into a new table view cell, and am having difficulty getting the cell to display.
Here is the code for my goal cell.
import UIKit
class GoalsViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var Goals: [String] = ["goal 1", "goal 2", "goal 3"]
let theEmptyModel: [String] = ["No data in this section."]
var valueToPass = ""
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func showGoalSelected() {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
let popUp = GoalSelectedPopUp()
self.view.addSubview(popUp)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "GoalConversationsCell_1") {
let viewController = segue.destination as! ActiveGoalsViewController
viewController.Goals.append([valueToPass])
}
}
}
extension GoalsViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Goals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalCell_1", for: indexPath)
cell.textLabel?.text = Goals[indexPath.row]
cell.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.textLabel?.numberOfLines = 3
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
valueToPass = Goals[indexPath.row]
performSegue(withIdentifier: "activeGoalsSegue", sender: self)
Goals.remove(at: indexPath.row)
if Goals.count != 0 {
showGoalSelected()
} else {
Goals.append(contentsOf: theEmptyModel)
}
tableView.reloadData()
}
}
Here is the goal cells storyboard with the push segue connecting it to the other table view.
That other table view is shown below.
Here is the code for this new tableview.
import UIKit
class ActiveGoalsViewController: UIViewController {
#IBOutlet weak var goalTableView: UITableView!
let sections: [String] = ["Mark as Complete:", "History:"]
var goals: [[String]] = [[], []]
let theEmptyModel: [String] = ["No data in this section."]
extension ActiveGoalsViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Goals[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TodayGoalViewCell_1", for: indexPath) as? GoalTableViewCell
cell?.goalLabel.text = Goals[indexPath.section][indexPath.row]
cell?.cellDelegate = self
cell?.index = indexPath
return cell!
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return Goals.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
if Goals[0] != theEmptyModel {
Goals[1].append(Goals[0][indexPath.row])
if Goals[1].first!.contains("No data in this section.") {
Goals[1].removeFirst()
}
Goals[0].remove(at: indexPath.row)
if Goals[0].count == 0 {
Goals[0].append(contentsOf: theEmptyModel)
}
tableView.reloadData()
}
}
}
Once the goal is selected, it sends me to the new storyboard, but this new view does not display the goal that was just added. Can someone help me figure out why this isn't working? Thanks.
I think in the second view controller you need to access the "goals" variable with a lower case g rather then the "Goals" variable with an upper case G.

reloadData() calls cellForRowAtIndexPath for every cell

So I'm having this issue that when reloadData() is called after the initial API call, it calls willDisplayCell method which when the last cell is displayed will load more data (API returns 10 data at a time).
However in the view it can show only 4 - 5 cells as I set the row height to 175 points manually. Does anyone know why is this happening?
If this is how the tableView works what can I do to make my tableView to load only 10 initially?
Following is my code.
class MainViewController: UIViewController {
var tableView: UITableView!
var photoViewmodel: PhotoViewModel!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Home"
initTableView()
photoViewmodel = PhotoViewModel()
photoViewmodel.loadNewPhotos {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
func initTableView() {
tableView = UITableView.init(frame: self.view.frame, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 175
tableView.separatorStyle = .none
tableView.register(HomeTableViewCell.nib, forCellReuseIdentifier: HomeTableViewCell.identifier)
view.addSubview(tableView)
}
}
extension MainViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return photoViewmodel.photos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRowAt")
let cell = tableView.dequeueReusableCell(withIdentifier: HomeTableViewCell.identifier, for: indexPath) as! HomeTableViewCell
cell.tag = indexPath.row
cell.configureCell(url: photoViewmodel.photos[indexPath.row].regularImgUrl, cacheKey: indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
print("willDisplay")
let lastCell = photoViewmodel.photos.count - 1
if indexPath.row == lastCell {
print("add 10 more photos")
photoViewmodel.loadNewPhotos(
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
}
}

Not receiving data in detailview

I have a TableView that when the user clicks it needs to show a detail view displaying the name of the row it clicked. I populate the tableView using a Json call but I can't figure out what I'm doing wrong.
This is my bits of code of ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var valueToPass:String!
#IBOutlet weak var tableView: UITableView!
// Instantiate animals class
var items = [animalObject]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
addDummyData()
}
func addDummyData() {
RestManager.sharedInstance.getRandomUser(onCompletion: {(json) in
if let results = json.array {
for entry in results {
self.items.append(animalObject(json: entry))
}
DispatchQueue.main.sync{
self.tableView.reloadData()
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier") as! CustomTableViewCell!
let animal = self.items[indexPath.row]
cell?.label.text = animal.name
return cell! //4.
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
// let indexPath = tableView.indexPathForSelectedRow!
var currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
print(currentCell.textLabel?.text)
valueToPass = currentCell.textLabel?.text
print(valueToPass)
performSegue(withIdentifier: "detailView", sender: indexPath)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "detailView") {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! DetailViewController
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
}
}
}
and my detailView only has the following info
#IBOutlet weak var tooPass: UILabel!
var passedValue: String!
override func viewDidLoad() {
super.viewDidLoad()
print("DETAILS")
print(passedValue)
tooPass.text = passedValue
}
I'm not sure if the preparedToSegue is firing a little bit earlier because my terminal looks like this:
I used as a reference the following question any guidance will be appreciated
Try this for didSelectRowAt method.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
valueToPass = self.items[indexPath.row]
print(valueToPass)
performSegue(withIdentifier: "detailView", sender: indexPath)
}
Just pass the entire item in the items array.
Make the DetailViewController's passed item of type item. Then just access item.whateverPropertyRequired.
The problem is that in your cellForRow method, you are casting your cell to CustomTableViewCell type while in your didSelectRow, you are casting it as UITableViewCell. Change
var currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
in your didSelectRow to
var currentCell = tableView.cellForRow(at: indexPath)! as CustomTableViewCell
Good approach here is to use view model. Create model which contains your items and data associated with them. And at segue pass your data model. It’s bad coding, getting data right from cell on didSelect method, unless some ui working on that cell

Switch order of editing accessory and reorder control in UITableViewCell

I'm very new to iOS development and just recently discovered editing accessories for table view cells. I'm trying to make a playlist app where, in editing mode, people can rearrange the order of the songs, or tap a song to edit its metadata.
Currently, I have disclosure indicators that only appear on the cells when the table is being edited, but they appear to the left of the reorder control: App screenshot Here's my ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var rowToBeEdited: Int?
var songs: Song = [...sample data...]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "songCell", for: indexPath) as! SongCell
let song = songs[indexPath.row]
cell.songNameLabel.text = song.name
cell.albumNameLabel.text = song.album
cell.artistsLabel.text = song.artists
cell.albumImage.image = song.albumImage
cell.editingAccessoryType = (tableView.isEditing) ? .disclosureIndicator : .none
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Add tableView functions for editing
#IBAction func editPlaylistButtonTapped(_ sender: UIBarButtonItem) {
tableView.isEditing = !tableView.isEditing
tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.isEditing {
let song = songs[indexPath.row]
rowToBeEdited = indexPath.row
performSegue(withIdentifier: "showDetailVCToEdit", sender: song)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
//tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
}
// other code not shown...
}
I've seen in some apps that the two controls are switched around (like in this app) and I was wondering how to achieve that.
Thanks in advance

Resources