When I run the below code, I am getting the proper JSON version and list of bonus codes in my console log, but the table view itself is not showing any data.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var bonuses = [JsonFile.JsonBonuses]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON {
self.tableView.reloadData()
}
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = bonuses[indexPath.row].name.capitalized
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? HeroViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
// MARK: - Download JSON from ToH webserver
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")
URLSession.shared.dataTask(with: url!) { [weak self] (data, response, error) in
if error == nil {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data!)
DispatchQueue.main.async {
completed()
}
print("JSON Version \(posts.meta.version) loaded.")
print(posts.bonuses.map {$0.bonusCode})
self?.bonuses = posts.bonuses
} catch {
print("JSON Download Failed")
}
}
}.resume()
}
}
The code is based off a tutorial I found online that originally used DOTA character information to populate the data. I changed it to using my own JSON feed, which seems to be valid since I can see the bonus codes in the console, but isn't putting any data on display in the app.
The API is working Asynchronously, hence table is loaded before the API call finishes. So you have to reload the table after API fetches the results.
print(posts.bonuses.map {$0.bonusCode})
self?.bonuses = posts.bonuses
DispatchQueue.main.async {
//reload table in the main queue
self.myTableView.reloadData()
}
}
The issue is that you are calling the completion closure before filling the self.bonuses array, try putting it after that line.
I will also recommend putting these lines:
tableView.delegate = self
tableView.dataSource = self
Before calling the downloadJSON method.
Related
I am a novice to swift. This is my first assignment for UI development. I have done the exercise perfectly and the tableView showed up as expected. The code is as below:
import UIKit
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
}
}
extension ViewController: UITableViewDelegate{
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gameScoreCell", for: indexPath)
cell.textLabel?.text = "hello world"
cell.detailTextLabel?.text = "score"
return cell
}
}
However, when I followed the same step and tried to integrate it with my project (with a navigation controller), the table view does not show up. Did I miss anything?
import UIKit
class HightScoreVC: UIViewController {
#IBOutlet var rankingTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
rankingTable.delegate = self
rankingTable.dataSource = self
}
}
extension HightScoreVC: UITableViewDelegate{
}
extension HightScoreVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "gameScoreCell", for: indexPath)
cell.textLabel?.text = "hello world"
cell.detailTextLabel?.text = "123"
return cell
}
}
I think you must register your cell in ViewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
rankingTable.delegate = self
rankingTable.dataSource = self
rankingTable.register(UITableViewCell.self, forCellReuseIdentifier: "gameScoreCell")
}
Points to keep in mind while working with tableview in swift.
Make sure constriants of tableview are given properly.
You have connected the class to the view controller in the identity inspector.
Provide delegate and datasource in viewDidLoad() itself rather than storyboard for better practice.
If you are creating xib for a cell, make sure you have registered the cell for your tableview, or if you are providing prototype cell, make sure you provide dequeueReusableCell() method and initialize your cell for some specific class.
Simple example for a tableview with some prototype cell
import UIKit
class UsersListViewController: UIViewController, Storyboarded {
//MARK: - Variables
var coordinator: AuthenticationCoordinator?
var usersList: UsersList?
//MARK: - Outlets
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var progressBar: UIActivityIndicatorView!
#IBOutlet weak var btnAddUser: UIButton!
//MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
initializeView()
getUsersList()
}
//MARK: - Actions
#IBAction func addUserAction(_ sender: UIButton) {
coordinator?.presentAddUser()
}
//MARK: - File private functions
fileprivate func initializeView() {
self.title = "Users list"
progressBar.startAnimating()
btnAddUser.layer.masksToBounds = true
btnAddUser.layer.cornerRadius = btnAddUser.frame.height / 2
tableView.delegate = self
tableView.dataSource = self
}
fileprivate func getUsersList() {
guard let url = URL(string: ApiUrl.delayResponseURL.rawValue) else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else { return }
guard let data = data else { return }
guard let response = response as? HTTPURLResponse, (200 ..< 299) ~= response.statusCode else { return }
do {
guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { return }
guard let prettyJsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) else { return }
guard let responseData = try? JSONDecoder().decode(UsersList.self, from: prettyJsonData) else { return }
self.usersList = responseData
DispatchQueue.main.async {
self.progressBar.stopAnimating()
self.progressBar.alpha = 0
self.tableView.reloadData()
}
} catch {
return
}
}.resume()
}
}//End of class
//MARK: - UITableViewDelegate
extension UsersListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let index = usersList?.data[indexPath.row].userID {
coordinator?.startSingleUserVC(index)
tableView.deselectRow(at: indexPath, animated: true)
}
}
}//End of extension
//MARK: - UITableViewDataSource
extension UsersListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "singleUserCell") as? SingleUserTableViewCell {
if let url = URL(string: usersList?.data[indexPath.row].avatar ?? "") {
DispatchQueue.global().async {
guard let data = try? Data(contentsOf: url) else { return }
val currentUser = self.usersList?.data[indexPath.row]
DispatchQueue.main.async {
cell.initCell(data, currentUser.firstName, currentUser.email)
}
}
}
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usersList?.data.count ?? 1
}
}//End of extension
I'm trying to implement the didSelectRow function and perform a segue but when running the cells select and nothing happens.
I created a print statement that also doesn't run which proves that the function doesn't appear to be firing. Why would this be?
I have checked the identifier is correct and have researched these for a few hours going through many stack overflow threads but with little luck.
import UIKit
import CoreData
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let viewController = ListNameViewController()
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var itemChoosen = 0
override func viewDidLoad() {
super.viewDidLoad()
homeListsTableView.delegate = self
homeListsTableView.dataSource = self
viewController.loadList()
}
#IBOutlet weak var homeListsTableView: UITableView!
#IBAction func templatesButton(_ sender: Any) {
tabBarController?.selectedIndex = 2
}
#IBAction func allListsButton(_ sender: Any) {
tabBarController?.selectedIndex = 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewController.listName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let result = viewController.listName[indexPath.row]
cell.textLabel?.text = ("\(String(result.listName!))")
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
context!.delete(viewController.listName[indexPath.row])
viewController.listName.remove(at: indexPath.row)
viewController.saveList()
homeListsTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "items2", sender: self)
print("selected")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
viewController.loadList()
homeListsTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homeListsTableView.reloadData()
}
}
ListNameViewController:
import UIKit
import CoreData
class ListNameViewController: UIViewController, UITableViewDelegate {
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var listName : [ListName] = []
override func viewDidLoad() {
super.viewDidLoad()
createButtonChange.isEnabled = false
//Objective-C Line used to keep checking if the text field is vaild before enabling the submit button
listNameValue.addTarget(self, action: #selector(textValidation), for: UIControl.Event.editingChanged)
}
#IBOutlet weak var listNameValue: UITextField!
#IBOutlet weak var locationOption: UITextField!
#IBOutlet weak var createButtonChange: UIButton!
#objc func textValidation() {
//Text Field Validation check before button is enabled
if listNameValue.text!.isEmpty {
createButtonChange.isEnabled = false
} else {
createButtonChange.isEnabled = true
}
}
// Create a new List
#IBAction func createButton(_ sender: Any) {
let newList = ListName(context: context!)
newList.listName = listNameValue.text
saveList()
self.navigationController!.popViewController(animated: true)
viewWillAppear(false)
}
func saveList() {
do {
try context!.save()
} catch {
print("Error saving context \(error)")
}
}
func loadList() {
let request : NSFetchRequest<ListName> = ListName.fetchRequest()
do{
listName = try context!.fetch(request)
} catch {
print("Error loading categories \(error)")
}
}
//Pass data to the HomeViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// let vc = segue.destination as! HomeViewController
}
}
// commented out core data and just used a normal array for testing.
Did you add segue in your storyboard for the tableview ? In this case the didSelect is not call but the prepare(for segue) of the tableview controller is called.
Ok solved it - There was a rouge tap gesture recognizer on the page. Removed it and works on one click. I have seen that if you wish to keep the gesture just add this line of code at the top of the function:
tap.cancelsTouchesInView = false
Took three days but I got there. Thanks for the help!
I'm coding a Note App in Swift 4. The root ViewController (NoteListViewController) gets populated when secondViewController (ComposeNoteViewController) Textfield and TextView are populated.
The problem is when I press a populated TableView cell, rather than fetch and display the content, it opens a fresh instance of theComposeNoteViewController.
import UIKit
import CoreData
class NoteListTableViewController: UITableViewController {
var noteListArray = [NoteListItem]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
loadNoteListItem()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return noteListArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NoteListItemCell", for: indexPath)
cell.textLabel?.text = noteListArray[indexPath.row].title
return cell
}
//MARK: - TABLEVIEW DELEGATE METHODS
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToComposeNote", sender: self)
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ComposeNoteViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedNoteList = noteListArray[indexPath.row]
}
}
import UIKit
import CoreData
class ComposeNoteViewController: UIViewController {
var noteComposeItemsArray = [ComposeNote]()
var noteListArray = [NoteListItem]()
// let noteListController = NoteListTableViewController()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var selectedNoteList : NoteListItem? {
didSet {
loadComposeItem()
}
}
#IBOutlet weak var noteTextView: UITextView!
#IBOutlet weak var noteTextField: UITextField!
#IBAction func noteSavePressed(_ sender: UIBarButtonItem) {
let newNoteTitleItem = NoteListItem(context: context)
let newComposeNote = ComposeNote(context: context)
newNoteTitleItem.title = noteTextField.text!
newComposeNote.note = noteTextView.text!
newComposeNote.parentTitleNote = selectedNoteList
noteComposeItemsArray.append(newComposeNote)
noteListArray.append(newNoteTitleItem)
saveComposeItems()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func saveComposeItems() {
do {
try context.save()
}catch {
print("Error saving context \(error)")
}
reloadInputViews()
}
func loadComposeItem() {
let request : NSFetchRequest<ComposeNote> = ComposeNote.fetchRequest()
let predicate = NSPredicate(format: "parentTitleNote.title MATCHES %#", selectedNoteList!.title!)
request.predicate = predicate
do {
noteComposeItemsArray = try context.fetch(request)
}catch {
print("Can't load Items")
}
reloadInputViews()
}
}
What I am trying to do in general:
1. Generate a table with all registered users (stored in core data).
2. Then allow the user to select a registered-user by pressing on a row.
3. When user selects a row, a new page opens with detailed user info.
I am trying to solve this by:
1. First storing the UserName from the given cell into "UserDefaults.standard".
2. Then on the second page, accessing the stored UserName from "UserDefaults.standard" and using that dynamic value to retrieve Core Data for more detailed user information.
Problems:
I am certain that the data that is passed into "UserDefaults.standard" is the full table and not data from a specific cell. I have seen this by using "Print()", is actually show all of the rows instead of only the one row that is selected.
So the UserName that is then generated into the second page is a random row that is further down (see picture).
public class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var userNameobject : [NSManagedObject] = []
#IBAction func pushTwo(_ sender: Any) {
}
#IBOutlet weak var usersList: UITableView!
override public func viewDidLoad() {
super.viewDidLoad()
usersList.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return }
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Users")
do {
userNameobject = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
public func tableView(_ usersList: UITableView, numberOfRowsInSection section: Int) -> Int {
return userNameobject.count
}
public func tableView(_ usersList: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let person = userNameobject[indexPath.row]
let cell = usersList.dequeueReusableCell(withIdentifier: "CellTwo", for: indexPath) as UITableViewCell
cell.textLabel?.text = person.value(forKeyPath: "userName") as? String
// STORE USERNAME
let defaults = UserDefaults.standard
defaults.set(person.value(forKeyPath: "userName"), forKey: "userNameDefault")
return cell
}
}
public func tableView(_ userList: UITableView, didSelectRowAt indexPath: IndexPath)
{
}
1- Connect segue source to VC not to cell
func tableView(_ userList: UITableView, didSelectRowAt indexPath: IndexPath)
{
self.performSegue(withIdentifier: "toSecond", sender:userNameobject[indexPath.row])
}
2- Implement
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let des = segue.destination as? SecondVC {
des.person = sender as! NSManagedObject
}
}
3-
class SecondVC:UIViewController {
var person: NSManagedObject?
}
I solved this by making giving "Sender:" the exact same line that generates each given cell the User Name. It works perfectly now!
public func tableView(_ userList: UITableView, didSelectRowAt indexPath: IndexPath)
{
let person = userNameobject[indexPath.row]
let two = person.value(forKeyPath: "userName") as? String
self.performSegue(withIdentifier: "userLinkSegue", sender: two!)
print(two!)
// STORE USERNAME
let defaults = UserDefaults.standard
defaults.set(two!, forKey: "userNameDefault")
}
import UIKit
import SwiftyJSON
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
var mass = [String]()
var jDatas = datas()
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://www.quandl.com/api/v3/datasets/WIKI/AAPL.json")
let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
if error != nil {
print("error")
} else {
if let content = data {
let myJson = JSON(data: content)
for item in myJson["dataset"]["data"] {
let dates = "open \(String(describing: item.1[1].string)))"
self.mass.append(dates)
}
}
}
}
task.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mass.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SimpleTableViewCell
cell.datasLabel?.text = "some text"
return cell
}
}
So my problem is that numberOfRowInSection doesn't work, I tried everything, can someone say whats the problem?
When I'm trying to debug the code it says that theres 0 in my massive
You forgot to reload the tableView after your for loop in dataTask's closure, so simply reload the tableView also on main thread.
for item in myJson["dataset"]["data"] {
let dates = "open \(String(describing: item.1[1].string)))"
self.mass.append(dates)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}