I'm getting an error with my code that says...
"Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'unable to dequeue a cell
with identifier ItemCell - must register a nib or a class for the
identifier or connect a prototype cell in a storyboard'?"
I've set the cell to ItemCell and I've run Product > Clean. It still can't find it. Can anyone see what I've done wrong? I've included a screen shot of my code and my storyboard.
import UIKit
class ItemsViewController: UITableViewController {
var itemStore: ItemStore!
#IBAction func addNewItem(_ sender: UIButton) {
let newItem = itemStore.createItem()
if let index = itemStore.allItems.index(of: newItem) {
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
#IBAction func toggleEditingMode(_ sender: UIButton) {
if isEditing {
sender.setTitle("Edit", for: .normal)
setEditing(false, animated: true)
} else {
sender.setTitle("Done", for: .normal)
setEditing(true, animated: true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let insets = UIEdgeInsetsMake(statusBarHeight, 0, 0, 0)
tableView.contentInset = insets
tableView.scrollIndicatorInsets = insets
tableView.rowHeight = 65
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
let item = itemStore.allItems[indexPath.row]
cell.nameLabel.text = item.name
cell.serialNumberLabel.text = item.serialNumber
cell.valueLabel.text = "$\(item.valueInDollars)"
return cell
}
override func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCellEditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let item = itemStore.allItems[indexPath.row]
let title = "Delete \(item.name)"
let message = "You sure ya wanna delete this?"
let ac = UIAlertController(title: title,
message: message,
preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
ac.addAction(cancelAction)
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in
self.itemStore.removeItem(item)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
})
ac.addAction(deleteAction)
present(ac, animated: true, completion: nil)
}
}
override func tableView(_ tableView: UITableView,
moveRowAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath) {
itemStore.moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
This is my ItemCell
import UIKit
class ItemCell: UITableViewCell {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var serialNumberLabel: UILabel!
#IBOutlet var valueLabel: UILabel!
}
And my ItemStore
import UIKit
class ItemStore {
var allItems = [Item] ()
#discardableResult func createItem() -> Item {
let newItem = Item(random: true)
allItems.append(newItem)
return newItem
}
func removeItem(_ item: Item) {
if let index = allItems.index(of: item) {
allItems.remove(at: index)
}
}
func moveItem(from fromIndex: Int, to toIndex: Int) {
if fromIndex == toIndex {
return
}
let movedItem = allItems[fromIndex]
allItems.remove(at: fromIndex)
allItems.insert(movedItem, at: toIndex)
}
}
You have to check your custom class name in storyboard as ItemCell.swift as your class name.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:ItemCell = tableView.dequeueReusableCell(withIdentifier: "ItemCell") as! ItemCell
let item = itemStore.allItems[indexPath.row]
cell.nameLabel.text = item.name
cell.serialNumberLabel.text = item.serialNumber
cell.valueLabel.text = "$\(item.valueInDollars)"
return cell
}
Check if you have set the correct custom class for the cell (ItemCell in this case) in Identity Inspector.
Related
Can anyone help me that how to show index path both table view and collection view index. In my case table view cell contain many collection view cell. And collection view cell contains one button when user pressed the button an alert show which show the pressed button from table view index (i.e 2) and collection view index (i.e 4).
Problem is that it show Collection View cell Index Path properly but don't show Table View Cell index Path.
import UIKit
class ViewController: UIViewController{
//MARK:- Variable Declaration
let arryOfCollectionCell = ["1","2","3","4","5","6","7","8"]
let arryOfTableCell = ["1","2","3","4","5","6","7","8","9","10"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
//MARK:- Table View
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arryOfTableCell.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
private func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? CellForTableView else { return }
tableViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: indexPath.row)
}
}
//MARK:- Collection View
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return arryOfCollectionCell.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CellForCollectionView
cell.myButtonTapAction = { cell in
let alert = UIAlertController(title: "You Got it 😊!", message: "Selected Index Path Number is \(indexPath[1])", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
return cell
}
}
Here is my Collection View Cell:
import UIKit
class CellForCollectionView: UICollectionViewCell {
#IBOutlet weak var clickBtnOut: UIButton!
var myButtonTapAction: ((Any) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func clickBtnAct(_ sender: UIButton) {
myButtonTapAction?(self)
}
}
Here is my CellForTabelView Class
import UIKit
class CellForTableView: UITableViewCell {
#IBOutlet weak var collectionViewOut: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
You can set tableViewCell index as tag of collectionView in willDisplayCell and can get it back in cellForItemAt delegate method
add tableViewCell.collectionViewOut.tag = indexPath.row in willDisplayCell
private func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? CellForTableView else { return }
tableViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: indexPath.row)
tableViewCell.collectionViewOut.tag = indexPath.row
}
and replace
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CellForCollectionView
cell.myButtonTapAction = { cell in
let alert = UIAlertController(title: "You Got it 😊!", message: "Selected Index Path Number is \(indexPath[1]), tableCellIndex:\(collectionView.tag)", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
return cell
}
I have added tableview in a viewcontroller, then In tableViewCell I have managed the collectionView .. Updated my answer to show you same expected result in alert controller
//
// ViewController.swift
// SampleTable
//
// Created by Mohammed Gomaa on 7/8/20.
// Copyright © 2020 Mohammed Gomaa. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let arryOfTableCell = ["1","2","3","4","5","6","7","8","9","10"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arryOfTableCell.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell") as! CellForTableView
cell.tableCellIndexPath = indexPath.row
return cell
}
}
class CellForCollectionView: UICollectionViewCell {
#IBOutlet weak var button: UIButton!
var tableCellIndex: Int = 0
var collectionCellIndex: Int = 0
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func clickBtnAct(_ sender: UIButton) {
let alert = UIAlertController(title: "You Got it 😊!", message: "Selected Collection Index is :\(collectionCellIndex) ,, Selected Table Index is :\(tableCellIndex)", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
class CellForTableView: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let arryOfCollectionCell = ["1","2","3","4","5","6","7","8"]
var tableCellIndexPath = 0
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arryOfCollectionCell.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! CellForCollectionView
cell.tableCellIndex = self.tableCellIndexPath
cell.collectionCellIndex = indexPath.row
cell.button.setTitle("t:\(self.tableCellIndexPath), c:\(indexPath.row)", for: .normal)
return cell
}
}
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if parentResponder is UIViewController {
return parentResponder as? UIViewController
}
}
return nil
}
}
I have implemented a UIActivityViewController in Swift 5 via a nav bar button and upon click this should share the contents of an array called sharedData.
When clicking the button, the share options popup is empty.
Here is my func code:
#IBAction func shareNavButton(_ sender: UIBarButtonItem) {
let shareItems: [Any] = [sharedData]
let activityVC = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
activityVC.popoverPresentationController?.barButtonItem = sender
self.present(activityVC, animated: true, completion: nil)
}
Here is my entire VC code:
class FavoritesViewController: UIViewController {
#IBOutlet var tableView: UITableView!
#IBAction func shareNavButton(_ sender: UIBarButtonItem) {
let shareItems: [Any] = [sharedData]
let activityVC = UIActivityViewController(activityItems: shareItems, applicationActivities: nil)
activityVC.popoverPresentationController?.barButtonItem = sender
self.present(activityVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
convertArray()
}
}
extension FavoritesViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sharedData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = sharedData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
-> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Remove") { (_, _, completionHandler) in
sharedData.remove(at: indexPath.row)
saveArray()
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
completionHandler(true)
}
if #available(iOS 13.0, *) {
deleteAction.image = UIImage(systemName: "trash")
} else {
// Fallback to default action
}
deleteAction.backgroundColor = .systemRed
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
return configuration
}
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let copyAction = UIContextualAction(style: .normal, title: "Copy") { (_, _, completionHandler) in
let cell = tableView.cellForRow(at: indexPath)
UIPasteboard.general.string = cell?.textLabel?.text
completionHandler(true)
}
if #available(iOS 13.0, *) {
copyAction.image = UIImage(systemName: "doc.on.clipboard")
} else {
// fall back to default action
}
copyAction.backgroundColor = .systemBlue
let configuration = UISwipeActionsConfiguration(actions: [copyAction])
return configuration
}
}
You are using an array of string inside another array. Your sharedData is an array.
let shareItems: [Any] = [sharedData]
Try with this code:
let shareItems: [Any] = sharedData
I want to make the transition from the UITableView cell to the view controller. My didSelectRowAt isn't working.
Here is code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var goals = [String]()
#IBAction func onAddTapped() {
let alert = UIAlertController(title: "Add Goal", message: nil, preferredStyle: .alert)
alert.addTextField { (dessertTF) in
dessertTF.placeholder = "Enter Goal"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
guard let goal = alert.textFields?.first?.text else { return }
print(goal)
self.add(goal)
}
alert.addAction(action)
present(alert, animated: true)
}
func add(_ goal: String) {
let index = 0
goals.insert(goal, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "segue", sender: self)
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let dessert = goals[indexPath.row]
cell.textLabel?.text = goals
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
desserts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
The problem is that you aren't creating your cells correctly.
Inside of your cellForRow function change
let cell = UITableViewCell()
to this:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
Make sure that in your storyboard file, that you set the cell identifier to "Cell"
i used Core Date to save names and phone numbers
i would like to make a call by touching cell
here is my code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
do {
let people = try PersistenceService.context.fetch(fetchRequest)
self.people = people
self.tableView.reloadData()
} catch {}
}
#IBAction func onPlusTapped() {
let alert = UIAlertController(title: "Add Person", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
alert.addTextField { (textField) in
textField.placeholder = "Phone number"
textField.keyboardType = .numberPad
}
let action = UIAlertAction(title: "Post", style: .default) { (_) in
let name = alert.textFields!.first!.text!
let phoneNumber = alert.textFields!.last!.text!
let person = Person(context: PersistenceService.context)
person.name = name
person.phoneNumber = phoneNumber
PersistenceService.saveContext()
self.people.append(person)
self.tableView.reloadData()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
override var prefersStatusBarHidden: Bool {
return true
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = people[indexPath.row].name
cell.detailTextLabel?.text = people[indexPath.row].phoneNumber
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
people.remove(at: indexPath.item)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
UIApplication.shared.openURL(NSURL(string: "tel://" + (people[indexPath.row].phoneNumber?.description)!)! as URL)
print(people[indexPath.row].phoneNumber?.description)
}
}
No need to add an #IBAction, you can use didSelectRow from UITableViewDelegate
You are already implementing the didSelectRowAt use IBActions only inside UITableView if you have a UIButton inside the UITableViewCell
My code right now just lists things you manually enter. However when the user switches view controllers the code disappears. I tried to use userdefualts to save my current code in the number of rows in selection function but it does not save the items in the tableview cells. I just want to save whatever is in the tableview cells.
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var items: [String] = [""]
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
listTableView.dataSource = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listitem") as! ItemTableViewCell
cell.itemLabel.text = items[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let userDefaults = UserDefaults.standard
userDefaults.setValue(items, forKey: "items")
userDefaults.synchronize()
return items.count
}
func alert(){
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter "
}
let add = UIAlertAction(title: "Add", style: .default){
(action) in
let textfield = alert.textFields![0]
self.items.append(textfield.text!)
self.listTableView.reloadData()
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(alert) in
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}}
You have a number of issues in your code:
You never load your data back from UserDefaults. - This is the big one
There is no need to call synchronise.
You should save your data when data is added/deleted, not in numberOfRowsInSection.
It will look nicer if you insert a new row rather than reloading the whole table
I would suggest something like:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var items = [String]()
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
listTableView.dataSource = self
self.items = UserDefaults.standard.stringArray(forKey:"items") ?? [String]()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listitem") as! ItemTableViewCell
cell.itemLabel.text = items[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func saveData() {
userDefaults.standard.set(items, forKey: "items")
}
func alert(){
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter "
}
let add = UIAlertAction(title: "Add", style: .default){
(action) in
guard let textfield = alert.textFields?.first else {
return
}
if let newText= textfield.text {
self.items.append(newText)
saveData()
let indexPath = IndexPath(row: items.count - 1, section: 0)
self.listTableView.insertRows(at: [indexPath], with: .automatic)
}
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(alert) in
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
saveData()
}
}
Also, you shouldn't really use UserDefaults for data storage this way, but I presume this is just a simple learning exercise.