How to have a UITableView link to another UITableView - ios

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.

Related

Master Detail View With Segue

I'm Trying to learn how to do a detail view for my project .
I have a simple tableView with a simple Array data to fill it.
The Table View :
TableView Example
I designed a detail View as well, with static tableViewCells
Detail View example :
Example
I'v Connected both with a segue :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("Profile", sender: indexPath);
}
I also connected all the labels and images with Outlets i want to change between each cell but i don't how to advance from here.
right now every cell shows the same thing but i want to change the data between rows . So i would like to change the data through the segue and create a master detail application like in my tableview. Can anybody help me ?
Am using Swift 2.3 and Xcode 8.1
If I understand your question correctly, you just want to pass dataSource element to the next viewController. So you can just pick it using indexPath.row and use sender parameter to set it in prepareForSegue method.
The code below assumes your dataSource is self.users array.
Swift 3
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let user = self.users[indexPath.row]
self.performSegueWithIdentifier("Profile", sender: user)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueId = segue.identifier? else { return }
if segueId == "Profile" {
guard let profileVC = segue.destination as? ProfileViewController else { return }
profileVC.user = sender as? User
}
}
Swift 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let user = self.users[indexPath.row]
self.performSegueWithIdentifier("Profile", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let segueId = segue.identifier else { return }
if segueId == "Profile" {
guard let profileVC = segue.destinationViewController as? ProfileViewController else { return }
profileVC.user = sender as? User
}
}
Edit
im trying to change data like al the labels you saw between rows like
for example shalvata will have a different data from light house and
so , change the labels and images and so on
It is still unclear for me what data you want to change exactly. Also I don't understand the language on your screenshots, but since you name the relationship as master-detail, I suppose the second screen is meant to show more info about the entity selected on the first screen.
If so, you should start from designing you model so that it contains all those fields you need on the second screen. Judging by the icons it would be something like
struct Person {
var name: String?
var image: UIImage?
var age: Int?
var address: String?
var phone: String?
var schedule: String?
var music: String?
var smoking: Bool?
var car: String?
var info: String?
var hobby: String?
}
Note: Remove ? for those fields which aren't optionals, i.e. always must be set for every entity (perhaps name field)
Usage
I don't known how and when you create your Person array, but basically there are two approaches:
Use a list of entities with all fields filled on MasterVC and just pass the selected person to the DetailVC in didSelectRowAtIndexPath
Use a list of entities with some basic data (name, address, image) required for MasterVC and fill the rest of the fields only when required (didSelectRowAtIndexPath method)
In any case you'll get selected person in DetailVC and now everything you need is to use that data in cellForRow method, just as you did on MasterVC. Perhaps it would be a better option to use static TableViewController for Details screen.
Sounds like what you're trying to do does not involve segues at all. You can change data of cells using the cellForRow method in your tableViewController.
https://developer.apple.com/reference/uikit/uitableview/1614983-cellforrow
For example
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "foo"
return cell
}
If that sounds confusing to you then you should take a step back and do some tutorials then post specific questions on SO.

How to match values in two TableViews

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.

Container View that contains a TableView change number of rows depending number of inputs Swift

I am new to swift as well as creating iOS apps and I thought I would make a simple app that calculates the averages of the numbers inputted into the TextField. The averageViewController also has a container view as well that contains TableView. Once the person has hit the "Next" button I would like the TableView to display the numbers that have been inputted. (each cell label has a single number).
This is my segue method in my averageViewController as well as the function I am using when the user presses the button:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sendResult" {
let inputArray = segue.destinationViewController as! averageTableViewController
inputArray.arrayFromSegue = average.getArray()
}
}
#IBAction func nextButton(sender: UIButton) {
average.arrayInput(((inputTextField.text!) as NSString).doubleValue)
calcAverage()
inputTextField.text=nil
}
This is the code I made for my averageTableViewController:
class averageTableViewController: UITableViewController {
var arrayFromSegue = NSMutableArray()
var arrayUsed = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
arrayUsed = arrayFromSegue
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var Cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
Cell.textLabel?.text = String(arrayUsed[indexPath.row])
return Cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayUsed.count
}
}
I have done quite a bit of research and I believe one of my mistakes is that the action segue that I am doing (show) does not produce the correct results.
Problem isn't in segue you do. If you want change number of rows depend on number of input you should update your array data and reload your table. In your case you can change like this:
Create variable hold your tableViewController, in your case can put name is: inputArray
inputArray = segue.destinationViewController as! averageTableViewController
When you tap nextbutton you update array average and assign it to tableViewConroller `inputArray and reload it:
inputArray.arrayUsed = average.getArray()
inputArray.tableView.reloadData()
If you have any problem don't hesitate ask me. I will help you.
You can check my demo: Demo
Your project leak segue to tableviewcontroller: Please fix project like step below:
drage segue from average to averagetable
make it is embed:
Select it and name it sendResult

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.

Alternative for switch statement, drill down Table Views?

I need to create a drill down effect with my table views that will expand four table views deep for each of my original cells in my master table view. So far i was successful in populating the master view, and second table view accordingly with this Object Oriented Method, here is the code in my master table view:
class FirstTableViewController: UITableViewController {
let aSport:[Sport] = {
var basketball = Sport()
basketball.name = "Basketball"
basketball.sportCategories = {
var temp = ["International Basketball","Wheelchair Basketball","Beach Basketball","Deaf Basketball","Dwarf Basketball"]
temp.sort(<)
return temp
}()
var golf = Sport()
golf.name = "Golf"
golf.sportCategories = {
var temp = ["Miniature Golf","Dart Golf","Sholf","Disc Golf","Footgolf"]
temp.sort(<)
return temp
}()
var football = Sport()
football.name = "Football"
football.sportCategories = {
var temp = ["Flag Football","Indoor Football","Arena Football","Non-Tackle Football","Paper Football"]
temp.sort(<)
return temp
}()
var swimming = Sport()
swimming.name = "Swimming"
swimming.sportCategories = {
var temp = ["Competitive Swimming","Synchronized Swimming","Duo Swimming","Relay Swimming"]
temp.sort(<)
return temp
}()
return [basketball,golf,football,swimming]
}()
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return aSport.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = aSport[indexPath.row].name
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let cell = sender as! UITableViewCell
let row = tableView.indexPathForCell(cell)?.row
let detail = segue.destinationViewController as! SecondTableViewController
detail.selectedSport = aSport[row!]
}
}
class Sport {
var name: String = "sport name"
var sportCategories: NSArray = ["variations of selected sport"]
var detailText: NSArray = ["little description of sport"]
}
here is the code in my second table view controller:
class SecondTableViewController: UITableViewController {
var selectedSport = Sport();
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedSport.sportCategories.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Custom", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = selectedSport.sportCategories[indexPath.row] as? String
cell.detailTextLabel!.text = selectedSport.detailText[indexPath.row] as? String
return cell
}
}
here are screenshots from my simulator so you get a visual:
https://40.media.tumblr.com/6ee47f49c2b223b514f8067c68ac6af1/tumblr_nqbe74HYGo1tupbydo1_500.png
when basketball is selected:
https://41.media.tumblr.com/ced0ee2ff111a557ec3c21f1fb765adf/tumblr_nqbe74HYGo1tupbydo2_500.png
Now as you can see, i was able to populate the first two views by creating a custom class, creating custom objects of that class and using the properties within the class. Now my dilemma is, each of the "sportCategories" properties have their OWN table views to expand to which will consist of a whole list of names of players in that respective sport. Now what method should i go about doing this? should i create a whole new class in my second table view controller to give the sportsCategories their own properties? or is there a way i can already expand off the work I've already done? a more efficient way?
If you only have one detail controller, you don't need the switch statement at all. The best (most object oriented) way to do this would be to use custom objects to populate your cells. You could create a Sport object that would have two properties, name (NSString), and categories (NSArray). In the master table view controller, you would create all the Sport objects, and add them to an array, sports. You would use sports to populate the array with,
let aSport = sports[indexPath.row] as! Sport
cell.textLabel.text = aSport.name
In didSelectRowAtIndexPath, you create an instance of DetailViewController, set the value of a property in that class (lets call it selectedSport), and push it,
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let detailVC = DetailViewController()
detailVC.selectedSport = sports[indexPath.row]
navigationController?.pushViewController(detailVC, animated: true)
}
In DetailViewController, you would use selectedSport.categories to populate the table view. Since you have access to the whole Sport object, you could use selectedSport.name to provide the title for the controller.
If the hierarchy is just from the main view controller to one of 21 specific view controllers and back (not 21 nested view controllers), and the behaviour of all these 21 tables is very similar, then there isn't really much reason to have 21 view controllers. Have one view controllers for all the 21 displays, then before you switch to it you tell it what data to display, and of course it has to be able to display the data of all 21 views. (If you had 15 views looking one way and 6 views looking a different way, you would use two view controllers).
(I don't like making things depending on a row index. That just makes it very difficult if you want to arrange the rows in a different way).

Resources