I am writing a note taking app, just for reference. I have arrays set up, and a table that feeds off the arrays with the following code:
import UIKit
import Foundation
var tableData = ["Pancake Recipe", "Costume Party", "Camping Supplies"]
var tableSubtitle = ["Some Milk and some Flour", "Let's dress up like Jen", "Tenting with Lucy"]
class ViewController: UIViewController {
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"cell")
cell.textLabel!.text = tableData.reverse()[indexPath.row]
cell.detailTextLabel!.text = tableSubtitle.reverse()[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
var listTitle = "Notes"
self.title = listTitle
UIApplication.sharedApplication().statusBarStyle = .LightContent
}
override func viewDidAppear(animated: Bool) {
println(tableSubtitle)
}
}
A user creates a new title and subtitle for the cell on a different page, and these are added to the arrays (the tableData and tableSubtitle arrays). I know the adding of the data works correctly, because when I watch the console it prints both the updated arrays perfectly.
When I then return to the main view controller, I am presented with an extra cell (as I wanted) but instead of the new content that I want, it is instead just a duplicate of the 'Pancake Recipe' cell.
Do I need to refresh the content of the cells when the view loads again? If so, how can I do this?
Thanks :)
For reference, here is a picture of the Table View after data has been added to both the arrays twice, and I have then returned to the Table View, despite the fact both the arrays now contain two extra and distinct entries (checked using println(tableData) and println(tableSubtitle)
The provided code does not provide much information to find the issue, probably the issue will be with data adding code.
For refreshing the table view use:
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
self.yourTableView.reloadData()
}
Related
I need the tableView to perform reloadData() after a row has been added via a textView. My tableViews all use the reusable code which works fine.
Below is my Reusable TableViewCode.
class ReusableSubtitleTable: NSObject, UITableViewDataSource, UITableViewDelegate{
let cell = "cell"
var dataArray = [String]()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) {
print("DataArray count from table view = \(dataArray.count)")
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let selfSizingCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SelfSizingCell
let num = indexPath.row
selfSizingCell.titleText.text = (stepText[num])
selfSizingCell.subtitleText.text = dataArray[num]
return selfSizingCell
}
}
The function below uses the reusable code to display the table which works fine.
class DetailViewController: UIViewController {
let step = 13
var tableView: UITableView!
let dataSource = ReusableSubtitleTable()
var selectedEntry: JournalEntry!
var dataModel = [String]()
var didSave = false
var coreDataManager = CoreDataManager()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = dataSource
tableView.dataSource = dataSource
dataSource.dataArray = dataModel
#IBAction func unwindToDetail( _ sender: UIStoryboardSegue) {
dataModel[10] = step11
didSave = coreDataManager.updateEntry(step11: step11, selectedEntry: selectedEntry)
}
}
The problem come in when a user wants to add to the last row. The user taps a button and is taken to the next controller which is a TextView. When user finishes their entry they tap the 'Save' button which returned to the DetailViewController via an unwind. The selectedEntry is saved and the dataModel updated. Now the table view needs to reload to display this added text.
I've tried adding tableView.ReloadData() after didSave. I've tried a Dispatch and tried saving the data before returning from the textView via the unwind but that doesn't work either.
I tried adding the below function to ReusableTableView and called it after the coredata update - there are no errors but it does not reload the table.
func doReload(){
tableView.reloadData()
}
I have verified that the data is saved and it does displays if I return to the summary controller and then go forward the DetailViewController.
Any help is appreciated.
Placing the UITableView reloadData() within either viewWillAppear or viewDidAppear should resolve this issue.
For example:
func viewDidAppear(_ animated: bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
This is because the view hierarchy isn't yet regarded as "visible" during the segue unwind and why you see it work by going back to reloading the view controller. The reloadData() function, for efficiency, only redisplays visible cells and at the time of the unwind the cells aren't "visible".
Apple Documentation - UITableView.reloadData():
For efficiency, the table view redisplays only those rows that are
visible.
I'm stuck with a very specific problem while using a Table View (XCode 9, Swift 4). What I want to do is, make an array named foodDetailInfoArray with text values of the foodName label in the table cells which have been selected manually by the user. Currently, while the .setSelected method works for changing the UI for a cell as I want, it isn't helping me record the foodName.text value properly. The problem is that the text values get recorded even while scrolling the table view and the array values get replaced as well. Below is the code and a sample of the printed output.
var foodDetailInfoArray: [String] = []
#IBOutlet var unselectedCell: UIView!
#IBOutlet var foodName: UILabel!
#IBOutlet var carbonValue: UILabel!
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
if selected == true {
self.unselectedCell.backgroundColor = UIColor.init(red: 4/255, green: 206/255, blue: 132/255, alpha: 1)
self.foodName.textColor = UIColor.white
self.carbonValue.textColor = UIColor.white
foodDetailInfoArray.append(foodName.text!)
} else {
self.unselectedCell.backgroundColor = UIColor.clear
self.foodName.textColor = UIColor.black
self.carbonValue.textColor = UIColor.black
}
print(foodDetailInfoArray)
}
The print statement gives me this sort of result:
(This is when the cells are not even selected and I'm just scrolling the table view.)
["pepper"]
["pasta"]
["pasta", "pepper"]
["pepper"]
["pepper", "pasta"]
["stir-fry"]
["stir-fry", "stir-fry"]
["vegetable"]
["vegetable", "vegetable"]
Whereas, what I ideally want would be (in the order of clicking the cell that contains given foodName):
["pasta"]
["pasta", "pepper"]
["pasta", "pepper", "tomato"]
["pasta", "pepper", "tomato", "stir-fry"]
and if a certain cell is deselected then the name has to be dropped, ie if tomato is deselected, then array would be
["pasta", "pepper", "stir-fry"]
... and so on
PS: I'm not a programmer by profession and altogether self taught recently, so please let me know if the question is unclear in any way.
I would handle the selection and deselection of the cell via the view controller, so you can also use your foodDetailInfoArray better. With the help of this answer you could do it like that way:
import UIKit
class ViewController: UITableViewController {
// example data
let names = [ "pepper", "pasta", "stir-fry", "vegetable"]
var foodDetailInfoArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// allow multiselection
tableView.allowsMultipleSelection = true
}
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = names[indexPath.row]
// Don't show highlighted state
cell.selectionStyle = .none
return cell
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// also do your UI changing for the cell here for selecting
// Add your food detail to the array
foodDetailInfoArray.append(names[indexPath.row])
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
// also do your UI changing for the cell here for deselecting
// Remove your food detail from the array if it exists
if let index = foodDetailInfoArray.index(of: names[indexPath.row]) {
foodDetailInfoArray.remove(at: index)
}
}
}
Result
I would try the delegate method didSelectRowAtIndexPath for tableViews. Have your view controller adopt the UITableViewDelegate protocol and implement the following.
Suppose you have a foods array, and a foodsSelected array that's initially empty.
let foods:[String] = ["Apples","Avocado","Bananas"]
var foodsSelected:[String] = []
Now whenever a cell is selected, this delegate method is called and add or remove the selected food from the foodsSelected array.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Check if the selected food is in the foodsSelect array
if(!foodsSelected.contains(foods[indexPath.row])){
//If it's not, append it to the array
foodsSelected.append(foods[indexPath.row])
}else{
//If it is, remove it from the array.
//Note there are many ways to remove an element from an array; I decided to use filter.
foodsSelected = foodsSelected.filter({$0 != foods[indexPath.row]})
}
print(foodsSelected)
}
Here is the output when I select these items in order: Apples, Avocado,Bananas,Avocado
["Apples"]
["Apples", "Avocado"]
["Apples", "Avocado", "Bananas"]
["Apples", "Bananas"]
I'm currently working on an app in which I would like the users to be able to favourite a button and that button then gets added to the 'Favourites' section for that particular ViewController.
The app is a soundboard so it is split into (currently) 2 characters, within those characters I have categories of sound files for the users to choose from, once the user selects a category it will load up the ViewController with a set of sounds which can be played by pressing the button (very simple I know).
I'm trying to implement a 'Favourite' function in order to allow the user to favourite the buttons of their choice and in doing so, the new 'favourited' button will be displayed in a new ViewController.
I've browsed through here and found some stuff in regards to NSUserDefaults
I'm new to Swift and iOS development as a whole so if someone could be kind enough to guide me in the right direction I would be very grateful.
I know how to set the Image of the favourite button to change whether it has been set as a favourite or not, I also noticed that it suggests I place the favourites in a UITableViewController. My app is currently using a UITabBarController with 4 different ViewControllers off them.
If you would like screenshots of my Storyboard to get a better understanding please let me know and I shall update the post, also if any code is required please let me know and once again, I shall update the post!
EDIT: I will happily add a button which when pressed loads a table view if someone knows how to add a normal button into a table view so when pressed it shows the favourites
EDIT 2: Image
try to do as follows-
Take a UITabBarController having one viewController as soundsViewController and another tableViewController as favouritesTableViewController shown as below image-
Now add these sample code to soundsViewController-
var favListArray:NSMutableArray = []
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("favList") != nil {
favListArray = NSMutableArray.init(array: NSUserDefaults.standardUserDefaults().objectForKey("favList") as! NSMutableArray)
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("soundCell", forIndexPath: indexPath) as! TableViewCell
cell.titleLbl.text = NSString.localizedStringWithFormat("Sound %d", indexPath.row) as String
if favListArray.containsObject(cell.titleLbl.text!) {
cell.addTOFVrtBtn.setTitle("-", forState: UIControlState.Normal)
}else{
cell.addTOFVrtBtn.setTitle("+", forState: UIControlState.Normal)
}
cell.addTOFVrtBtn.tag = indexPath.row
cell.addTOFVrtBtn.addTarget(self, action:#selector(addToFav) , forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func addToFav(sender:UIButton) {
let cell = self.tableView.cellForRowAtIndexPath(NSIndexPath.init(forRow: sender.tag, inSection: 0)) as! TableViewCell
if favListArray.containsObject(cell.titleLbl.text!) {
favListArray.removeObject(cell.titleLbl.text!)
}else{
favListArray.addObject(cell.titleLbl.text!)
}
tableView.reloadData()
NSUserDefaults.standardUserDefaults().setObject(favListArray, forKey: "favList")
}
these is a sample code, here TableViewCell is subclass of UITableViewCell having titleLbl and addTOFvrtBtn.
Add below sample code to favouritesTableViewController -
varfavSoundList:NSMutableArray = []
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if NSUserDefaults.standardUserDefaults().objectForKey("favList") != nil {
favSoundList = NSMutableArray.init(array: NSUserDefaults.standardUserDefaults().objectForKey("favList") as! NSMutableArray)
print(favSoundList)
self.tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return favSoundList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = favSoundList.objectAtIndex(indexPath.row) as? String
return cell
}
Here is the o/p screen demo-
I hope this will help you or you can get an idea how to make Favourites list and Favourites section..Thanks
Don't think of it as favouriting a button, a button is just how you're displaying the data in your app - what you're really favouriting is the data itself, so in this case a particular sound file.
So, your current data is a list of sound files, or the path to those files. Your favourites list can be exactly the same. I imagine you might be simply getting a list of files from disk to display so your list of favourites will need to be stored differently. That could be in user defaults, or in a plist file, either way it's an array of strings and both are simple options to store the array.
When storing you just set the array into defaults with a key, or write it to a file. When you read back you should either read from user defaults and then make a mutableCopy or use NSMutableArray to read the file in so you have an editable instance afterwards and you can add and remove favourites from the list.
Your approach of using buttons to represent the sound files is probably a lot more code and effort than you require. Table views are specifically designed to show a list of things, and there's no reason that a table view can't look like a list of buttons (while being much more flexible and much less code to create).
I have the following problem:
I am making a Pokédex-like application that displays a list of all 721 Pokémon on the first tab, and another list on the second tab containing My Favorite Pokémon. Essentially, there are two identical ViewControllers connected to my TabBar.
My storyboard is as follows:
So here is the problem:
The TableView on the first (and initial) tab works fine. However, when I load the TableView on the second tab the Pokémon are loaded, but not displayed. I am able to click the TableViewCell and go to the detail page, but the label in the TableViewCell is not showing anything.
This is the code I use for loading Favorites TableView
class FavoritesViewController: BaseViewController,
UITableViewDataSource, UITableViewDelegate {
#IBOutlet var FavoritesListView: UITableView!
var pokemonList: [String] = ["Nothing Here!"]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FavoriteCell", forIndexPath: indexPath) as! FavoriteCell
var name = pokemonList[indexPath.row]
capitalizeFirstLetter(&name)
cell.nameLabel.text = name
return cell;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemonList.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(pokemonList[indexPath.row])
self.performSegueWithIdentifier("ToPokemonDetail", sender: pokemonList[indexPath.row])
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "ToPokemonDetail"){
let destination = segue.destinationViewController as! PokemonDetailViewController
let thisPokemon = sender as! String
destination.currentPokemon = thisPokemon
}
}
override func viewWillAppear(animated: Bool) {
FavoritesListView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Fetch the cached list, getNames returns an array of strings
let list = utility.getNames("Favorites")
pokemonList = list
}
The delegate and the dataSource are set via the storyboard.
The above code works, and shows the Favorites list just fine. The class for the complete Pokédex has a similar construction.
I have tried switching Favorites and Pokédex around, so that it shows the complete Pokémon list on startup. All 721 Pokémon are shown correctly, but then the Favorites are not visible.
What else I have tried:
Checking the Reuse Identifiers, over and over
Referencing outlets should be bound correctly
Calling TableView.reloadData() in the viewDidAppear method
Switching around the tab items
Does anyone have any clue what on earth is going on here?
Feel free to ask any more questions
Edit: this is what happens when I swap the two TabBar Buttons around, no code changes
Pokédex Screen
Favorites Screen
GitHub Project Here
Problem is in storyboard cell label frame. Set constraints of view controller for (Any,Any) Size Class. I can commit the code on github if you can give me write rights on your git. Thanks
Perhaps your table's delegate and dataSource are not set.
table.delegate = self
table.dataSource = self
Of course this is after you add the properties to your view controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
Your number of rows is always 0 for that controller,
I looked into your code pokemonList count is always 0 its not updating data in it
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemonList.count
}
The big issue is your PokemonDetailViewController is not a UITableViewController. It needs to inherent from UITableViewDataSource, UITableViewDelegate and then be connected to the storyboard view to provide data and formatting for a table.
I've got an issue where my tableView isn't updating based on the datasource correctly. I'm using a tabbed application structure with Storyboards.
My overall goal here is to have a tableView on the second tab display items that are removed from an array stored in a struct. The items are added to the array from the first tab.
There are 2 ViewControllers (1 for the interface for scrolling through items and selecting to remove them, and 1 to handle the tableView) and 2 Views (1 for the interface for scrolling through items and removing them and 1 for the tableView). The first tab is for providing the interface for removing the items and the second tab is for the tableView.
The remove and add to the array functionality works, just not the displaying it in the tableView.
Currently, if I hard code items in my "removed items" array, they are displayed in the tableView. The problem is that as I add items to the array from my removeItem function in the first ViewController, the tableView does not update, only the hard coded items are shown.
This makes me assume that I have my datasource and delegate setup correctly, since the tableView is getting it's data from the intended datasource. The issue is it's not updating as the user updates the array with new items.
I've tried using self.tableView.reloadData() with no success. I might not be calling in the correct location though.
I'm not sure where the disconnect is.
Here is my second view controller that controls the tableView
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let cellIdentifier = "cellIdentifier"
var removedTopicsFromList = containerForRemovedTopics()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView?.registerClass(UITableViewCell.self, forCellReuseIdentifier: self.cellIdentifier)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// UITableViewDataSource methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return removedTopicsFromList.removedTopics.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier) as UITableViewCell
cell.textLabel!.text = self.removedTopicsFromList.removedTopics[indexPath.row]
return cell
}
Here is the struct where the removed phrases are stored
struct containerForRemovedTopics {
var removedTopics: [String] = []
}
structure instances are always passed by value. So if your code is something like:
var removedTopicsFromList = secondViewController.removedTopicsFromList
removedTopicsFromList.removedTopics.append("SomeTopic")
secondViewController.reloadData()
then you are changing the different structure.
Maybe you got stuck with this problem I guess.