How to pass data through table views - swift

my data structure sample..
Sports
Bat-and-ball
Baseball
Softball
Cricket
Hockey
Field Hockey
Ice Hockey
Roller Hockey
Engineering
Computer Science
Software Engineer
Electrical Engineer
Here I have three level data as you can see . but by default in tableview it provides only upto 2 level. how can i display the third level data.
I am only able to show the 1st level data in sections and second level data in rows using customcell. now where to display the third level data. so that i can iterate based on number of rows..
my screen should be like that..

There are plenty of ways to do it.
I will list a couple of them later.
But first you might think about whether it is a good user experience to show 3 levels of data in one table view. It might be difficult for the user to navigate through it.
So these are some options.
Show the top level data (let's call it a category) in a selector(dropdown) and then in a table view just have two levels (sections and rows) for the selected category.
Here is an example of such dropdown view:
https://github.com/PhamBaTho/BTNavigationDropdownMenu
If you really need to show a list with 3 levels of data you can have a table view, which will show the top level data as a section and second level as a row. For each row the corresponding cell will contain inside a table view (or might be just a stack view) showing just the rows for the 3rd level.

you can add table in custom cell and 2nd and third level data can display on that inner table.

This code ran beautifully on my device.
Storyboard
TableViewController1
import UIKit
class TableViewController1: UITableViewController {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return group1.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = group1[indexPath.row].title
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segue1" {
let indexPaths = self.tableView!.indexPathsForSelectedRows!
let indexPath = indexPaths[0] as NSIndexPath
let vc = segue.destinationViewController as! TableViewController2
vc.currentGroup = group1[indexPath.row].title
}
}
}
TableViewController2
import UIKit
class TableViewController2: UITableViewController {
var currentGroup: String?
var filteredData = [Data]()
override func viewDidLoad() {
super.viewDidLoad()
for data in group2 {
if data.group == currentGroup {
filteredData.append(data)
}
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = filteredData[indexPath.row].title
return cell
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segue2" {
let indexPaths = self.tableView!.indexPathsForSelectedRows!
let indexPath = indexPaths[0] as NSIndexPath
let vc = segue.destinationViewController as! TableViewController3
vc.currentGroup = group2[indexPath.row].title
}
}
}
TableViewController3
import UIKit
class TableViewController3: UITableViewController {
var currentGroup: String?
var filteredData = [Data]()
override func viewDidLoad() {
super.viewDidLoad()
for data in group3 {
if data.group == currentGroup {
filteredData.append(data)
}
}
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = filteredData[indexPath.row].title
return cell
}
}
Data Class
import Foundation
class Data {
var title: String
var group: String?
init (title: String, group: String?) {
self.title = title
self.group = group
}
}
let group1 = [Data(title: "Sport", group: nil), Data(title: "Engineering", group: nil)]
let group2 = [Data(title: "Hockey", group: "Sport"), Data(title: "CS", group: "Engineering")]
let group3 = [Data(title: "Ice Hockey", group: "Hockey"), Data(title: "Software Engineer", group: "CS")]
P.S. I hate to make things complicated.
Happy hacking!

Related

Cannot add cell to table view

I'm new to this, I've just made an indexed table view "groups search" with all groups in my app and got a problem: when I'm trying to add some group to the "my groups" view, there are should appear a selected group, but actually I got the first one from all groups array instead. Also I can't add several items started with a similar letter in the "my groups". It might be stupid, but I have no idea how to fix that. Thank you!
import UIKit
final class AllGroupsViewController: UITableViewController {
var groups = [
"cats",
"birds",
"dogs",
"books",
"music",
"movies",
"art",
"science",
"tech",
"beauty",
]
var groupSectionTitles = [String]()
var groupsDictionary = [String: [String]]()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(
nibName: "GroupCell",
bundle: nil),
forCellReuseIdentifier: "groupCell")
for group in groups {
let groupKey = String(group.prefix(1))
if var groupValues = groupsDictionary[groupKey] {
groupValues.append(group)
groupsDictionary[groupKey] = groupValues
} else {
groupsDictionary[groupKey] = [group]
}
}
groupSectionTitles = [String](groupsDictionary.keys)
groupSectionTitles = groupSectionTitles.sorted(by: { $0 < $1 })
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return groupSectionTitles.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let groupKey = groupSectionTitles[section]
if let groupValues = groupsDictionary[groupKey] {
return groupValues.count
}
return 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return groupSectionTitles[section]
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return groupSectionTitles
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as? GroupCell
else { return UITableViewCell() }
var currentGroup = groups[indexPath.row]
let groupKey = groupSectionTitles[indexPath.section]
if let groupValues = groupsDictionary[groupKey] {
currentGroup = groupValues[indexPath.row]
}
cell.configure(
photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
name: currentGroup)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
defer { tableView.deselectRow(
at: indexPath,
animated: true) }
performSegue(
withIdentifier: "addGroup",
sender: nil)
}
}
import UIKit
final class MyGroupsViewController: UITableViewController {
var groups = [String]() {
didSet {
//
}
}
#IBAction func addGroup(segue: UIStoryboardSegue) {
guard
segue.identifier == "addGroup",
let allGroupsController = segue.source as? AllGroupsViewController,
let groupIndexPath = allGroupsController.tableView.indexPathForSelectedRow,
!self.groups.contains(allGroupsController.groups[groupIndexPath.section])
else { return }
self.groups.append(allGroupsController.groups[groupIndexPath.section])
tableView.reloadData()
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(
nibName: "GroupCell",
bundle: nil),
forCellReuseIdentifier: "groupCell")
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
groups.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as? GroupCell
else { return UITableViewCell() }
let currentGroup = groups[indexPath.row]
cell.configure(
photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
name: currentGroup)
return cell
}
override func tableView(
_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
groups.remove(at: indexPath.row)
tableView.deleteRows(
at: [indexPath],
with: .fade)
}
}
}
First here is what I understand the goal is:
There is a UITableViewController called MyGroupsViewController which should show all the groups the user has selected
On tapping on + from the MyGroupsViewController, the user is taken to another UITableViewController called AllGroupsViewController which shows all the groups the user can join
On selecting a UITableViewCell from AllGroupsViewController, you will unwind back to MyGroupsViewController showing the groups added for the user
I will say you were quite close and I have just added some minor things which I hope will bring you close to your goal.
One way to pass data between UIViewControllers when using segue transitions is to override a function called prepare for segue (Read more about it in the Apple docs)
1
First change I will make is to add the override prepareForSegue to the end of the MyGroupsViewController class so that I can pass the groups array to the AllGroupsViewController
You can put it anywhere, I added it below your tableview editing function
override func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
groups.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
// This was added by me
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Check the correct segue identifier and retrieve the destination
if segue.identifier == "addGroup",
let allGroupsViewController = segue.destination as? AllGroupsViewController
{
// Set the groups variable with data we stored in the
// user groups array
allGroupsViewController.userGroups = groups
}
}
2
In AllGroupsViewController, I added an array user groups to store all the groups the user taps on and the existing user's groups will be sent to it by MyGroupsViewController in step 1
class AllGroupsViewController: UITableViewController {
var groups = [
"cats",
"birds",
"dogs",
"books",
"music",
"movies",
"art",
"science",
"tech",
"beauty",
]
// I added this. This will save groups the user wants
var userGroups: [String] = []
3.
Then I made some small changes to your tableView didSelectRowAtIndexPath function to store what the user has tapped
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
defer {
tableView.deselectRow(at: indexPath, animated: true)
}
// Added by me: Retrieve the group tapped by the user
let groupKey = groupSectionTitles[indexPath.section]
var currentGroup = ""
if let groupValues = groupsDictionary[groupKey] {
currentGroup = groupValues[indexPath.row]
}
// Added by me: Check that this group was not added for the user
// before to avoid duplicates
if userGroups.firstIndex(of: currentGroup) == nil {
userGroups.append(currentGroup)
}
self.performSegue(withIdentifier: "addGroup", sender: nil)
}
4
Finally, back in MyGroupsViewController, I updated your addGroup unwind function slightly as follows:
#IBAction func addGroup(segue: UIStoryboardSegue) {
guard segue.identifier == "addGroup",
let allGroupsViewController = segue.source as? AllGroupsViewController
else { return }
// Added by me. Removed old code and added by me. Just update
// the groups array with what was selected in AllGroupsViewController
groups = allGroupsViewController.userGroups
tableView.reloadData()
}
You will get this end result with groups added to the user, without duplicates and also you are able to add groups with the same starting letter.
You can watch the experience here on Youtube
If I missed something or did not answer something from your question, please add some more info / questions in the comments.

How to make a push segue when a specific UITableViewCell is selected

I am working on my first iOS app and new student to swift.
Everything is working however I am having trouble figuring out how to segue from a specific cell to a third view controller. I have a IOS UITableView with three sections and a total of (44) cells. All cells when tapped segue to one DetailVC titled: showProductDetai which is fine. The problem that I am having is that I need to have only (1) particular cell in section 0 row 5 in the UITableView to go to its own ViewController in which I titled: second view controller instead of the normal showProductDetail VC. Is there an intelligent way to make the specific cell in section 0 row 5 of the tableView go to second view controller when selected?
Here is my current code that is working. How would I code to make the changes?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath) as! ProductTableViewCell
// Configure the cell...
let productLine = productLines[indexPath.section]
let products = productLine.products
let product = products[indexPath.row]
cell.product = product
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let productLine = productLines[section]
return productLine.name
}
// Mark: UITableViewDelegate
var selectedProduct: Product?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let productLine = productLines[indexPath.section]
let product = productLine.products[indexPath.row]
selectedProduct = product
performSegue(withIdentifier: "ShowProductDetail", sender: nil)
}
// Mark: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "ShowProductDetail" {
let DetailVC = segue.destination as! DetailViewController
DetailVC.product = selectedProduct
}
}
see what I did here? You can use the indexPath parameter to get the section & row the user touched, and then you can set up your programmatic segue.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if ((indexPath.section == 0) && (indexPath.row == 5)) {
performSegue(withIdentifier: "GoToSecondViewController", sender: nil)
} else {
let productLine = productLines[indexPath.section]
let product = productLine.products[indexPath.row]
selectedProduct = product
performSegue(withIdentifier: "ShowProductDetail", sender: nil)
}
}
Write a segue code for the desired section and row:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 && indexPath.row == 5 {
//Perform Segue
} else {
//Rest of the functionality
}
}
Make sure you have connected proper Sugues and given proper identifiers in Storyboard.
You don't need to write prepare Segue method

Swift: How to access the cells within a UITableView

I currently have UITableView that has one UITableViewCell within it. Within my code I created a button over this cell. When that button is pressed, the cells within it expand.
How would I go about making something expand when one of those cells are selected? Would I need to put a UITableView within the cell?
This is what I would do if I needed to bang something out in a hurry. Let each vegetable occupy its own table section, then expand or contract the section when the user toggles:
// We need some kind of data model.
class Vegetable {
init(name: String, facts: [String]) {
self.name = name
self.facts = facts
}
let name: String
let facts: [String]
var isShown: Bool = false // This flag gets toggled, which is why I've defined it as a class.
}
// And a table view controller configured for dynamic (not static!) cells. Create this in a storyboard, and change the UITableViewControllers class to VeggieTable.
class VeggieTable: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
veggieData = [Vegetable(name: "Tomato", facts: ["Number: 15", "Red Colored"]),
Vegetable(name: "Lettuce", facts: ["Good with peanut butter", "Green", "Crunchy snack"])] // etc.
}
var veggieData: [Vegetable]!
override func numberOfSections(in tableView: UITableView) -> Int {
return veggieData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let veg = veggieData[section]
return veg.isShown ? veg.facts.count + 1 : 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let veg = veggieData[indexPath.section]
switch indexPath.row {
case 0:
cell.textLabel?.text = veg.name
default:
cell.textLabel?.text = veg.facts[indexPath.row - 1]
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let veg = veggieData[indexPath.section]
// Toggle the is shown flag:
veg.isShown = !veg.isShown
tableView.deselectRow(at: indexPath, animated: true)
// TODO: replace reloadData() with sophisticated animation.
// tableView.beginUpdates()
// tableView.insertRows() / deleteRows() etc etc.
// tableView.endUpdates()
tableView.reloadData()
}
}
Eventually you'd add table animations to make the rows appearing look smooth etc.
Hope that helps.

SWIFT if statement and segue

I have a problem with if statement and segue
can I make a connection between them !
I have an array of these states ["Florida","NY"]()
my idea, for example, if I clicked "Florida"<(this is "string") on table view
I want the segue moves me from this table view to another table view
and if I clicked "NewYork" on tableView I want the segue moves me from this table view to another that has information about NY
thanks
You could do something like this:
class SelectCityViewController : UITableViewController {
// store a reference to use in transition
var selectedCity : String?
let cities : [String] = ["New York", "San Francisco", "Los Angeles"]
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = //TODO: your implementation here
cell.textLabel?.text = cities[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.selectedCity = cities[indexPath.row]
performSegueWithIdentifier("YOUR_SEGUE_IDENTIFIER", nil)
}
override func tableView(tableView: UITableView, numberOfItemsInSection section: Int) -> Int {
return self.cities.count
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? CityDetailViewController {
vc.selectedCity = self.selectedCity!
}
}
}
Then your second view controller would have a variable with your selected city stored in it so you could setup your content accordingly
class SelectedCityDetailViewController : UITableViewController {
// Set during the segue
var selectedCity : String!
// The rest of your methods
}
you can do something like this in didSelectRowAtIndexPath.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let currentCell = tableView.cellForRowAtIndexPath(indexPath)! as UITableViewCell
if let text = currentCell.textLabel?.text {
if text == "Florida"{
// segue tableview controller
} else if text == "NY" {
//another segue
}
}
}
Take a viewController and add a tableView. Add two another view controllers, one for the NY info and the other containing one more uiTableView. Create two segues from main controller to the other two. Name the segues so that you can refer to them in the code. In didSelectRowAtIndexPath add the following Code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let currentCell = tableView.cellForRowAtIndexPath(indexPath)! as UITableViewCell
if indexPath.row == 0{
self.performSegueWithIdentifier("florida", sender: nil)
}else if indexPath.row == 1{
self.performSegueWithIdentifier("ny", sender: nil)
}
}
Please check this Link. I have created a demo project for you. I think you are a starter to the iOS development. Carry On!! :)

Segue not sending correct cell info in Swift

I have a tableView that triggers a segue to a detail view when a cell is pressed. The table contains a list of users that the currentUser is friends with. Pressing the cell loads the view for that user (name, profile, etc). It's a very simple app I'm creating to learn how to program.
The problem is that when I press on a cell, it's always loading the user info for the last user in the table (and also happens to be the most recent "friend" that the user made). I have a feeling that the issue is with an if statement I have in the tableView function:
extension MatchesViewController: UITableViewDataSource
{
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return numberOfMatches
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("MatchCell", forIndexPath: indexPath)
if PFUser.currentUser()!.objectId == self.user1.objectId{
let user = matchesResults[indexPath.row]["user2"] as! PFUser
cell.textLabel?.text = user["first_name"] as! String
self.viewUser = user
}
if PFUser.currentUser()!.objectId == self.user2.objectId{
let user = matchesResults[indexPath.row]["user1"] as! PFUser
cell.textLabel?.text = user["first_name"] as! String
self.viewUser = user
}
return cell
}
}
Here's the segue code, but I don't think there is an issue with it (although I could be wrong):
extension MatchesViewController: UITableViewDelegate
{
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
self.performSegueWithIdentifier("UserSegue", sender: "viewUser")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "UserSegue") {
let destinationVC = segue.destinationViewController as! UserViewController
destinationVC.user = self.viewUser
}
}
}
Any ideas? If there is an issue with my tableView If statement, how can I fix it?
Thanks!
You can get the user object by indexPath, than pass it through the sender parameter of the performSegueWithIdentifier method
extension MatchesViewController: UITableViewDelegate
{
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let user:PFUser?
if PFUser.currentUser()!.objectId == self.user1.objectId{
user = matchesResults[indexPath.row]["user2"] as! PFUser
}
if PFUser.currentUser()!.objectId == self.user2.objectId{
user = matchesResults[indexPath.row]["user1"] as! PFUser
}
self.performSegueWithIdentifier("UserSegue", sender: user)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// sender is the user object
if (segue.identifier == "UserSegue") {
let destinationVC = segue.destinationViewController as! UserViewController
destinationVC.user = sender // maybe you need cast sender to UserObject
}
}
}