How to match values in two TableViews - ios

There is a tableview with folder names and after clicking on each row, it segues to another TableView to show sections and rows for each folder. But I got problem how to show values corresponding to every folder. The error message is: fatal error: unexpectedly found nil while unwrapping an Optional value. Please see my codes below. I guess the problem is prepareForSegue codes didn't work well.
Note: Somewhere I need to delete half ")" as it doesn't work for "()" and I can't edit. Please help to edit.
In first tableview:
var folderNames = [String]()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == segueToDetailsTable {
let detailsVC = segue.destinationViewController as! DetailsViewController
detailsVC.detailsTableView.indexPathForCell(UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "DetailsCell")) == savingTableView.indexPathForSelectedRow
}
}
In second TableView to show details:
var sectionTitles = ["WebSite", "Date Saved", "Document Used","Add Notes"]
var detailsInSection = [[String](), [NSDate](),[AnyObject](),[String]()]
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return detailsInSection.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTitles[section]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return detailsInSection[section].count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DetailsCell")
cell?.textLabel!.text = detailsInSection[indexPath.section][indexPath.row].description
return cell!
}

You're doing some weird stuff in prepareForSegue. detailsVC hasn't loaded yet thus the tableview may still be nil. But most importantly, you cant connect the tables like that. When you select a row, figure out what data should be shown in the next. After you've aggregated the data set it as a property on the second vc inside the prepareForSegue method. When the next vc loads the property that it is using should already be set when the table loads. You also have two different data sources that tableview reads from. This is a bit dangerous because you cant guarantee there be a corresponding value in the other source.

Related

How to use a definitions in a swift dictionary with an array (Dictionary<String, [String]>) to display on tableview cell?

Ok first thing first, what am I trying to do? Well I am trying to run something that is like a tag system where it filters out the data with the post by using a dictionary String, [String] type and displays it to the screen. I already figured this out on the console level, but I am stumped on how to do it with this which is kind of weird. I try and it returns nil inside the UI ,but works perfectly on the console level.
Simplified. I want the quick tags filtered array to show up in the tableview
I am repeating this again, console works perfect, but the UI gets wacky and returns nil or does nil. Ok here is the code.
Not 100% sure about this area causing problems ,but I displayed here just in case.
//this is the part where I add the stuff into the console
//and add the dictionary part.
#IBAction func ReplyAction(_ sender: UIButton) {
if !(TextFieldForComments.text?.isEmpty)! && TextFieldForComments.text != nil
{
CommentGlobals.shared.addToCommentSection(newElement: TextFieldForComments.text!)
let tagCheck = TextViewForComment.text
if !quickTags.FilteredComments.keys.contains(TextViewForComment.text){
quickTags.FilteredComments.updateValue(["\(String(describing: TextFieldForComments.text))"], forKey: tagCheck!)
print("Hey here is the dictinary you wanted \(quickTags.FilteredComments)")
}
else {
quickTags.FilteredComments[tagCheck!]?.append(CommentGlobals.shared.commentSection.last!)
print("Hey here is the dictinary you wanted wo wo \(quickTags.FilteredComments)")
}
TextFieldForComments.text = ""
//this line of code is important or it
//won't insert the table view right.
CommentFeed.insertRows(at: [IndexPath(row: CommentGlobals.shared.commentSection.count-1, section: 0)], with: .automatic)
}
Here is the problem area
//this is the problem area
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let ceal = CommentFeed.dequeueReusableCell(withIdentifier: "commentFeed", for: indexPath)
//guard let selectedDictionary = quickTags.FilteredComments["\(TextViewForComment)"] else {return ceal}
//this is the part that works, but noted it out for reference
//this doesn't work for what I am trying to do because
//I don't want to display the comments of every view
//ceal.textLabel?.text = "\(CommentGlobals.shared.commentSection[indexPath.row])"
//this is failure. I also tried another way ,but it just printed nil
//on to the UI
//ceal.textLabel?.text = "\(selectedDictionary[indexPath.row])"
return ceal
}
Ok if you need more information, please let me know.
Oh yea I can't say this enough it works on a console level perfectly, but not when I try to get it onto the UI.
I also stored the quick tags in a static array, and I stored the rest in a singleton
Here is the expected output (UI)
comment
comment
comment
the current output is like this(UI)
(it does nothing, runs nil, or crashes)
some other sources lead the thing to be like this
comment comment comment
all of them on the same line which is not what I want.
func numberOfSections(in 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 CommentGlobals.shared.commentSection.count
print(quickTags.FilteredComments.count)
return CommentGlobals.shared.commentSection.count
}
UITableViews do not contain their own data set, they take it from the dataSource. insertRows(at:, with:) is only for cell animation, and should be wrapped with begin/endUpdates().
class ViewController: UITableViewDataSource {
#IBAction func ReplyAction(_ sender: UIButton) {
// ...
CommentFeed.beginUpdates()
CommentFeed.insertRows(at: [IndexPath(row: CommentGlobals.shared.commentSection.count - 1, section: 0)], with: .automatic)
CommentFeed.endUpdates()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CommentGlobals.shared.commentSection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commentFeed", for: indexPath)
cell.textLabel?.text = CommentGlobals.shared.commentSection[indexPath.row]
return cell
}
}
Edit: Note that your data set's number of rows and sections MUST match the expected results at the end of the animation, or you will get an exception when calling endUpdates().

Passing data between table views in swift

Trying to code for about us page using tableviews. I am able to create the first table view for "about us" components
var aboutUs = ["About us", "Contact us", "Privacy policy"]
Have to pass on this items to next table view containing aboutUsDetails
var aboutUsDetails = ["A team of docs and coders ","Contact us at editor#gmail.com","Privacy Policy will be updated soon"]
I have created the segue function like this
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == SegueDetailsTableViewController
{
let detailViewController = segue.destination as! DetailsTableViewController
let myIndexPath = self.tableView.indexPathForSelectedRow!
let row = myIndexPath.row
detailViewController.aboutUsDetails = aboutUs[row]
}
I am a bit confused here because aboutUsDetails is a [String]; and it is not passing on? How do I overcome this?
If you are trying to pass only one string, there is no need to declare aboutUsDetails property as an array of strings, instead let it be just a single string:
In DetailsTableViewController:
Change aboutUsDetails: [String]? to aboutUsDetails: String?
If you are implementing the following code snippets, you should change them as follows:
Change this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return aboutUsDetails.count
}
to this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
And change this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// you might want to have a custom cell, you don't have to change it...
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellId")
cell?.textLabel?.text = aboutUsDetails[indexPath.row]
}
to this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// you might want to have a custom cell, you don't have to change it...
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCellId")
// this is part you should foucing on...
cell?.textLabel?.text = aboutUsDetails
}
Note: you might need to do Optional Binding to display the value of aboutUsDetails as it should (without "Optional("...")").
OR
if you insist to declare aboutUsDetails as an array of strings (which I think that there is no need to do that), you will need to pass your string as an array containing one string, as follows:
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == SegueDetailsTableViewController
{
let detailViewController = segue.destination as! DetailsTableViewController
let myIndexPath = self.tableView.indexPathForSelectedRow!
let row = myIndexPath.row
// here is the change, you are passing your string as the first
// (and the only one) element of 'aboutUsDetails'
detailViewController.aboutUsDetails = [aboutUs[row]]
}
}
Hope this helped.
aboutUs[row] is a single string, aboutUsDetails seems to be an array of strings. You can either change aboutUsDetails to be a String, or pass your data like this:
detailViewController.aboutUsDetails = [aboutUs[row]]
You need to only one changes and very small to below line
detailViewController.aboutUsDetails = [aboutUs[row]]
because aboutUs[row] is string and [aboutUs[row]] is Array of String and you can not assign String to aboutUsDetails which is array of String.

UITableViewCells not appearing in second Tab

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.

Swift table view Embedded in Container

I have an NSManagedObject that is being used in my main view.
in this view I have two containers, each with their own static TableViews going on.
In my NSManagedObject I have an array I'd like to loop over, and display info on the screen like so:
Customer1 Name
Customer1 Type
Customer1 Address
Customer2 Name
Customer2 Type
Customer2 Address
I have tried to go the route of using a TableView, I have added a container, embedded the tableview in it, set a custom cell and tried to populate the custom cell with some test data. When I run it though the TableView just shows the four empty rows. (I'm probably missing something to do with the amount of rows which is why my test data isn't showing):
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tblPartyDetails.dequeueReusableCellWithIdentifier(
"JobViewPartyCell", forIndexPath: indexPath)
as! JobViewPartyCell
cell.lblPartyName.text = "test name"
cell.lblPartyAddress.text = "test adddress"
cell.lblPartyType.text = "test partyType"
return cell
}
I also have to figure out how to pass my NSManagedObject into this TableView class and it seems like a lot of effort for what is just a repeated block of information...or...is this the only way to do it?
So, am I going about this in the right way? If so, how do I fix it and add my NSManagedObjects details to the TableView. If I'm not going about this correctly, what are the alternatives? I had a look at some other custom 'card' type stuff, like facebook and google cards, but those techniques use custom TableViewCells as well.
edit. PrepareForSegue function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == jobSegueIdentifier {
if let destination = segue.destinationViewController as? JobViewController {
if let jobIndex = tblJobs.indexPathForSelectedRow() {
let workItem:Work = fetchedResultsController.objectAtIndexPath(jobIndex) as! Work
destination.workItem = workItem
}
}
}
}
First of all you returned 0 in your numberOfRowsInSection and what you should do is putting the number of rows you want to display, if your are testing your tableView put any number.
And if your data is in your mainView you should pass your data to the contained tableView so you can display it and in your number of rows you should return the number of elements in your data array.
First give an identifier to your embed segue in the storyboard and in your main view implement the prepareForSegue function as follows:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embedSegueIdentifier" {
let distinationVC = segue.destinationViewController as? EmbeddedTableViewController //replace EmbeddedTableViewController with your tableViewControllerClass
distinationVC?.dataArray = yourDataArray //yourDataArray is in your main view and you should define data array in your embedded table view controller
}
}
and in your tableViewController add the following:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
I hope this helped.

How to have a UITableView link to another UITableView

I am currently making an app using swift that has information about cars.
I am using an UITableView for makes, models, years.
What I want to know is if can I have an UITableView linked to another UITableView depending on user input, for example:
tableview 1 (makes)
Audi
Honda
tableview 2 (Models)
Audi -> A1, A2, A3........
Honda -> Civic, Jazz...
tableview 3 (years)
Audi -> A3 -> 2005,2006,2007.....
Honda -> Civic -> 2005,2006,2007.....
Code for tableview 1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.Makes = [Make(name: "Audi"),Make(name: "Nissan"),Make(name: "Fiat"),Make(name: "Ford"),Make(name: "Honda"),Make(name: "Mercedes-Benz"),Make(name: "Lexus"),Make(name: "BMW"),Make(name: "Vauxhall"),Make(name: "VW")]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.Makes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
var make = Makes[indexPath.row]
cell.textLabel?.text = make.name
return cell
}
1.- Organise your data in a good manner, maybe a graph, a tree, or simply lists relating all of your data.
2.- For simplicity makes functions that will give you corresponding data to each tableview.
Lets say:
func getModels(make: Makes) -> [Model]
func getYears(model: Model) -> [Years]
or simply
func getModels(make: String) -> [String]
func getYears(model: String) -> [String]
also, some helper functions that will allow you to implement any data structure behind, just like, for example:
func getMaker(int:Int) -> Maker? or func getMaker(int: Int) -> String?
3.- You must keep in memory which of your possible makers and models have been selected, for now, keep it like:
var selectedMaker: String?
var selectedModel: String?
4.- I assume you will have all your UITableViews at the same UIViewController or UITableViewController, so you will need to decide corresponding data to show to every one.
For this you will need to differentiate each one, how is up to show, with tags, instance equality, etc. I suggest for later readability and facility of use to end up having a function that will return a number? maybe, corresponding to the tableview. For this explanation sake, lets call it func whichTableIsThis(tableView: UITableView) -> Int?
5.- Your delegates should work different for everyone of those tableviews. Here we will be using our brand new function that must return 1, 2 or 3 ..nil if this tableview is not one of those. :)
extension YourViewControlerWithTableViews: UITableViewDelegate, UITableViewDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//I'm assuming you will have only one cell, lets call it `AttributesTableViewCell`
var cell = tableView.dequeueReusableCellWithIdentifier("yourCellName", forIndexPath: indexPath) as! AttributesTableViewCell
cell.attributeValue.text = ""
if let tableNumber = whichTableIsThis(tableView) {
//here you will be checking for every of your tree cases, for this example I will check just for Models
//OK, so tableNumber returned 2
if tableNumber == 2 && selectedMaker != nil{
let value = getModels(selectedMaker!)[indexPath.row]
cell.attributeValue.text = value
}
//...
}
return cell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let tableNumber = whichTableIsThis(tableView) {
//here you will be checking for every of your tree cases, for this example I will check just for Models
//OK, so tableNumber returned 2
if tableNumber == 2 && selectedMaker != nil{
return getModels(selectedMaker!).count
}
//...
}
return 0
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let tableNumber = whichTableIsThis(tableView) {
//here you will be checking for every of your tree cases, for this example I will check just for Maker
//OK, so tableNumber returned 1
if tableNumber == 1 {
selectedMaker = getMaker(indexPath.row)
//Here you must refresh data for your next tables in hierarchy, to allow them to refresh with new data
selectedModel = nil
selectedYear = nil
tableview2.reloadData()
tableview3.reloadData()
}
//...
}
}
}
And..that should be all. Hope it helps!
This approach of drilling down to see more details is very common, and Xcode even provides a template to illustrate this, called Master-Detail.
The way this works is when you select a row in the first (or Master) tableView, it performs a showDetail segue to the second (or detail) tableViewController.
In prepareForSegue, you would get the indexPath of the selected row, and pass the make to the detail (destination) view controller. That view controller would then show all the models for that make of car.
You would use the same process in the detail tableView to pass a specific model to the a third tableViewController to see all years for that make and model.
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
Let make = Makes[indexPath.row]
let controller = (segue.destinationViewController
as UINavigationController).topViewController
as! DetailViewController
controller.detailItem = make
controller.navigationItem.leftBarButtonItem =
splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Update:
The Master-Detail template provides other benefits, such as Adaptive UI. For example, on an iPad or iPhone 6 Plus, the user could choose to see both the master and detail views in a split view.

Resources