Alternative for switch statement, drill down Table Views? - ios

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).

Related

passing array values between view [swift 3] [iOS]

i've been developing apps in swift 3,
kinda new here (swift programming),
so here's the thing,
i want to pass the data from an array in first table view controller to second table view controller and then in second table view controller i want to use the if function to show different image in table view based on the values that i've been using in first table view,
i'd already did the segue thing but it didn't work
here's my first table view controller code block
var FirstTableArray = [String]()
var SecondArray = [SecondTable]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
FirstTableArray = ["Alam","Sejarah dan Budaya","Buatan"]
SecondArray = [SecondTable(SecondTitle:["11","12","13"]),
SecondTable(SecondTitle:["21","22","23"]),
SecondTable(SecondTitle:["31","32","33"])]
}
the "FirstTableArray" is the array that i wanna pass its values, here's other code snippet in the first table view controller for using the "FirstTableArray" values to show the label in table view
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Cell = self.tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CostumTableViewCell
Cell.judulGambar.text = FirstTableArray[indexPath.row]
Cell.namaGambar.image = UIImage(named: gambarMenu[indexPath.row])
Cell.namaGambar.layer.cornerRadius = Cell.namaGambar.frame.size.width/2
Cell.namaGambar.clipsToBounds = true
return Cell
}
now that i've been successfully passing the "SecondArray" values using this prepare for segue method but i can't do it for the "FirstTableArray" values
here's my segue method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var IndexPath : IndexPath = self.tableView.indexPathForSelectedRow!
let DestViewController = segue.destination as! SecondTableViewController
var SecondArrayTableTwo : SecondTable
SecondArrayTableTwo = SecondArray[IndexPath.row]
DestViewController.SecondArray = SecondArrayTableTwo.SecondTitle
}
and now here's my second table view controller that i want to pass the first table view controller data to this view, my second array data has been passed its value, but i cannot do it for the "FirstTableArray" values
my second table view controller code
var namaGambarDetail = ["gunungmahawu.jpg","pantaimalalayang.jpg","pulaumanadotua.jpg","pulausuladen.jpg"]
var gambarSejarah = ["pantaimalalayang.jpg","gunungmahawu.jpg","pulaumanadotua.jpg","pulausuladen.jpg"]
var FirstArray = [String]()
var SecondArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return SecondArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let Cell = self.tableView.dequeueReusableCell(withIdentifier: "SecondCell", for: indexPath) as! CostumTableViewCell
Cell.judulDetail.text = SecondArray[indexPath.row]
Cell.gambarTampilDetail.image = UIImage(named: namaGambarDetail[indexPath.row])
Cell.gambarTampilDetail.layer.cornerRadius = Cell.gambarTampilDetail.frame.size.width/2
Cell.gambarTampilDetail.clipsToBounds = true
return Cell
}
i want to pass the "FirstTableArray" value data when the row has been clicked, like an indexpath for selected values so i can change the image based on clicked values in first view with an if statement function like this
if(FirstTableArray.text.contains("my first table view array values")){ Cell.gambarTampilDetail.image = UIImage(named: namaGambarDetail[indexPath.row])}
sorry for my long post, hope u guys understand what i mean, sorry for my bad english tho, cmiiw, thanks, any help is appreciated :)
Remove this line from SecondTableViewController
var SecondArray = [String]()

Is it valid to reuse a second view controller with dynamic cells to display elements of a different array in Swift?

I currently have 2 table view controllers. I've added two disclosure indicators on two static cells for marital status and home state (canton). The user clicks on one of both and is taken to another view controller where he makes the appropriate selection.
The code is currently working for marital status. My question is if here I could reuse the second view controller (i.e. the one with the dynamic cells) for the same purpose but utilising a different array (in this case an array with states' names). For me it is clear that I could simply add a new view controller and implement the states' list there. Here is a screenshot of the storyboard:
First View Controller code:
import UIKit
class FirstTableViewController: UITableViewController, DataEnteredDelegate {
#IBOutlet var maritalStatusCell: UITableViewCell!
#IBOutlet var maritalStatusLabel: UILabel!
func userDidEnterInformation(info: String) {
maritalStatusLabel.text = "Marital Status: (\(info))"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "maritalStatusSegue" {
let sendingVC: SecondTableViewController = segue.destination as! SecondTableViewController
sendingVC.delegate = self
}
}
}
Second View Controller code:
import UIKit
protocol DataEnteredDelegate {
func userDidEnterInformation(info: String)
}
class SecondTableViewController: UITableViewController {
let maritalStatusArray: [String] = ["Single", "Married"]
let cantonArray: [String] = ["ZG", "ZH", "BE", "LU", "AG"]
var delegate: DataEnteredDelegate? = nil
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return maritalStatusArray.count
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if delegate != nil {
let information: String? = tableView.cellForRow(at: indexPath)?.textLabel?.text
delegate!.userDidEnterInformation(info: information!)
dismiss(animated: true, completion: nil)
self.navigationController?.popViewController(animated: true)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MaritalStatusCell", for: indexPath)
cell.textLabel?.text = maritalStatusArray[indexPath.row]
return cell
}
}
Does is make sense here to use the second table view controller for the states' list as well ? If yes, how can I implement that ? Thanks.
Yes you can use the Same View controller for displaying the Array of your states' names which I think you have declared in cantonArray, what you need to do is declare a bool variable in Second View Controller (In case if you want to manage only two arrays, if you want to manage more arrays then declare an enum). Then in the segue get from which index that segue is fired, you can get the selected indexPath like this
if let indexPath = tableView.indexPathForSelectedRow{
}
Now check the indexPath.row, if it is 0 then you have selected Marital State so you need to show maritalStatusArray array so make the bool variable true if you get indexpath.row = 1 then make that variable false
Now in Second View Controller add a condition as per the bool variable and show the data from that array like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MaritalStatusCell", for: indexPath)
if showMaritalArray {
cell.textLabel?.text = maritalStatusArray[indexPath.row]
} else {
cell.textLabel?.text = cantonArray[indexPath.row]
}
return cell
}
This is how you can declare enum
enum SelectedRow {
case MaritalStatus
case States
case ThirdRow
}
var selectedRow = SelectedRow.MaritalStatus

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.

How do I link dynamic prototype cells to different UITableViewControllers?

Pardon me but I'm new to code & Swift.
Can't post an image but I need to link my custom UITableViewController 7 dynamic prototype cells to 7 different UItableviewcontrollers. So far I tried to segue the cell to another UITableViewController, but the rest of the cells are linking to the same place as well. Lets say if i want to link a fast food category to a list of fast food restaurants in a UITableViewController, every other category is also linking to the same UITableViewController.
I'm not quite sure what to do.
My main controller code is as follows:
class CategoriesTableViewController: UITableViewController {
var category: [Categories] = categoriesData
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 {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return category.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CategoriesCell", forIndexPath: indexPath)as CategoriesCell
let categories = category[indexPath.row] as Categories
cell.textLabel?.text = categories.category
if let categoryLabel = cell.viewWithTag(100) as? UILabel {
categoryLabel.text = categories.category
}
return cell
}
This is my categories.swift file:
class Categories: NSObject {
var category: String
init(category: String) {
self.category = category
super.init()
}
}
and categories data.swift:
let categoriesData = [Categories(category:"Restaurants/Cafe"), Categories(category:"Fine Dining"), Categories(category:"Catering"),Categories(category:"Buffet"), Categories(category:"Food Court/Hawker Centre"), Categories(category:"Fast Food"), Categories(category:"Others")]
my categories cell.swift shows the following:
class CategoriesCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
}
Maybe can post pictures so we can see as to why you are going that route.
I think you want the sections of the tableview to display different content?
I would suggest:
split the table view into the number of sections you want (each section can have a given number of rows)
create one UITableViewController class
in your
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
Set your content based on section & row number
Try this:
Create 7 different prototype cells.
Change the reuse identifier of each prototype cell to match one of your categories.
Link each prototype cell to a different ViewController with a segue.
In cellForRowAtIndexPath, use the category for that row to fetch the correct prototype cell:
let categories = category[indexPath.row] as Categories
let cell = tableView.dequeueReusableCellWithIdentifier(categories.category, forIndexPath: indexPath) as CategoriesCell

Swift: How to load external array to my UITableView

I am learning Swift and I have pattern that I used to do in Objective C, but don't understand how to do it here.
I have UIViewController with TableView. I works fine when I put my array inside it. But according to MVC I want to move my array with data to another class. And I have no idea how to do it. Everything I tried doesn't work.
Thank you!
My code, how to move tableDS outside:
import UIKit
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
//temp table data
let tableDS = ["fdf", "dfd"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableDS.count
}
let textCellIdentifier = "TableViewCell"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MyCell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as MyCell
let row = indexPath.row
cell.dayLabel.text = tableDS[row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let row = indexPath.row
println(tableDS[row])
}
}
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = tableDS[indexPath.row]
return cell
}
This should work.
If you want to use the MVC pattern, create a new singleton class, create the array there, then create a method returning the array.
First you need to initialize your table view with an empty array. When you load your MyViewController from another view controller in the code example below you can pass your data, and change your let tableDS = [“fdf”, “dfd”] to var tableDS = [“fdf”, "dfd"]. let is used for a constant variables.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YourMyViewControllerSequeId" {
let myViewController = segue.destinationViewController as MyViewController
var myArrayToPass = ["learn swift", "or get a life"];
myViewController.tableDS = myArrayToPass
myViewController.tableView.reloadData()
}
}
In the MVC design pattern for a table view the table view is the view object. The controller is the view controller.
The model is whatever you use to store your data.
The controller object serves as an intermediary between the model and the view.
For a simple table view the model object can be a as simple as an array. The array is the model. Thus there is no reason to store the data in a separate object.
If you really want to make your model a completely different object, create a new class. Call it MyTableViewModel. Make your MyTableViewModel class contain an array of your data. Also make MyTableViewModel conform to the UITableViewDatasource protocol. To do that, you'll have to implement several methods - in particular, cellForRowAtIndexPath.
Now in your view controller, create a MyTableViewModel object as a strong property of your view controller, install the array in it, and make it the data source of the table view.
Done.
Again, though, it's quite common to just treat a simple array as your model, and let the view controller serve up cells by implementing cellForRowAtIndexPath in the view controller.

Resources