Need to update tableView - swift

I'm studying programming in Swift, and I've got this example from a book with a mistake (commented). How can I fix this and update tableView while pressing the Add button? I've read many solutions, but none seem to work. Also, tableView updated when I reloaded simulator. I've been stuck with this for two days, and it's driving me mad! :)
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
managedObjectContext = appDelegate.managedObjectContext! as NSManagedObjectContext
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadBooks() -> Array<AnyObject> {
var error: NSError? = nil
var fetchRequest = NSFetchRequest(entityName: "Book")
let result: [AnyObject] = managedObjectContext!.executeFetchRequest(fetchRequest, error:&error)!
return result
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return loadBooks().count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
var book: Book = loadBooks()[indexPath.row] as! Book
cell.textLabel!.text = book.title
return cell
}
#IBAction func addNew(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Book", inManagedObjectContext:managedObjectContext)
var book = Book(entity: entity!,insertIntoManagedObjectContext:managedObjectContext)
book.title = "My Book:" + String(loadBooks().count)
var error: NSError?
managedObjectContext.save(&error)
myTableView.reloadData() //mistake!
}
}

You have to declare your an outlet for your tableview which is probably set up in the according storyboards view controller.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var managedObjectContext: NSManagedObjectContext!
#IBOutlet weak var myTableView: UITableView!
// your code
}
Please don't forget to connect your table view instance from the storyboard with your new created outlet, by opening code and storyboard side-by-side and draw a line from the dot next to the outlet to the tableview.
Or you can use the way apple describes here (https://developer.apple.com/library/ios/recipes/xcode_help-IB_connections/chapters/CreatingOutlet.html) to create your outlet from the table view on the storyboard.

edit:
Nevermind, i realized that if you wouldnt have had connected the dataSource and delegate, the crash would have happend way before the addNew().
Kie's answer is correct, however to see an actual result you also need to connect the dataSource and delegate of the tableView to your class.
You can do this either in storyboard, or in code in the viewDidLoad method.
override func viewDidLoad(){
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
// Additional code
}

Related

How to open View Controller in Table View with Button by using Objc in Swift?

Stackoverflow
I know how to make a button in the table view cells with website links, rate, mail, and many things. However, How could I open the view controller with the instantiateViewController in the #Objc func's statements?
For example.
Create a new Table View Cell folder called FeedBackButtonsTableViewCell
class FeedBackButtonsTableViewCell: UITableViewCell {
#IBOutlet weak var ButtonCells: UIButton!
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
}
}
Let create a new view controller folder called
class FeedbackViewController: UIViewController {
#IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Feedback"
}
}
add the extension to calling the view controller to UITableViewDataSource and UITableViewDelegate and create a obj func statements inside of the second FeedbackViewController with UITableViewDataSource and UITableViewDelegate under the cells.
extension FeedbackViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
if indexPath.row == 1 {
buttonCell = TableView.dequeueReusableCell(withIdentifier: "ButtonCells") as? FeedBackButtonsTableViewCell
buttonCell?.ButtonCells.addTarget(self,action: #selector(LearnMore),for: .touchUpInside)
buttonCell?.ButtonCells.tag = indexPath.row
return buttonCell!
}
#objc func LearnMore() {
// How could I write to open the view controller with UIButton in the Table View Cells?
}
}
Thank you for bring a kind of help! :)
Simple solution could be to use procol.
protocol CellActionDelegate{
func didButtonTapped(index: Int)
}
Now confirm the protocol in FeedbackViewController. Take index and actionDelegate properties in your UITableViewCell subclass.
class FeedBackButtonsTableViewCell: UITableViewCell{
var actionDelegate: CellActionDelegate?
var index: Int?
.....
// Take Action of UIButton here
#IBAction func more(_ sender: Any) {
if let delegate = self.actionDelegate{
delegate.didButtonTapped(index!)
}
}
}
Now in your FeedbackViewController set actionDelegate & Corresponding index in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
you can open anotherView controller from func didButtonTapped(index: Int) definition .
extension FeedbackViewController:CellActionDelegate{
func didButtonTapped(index: Int) {
let storybord = UIStoryboard(name: "Main", bundle: nil)
guard let controller = storybord.instantiateViewController(withIdentifier: "AnotherControllerIdentfier") as? AnotherViewController else{
fatalError("Could not finc another view controller")
}
self.present(controller, animated: true, completion: nil)
}
}
#objc func LearnMore() {
let viewController = FeedbackDetailsViewController()// creation of viewController object differs depends on how you fetch the UI, means either you are using storyboard or xib or directly making ui in code.
self.navigationController?.pushViewController(viewController, animated: true)
}

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value after I try to reloadData()

I am creating an app that scans barcodes and adds the barcode value (and name and date but working on that later). After I scan the barcode, I call the add method which should add it to my tableview but I am hitting an error at my reloadData().
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var data: [Ticket] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
data = createArray()
}
func createArray() -> [Ticket] {
let video1 = Ticket(number: "123456789", name: "First Name - Last Name", date: "May 18th, 2019, 7 am")
let video2 = Ticket(number: "123456789", name: "First Name - Last Name", date: "May 18th, 2019, 7 am")
return [video1, video2]
}
}
extension FirstViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let ticket = data[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "TicketCell") as! TicketCell
cell.setTicket(ticket: ticket)
return cell
}
func add (_ code: String)
{
let tic = Ticket(number: code, name: "First Name - LastName", date: "May 18th, 2019, 10 am")
print(tic.number1)
print(tic.name1)
print(tic.date1)
data.append(tic)
self.tableView.reloadData() // Error Occurs Here
}
}
This is where I'm calling the add method from a different controller. The String "code" is the barcode's value:
func found(code: String) {
FirstViewController().add(code)
viewDidLoad()
}
Each time you add a barcode using FirstViewController(), you are creating a new instance of FirstViewController, you can use delegation to update the original instance.
First create a protocol with your add method
protocol BarcodeScanDelegate: class {
func add(_ code: String)
}
Extend FirstViewController to inherit BarcodeScanDelegate and move the add method
extension FirstViewController: BarcodeScanDelegate {
func add (_ code: String) {
let tic = Ticket(number: code, name: "First Name - LastName", date: "May 18th, 2019, 10 am")
self.data.append(tic)
self.tableView.reloadData()
}
}
Within the 2nd (barcode scan) view controller create a variable that keeps a reference to the above delegate
class BarcodeScanViewController: UIViewController {
weak var delegate: BarcodeScanDelegate?
func addScannedBarcode() {
self.delegate?.add("BARCODE_HERE")
}
}
Now when you launch 2nd (barcode scan) view controller, assign its variable delegate to self
class FirstViewController: UIViewController {
#IBOutlet var tableView: UITableView!
...
#objc func launchBarcodeScanner() {
let viewController = BarcodeViewController()
// or use following if you are using storyboard
// let viewController = self.storyboard!.instantiateViewController(withIdentifier: "BarcodeVC") as! BarcodeViewController
viewController.delegate = self // <<<
self.present(viewController, animated: true)
}
}
You have the tableView in FirstViewController class in storyboard.
But in this method you are not appending a ticket to the existing array. You are creating new instance for FirstViewController. The tableview is in storyboard and you are creating this FirstViewController instance programmatically. So the IBOutlet will be nil. So it is crashing.
func found(code: String) {
FirstViewController().add(code)//new FirstViewController instance
viewDidLoad()
}
Instead of adding the code from another view controller, pass the code to the FirstViewController using custom delegate and reload the tableview.

Populate tableview using contacts framework

I want to populate a tableview with contact details that I have entered in the app.
first i have a table view controller that will show the list of entered contacts. its swift file looks like this.
import Contacts
import ContactsUI
class thecontacts: UITableViewController, CNContactPickerDelegate, UIPickerViewDelegate {
let contacts = CNMutableContact()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell!
return cell
}
I want to populate this from another view controller. I want to populate this tableview with a persons name, and have it as a contact in the tableview. This view controllers swift file is:
import Contacts
class newContact: UIViewController {
//outlets
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
var tableViewController: newContact? = nil
override func viewDidLoad() {
}
#IBAction func addBuddy(sender: AnyObject) {
let contacts = CNMutableContact()
contacts.nickname = nameTextField!.text!;
contacts.emailAddresses = [CNLabeledValue(label: nil, value: emailTextField.text!)]
self.tableViewController?.addBuddy(contacts)
dismiss()
}
func dismiss(){
dismissViewControllerAnimated(true, completion: nil)
}
}
my tableViewController file is called thecontacts.swift. My new contact details viewController is called newContacts.swift.
the problem I have is that the details I enter into the newContact file are not appearing in my thecontacts table view.

Tab Bar Item hidden behind tableview / not being shown?

I have an empty view with a tab bar pictured below, when i load a routine a table appears containing the contents, however it seems to overlay the tab bar killing off app navigation. Its not sized in the storyboard to overlay it and its constraint locked to not do so, so im unsure why this is happening, pics of the issue and VC's code below:
VC Code:
import Foundation
import UIKit
import CoreData
class RoutineController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - DECLARATIONS
#IBAction func unwindToRoutine(segue: UIStoryboardSegue) {}
#IBOutlet weak var daysRoutineTable: UITableView!
#IBOutlet weak var columnHeaderBanner: UIView!
#IBOutlet weak var todaysRoutineNavBar: UINavigationBar!
#IBOutlet weak var addTOdaysRoutineLabel: UILabel!
let date = Date()
let dateFormatter = DateFormatter()
let segueEditUserExerciseViewController = "editExerciseInRoutineSegue"
//This is the selected routine passed from the previous VC
var selectedroutine : UserRoutine?
// MARK: - VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
setupView()
daysRoutineTable.delegate = self
daysRoutineTable.dataSource = self
view.backgroundColor = (UIColor.customBackgroundGraphite())
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateStr = dateFormatter.string(from: date)
todaysRoutineNavBar.topItem?.title = dateStr + " Routine"
}
// MARK: - VIEWDIDAPPEAR
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
}
// MARK: - TABLE UPDATE COMPONENTS
private func setupView() {
updateView()
}
// MARK: - TABLE SETUP
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = self.selectedroutine?.userexercises?.count
{
print("exercises: \(count)")
return count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
// MARK: - VIEW CONTROLER ELEMENTS VISIBILITY CONTROL
fileprivate func updateView() {
var hasUserExercises = false
if let UserExercise = self.selectedroutine?.userexercises {
hasUserExercises = UserExercise.count > 0
}
addTOdaysRoutineLabel.isHidden = hasUserExercises
columnHeaderBanner.isHidden = !hasUserExercises
daysRoutineTable.isHidden = !hasUserExercises
}
// MARK: - SETTING DATA FOR A TABLE CELL
func configure(_ cell: TodaysRoutineTableViewCell, at indexPath: IndexPath) {
if let userExercise = selectedroutine?.userexercises?.allObjects[indexPath.row]
{
print("\((userExercise as! UserExercise).name)")
cell.todaysExerciseNameLabel.text = (userExercise as! UserExercise).name
cell.todaysExerciseRepsLabel.text = String((userExercise as! UserExercise).reps)
cell.todaysExerciseSetsLabel.text = String((userExercise as! UserExercise).sets)
cell.todaysExerciseWeightLabel.text = String((userExercise as! UserExercise).weight)
}
}
}
requested table constraints
Debug hierarchy
The Segue that sends the user back to the view that looses its tab bar
if segue.identifier == "addToTodaySegue" {
let indexPath = workoutTemplateTable.indexPathForSelectedRow
let selectedRow = indexPath?.row
print("selected row\(selectedRow)")
if let selectedRoutine = self.fetchedResultsController.fetchedObjects?[selectedRow!]
{
if let todaysRoutineController = segue.destination as? RoutineController {
todaysRoutineController.selectedroutine = selectedRoutine
}
}
}
I also feel perhaps the viewDidAppear code may cause the issue, perhaps the super class?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.daysRoutineTable.reloadData()
self.updateView()
Updated storyboard image
I suspect you need to embed your viewController in a UINavigationController.
Consider the following setup:
I suspect your setup is like the upper one:
TapBar -> ViewController -show segue-> ViewController
Which results in a hidden tapbar, like in your description:
While the bottom setup:
TapBar -> NavigationCntroller -rootView-> ViewController -show segue-> ViewController
results in:
which is what you want, how I understood.
Update
It's hard to see. The screenshot of your Storyboard is in pretty low resulution, but the segues look wrong. Double check them. A Segue of type show (e.g push) looks like this:
Also clear project and derived data. Segue type changes sometime are ignored until doing so.
Try calling this self.view.bringSubviewToFront(YourTabControl).
The previous suggestion should work. But the content at the bottom part of tableview will not be visible as the tabbar comes over it. So set the bottom constraint of tableview as the height of tabbar.

NSTableView Delegate methods won't get called

I'm currently trying to parse the reddit headlines from a specific subreddit and display these in an NSTableView. The thing is, the numberOfRows function gets called and returns the correct integer but the tableView delegate function never gets called.
As far as I can see everything is wired up correctly in the code.
ViewController:
#IBOutlet weak var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Downloader.load(url: URL(string: "https://www.reddit.com/r/" + "gaming" + ".json")!){
(result) in
let tvc = TableViewController(data: result)
self.tableView.delegate = tvc
self.tableView.dataSource = tvc
self.tableView.reloadData()
}
}
TableViewController:
class TableViewController: NSObject{
var json: JSON!
init(data: JSON) {
super.init()
self.json = data
}
}
extension TableViewController : NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return JSONFormatController.getTitlesFrom(json: json).count
}
}
extension TableViewController : NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var titles = JSONFormatController.getTitlesFrom(json: json)
if let cell = tableView.make(withIdentifier: "entry", owner: nil) as? NSTableCellView {
cell.textField?.stringValue = titles[row]
return cell
} else {
return nil
}
}
}
The result variable and getTitlesFrom method do work, I checked these.
I think your issue is that your TableViewController object is getting deallocated because you are not keeping a reference to it. Try this:
#IBOutlet weak var tableView: NSTableView!
var tvc : TableViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Downloader.load(url: URL(string: "https://www.reddit.com/r/" + "gaming" + ".json")!){
(result) in
self.tvc = TableViewController(data: result)
self.tableView.delegate = self.tvc
self.tableView.dataSource = self.tvc
self.tableView.reloadData()
}
}
Explanation: tvc is a local variable of the download block which is getting deallocated after it has executed. Presumably your assumption is that storing the tvc in delegate and/or dataSource is keeping tvc alive. But they are not, they are weak references.