Cannot add cell to table view - swift

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.

Related

TableView SearchBar doesn't work: Index out of range

I know there are some similar questions, but it's doesn't work to me. I'm new to this, so I followed some tutorial trying make a search bar in my table view screen.
I got a problem: there are index out of range and I cannot realise why.
Here is my code:
import UIKit
final class AllGroupsViewController: UITableViewController {
var groups = [
"cats",
"birds",
"dogs",
"books",
"music",
"movies",
"art",
"science",
"tech",
"beauty",
]
#IBOutlet var searchBar: UISearchBar!
var isSearching = false
var filteredData = [String]()
var userGroups: [String] = []
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 {
if isSearching {
return filteredData.count
} else {
return groups.count
}
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]
}
if isSearching {
currentGroup = filteredData[indexPath.row]
} else {
currentGroup = groups[indexPath.row]
}
return cell
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)
}
let groupKey = groupSectionTitles[indexPath.section]
var currentGroup = ""
if let groupValues = groupsDictionary[groupKey] {
currentGroup = groupValues[indexPath.row] // here is index out of range
}
if userGroups.firstIndex(of: currentGroup) == nil {
userGroups.append(currentGroup)
}
self.performSegue(withIdentifier: "addGroup", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addGroup",
let myGroupsViewController = segue.destination as? MyGroupsViewController {
myGroupsViewController.groups = userGroups
}
}
}
extension AllGroupsViewController {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = groups.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
isSearching = true
tableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
isSearching = false
searchBar.text = ""
tableView.reloadData()
}
}
I'll be so glad if somebody will help me. And, please, can you recommend me some good tutorial to achieve my aim?
Actually issue is more to do with logic of accessing groups than crash because of adding search bar.
For example:
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
if isSearching {
return filteredData.count
} else {
return groups.count
}
let groupKey = groupSectionTitles[section]
if let groupValues = groupsDictionary[groupKey] {
return groupValues.count
}
return 0
}
Here because you use if-else you will either return filteredData.count when searching or groups.count - you will not go beyond this code
So when you are not searching, you will return groups.count which is 10 and that is wrong because you want to return the count for which section we are in, for example a should return 1, b should return 3.
The logic after if-else block should replace logic in else section
Now looking at next two functions:
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]
}
if isSearching {
currentGroup = filteredData[indexPath.row]
} else {
currentGroup = groups[indexPath.row]
}
return cell
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)
}
let groupKey = groupSectionTitles[indexPath.section]
var currentGroup = ""
if let groupValues = groupsDictionary[groupKey] {
currentGroup = groupValues[indexPath.row] // here is index out of range
}
if userGroups.firstIndex(of: currentGroup) == nil {
userGroups.append(currentGroup)
}
self.performSegue(withIdentifier: "addGroup", sender: nil)
}
First because numberOfRowsInSection returns wrong values, we will have issues in these functions.
Then I think the logic of accessing the right data source of groups, group sections is not done right.
For example: currentGroup = groups[indexPath.row] in cellForRowAt indexPath is not right because this gets group from group array of 10 when we only want to group for the specific section.
And also I see return cell twice so code after the first will not be run.
So what I did is just refactored these functions to make it more clear and added some comments.
First, we need to keep in mind the different data sources:
// All the groups
var groups = [
"cats",
"birds",
"dogs",
"books",
"music",
"movies",
"art",
"science",
"tech",
"beauty",
]
// Checks if search is active or not
var isSearching = false
// This will hold the filtered array when searching
var filteredData = [String]()
// This will hold groups of the user
var userGroups: [String] = []
// This will hold section prefixes [a, b, c, etc]
var groupSectionTitles = [String]()
// This will hold mapping of prefixes to groups
// [a: [art], b: [beauty, books], etc]
var groupsDictionary = [String: [String]]()
There is nothing different above from your code, only comments, however we have to keep a visual image of this because this is important to how we need to access the data
Next, I created this function to get the correct groups in a section since we need to do this many times
private func getGroups(in section: Int) -> [String]
{
// The current section should be got from groupSectionTitles
let groupKey = groupSectionTitles[section]
var groupsInSection: [String] = []
// Get groups for current section
if let groupValues = groupsDictionary[groupKey] {
groupsInSection = groupValues
}
// Change groups in section if searching
if isSearching {
groupsInSection = filteredData
}
return groupsInSection
}
Then I refactored these functions slightly:
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
if isSearching {
return filteredData.count
} else {
let groupsInSection = getGroups(in: section)
return groupsInSection.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 groupsInSection = getGroups(in: indexPath.section)
cell.configure(
photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
name: groupsInSection[indexPath.row])
return cell
}
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath)
{
let groupsInSection = getGroups(in: indexPath.section)
let currentGroup = groupsInSection[indexPath.row]
if userGroups.firstIndex(of: currentGroup) == nil {
userGroups.append(currentGroup)
}
defer {
tableView.deselectRow(at: indexPath, animated: true)
}
self.performSegue(withIdentifier: "addGroup", sender: nil)
}
I think now your crash will be resolved and things work as expected.
However, since you did not connect and implement search delegate yet, maybe there can be some issues when isSearching becomes true but I think that can be for another question on filtering with search delegate.
For tutorials, you can have a look at:
UISearchResultsController tutorial - watch from minute 8 onwards
UISearchBar tutorial - watch from minute 10 onwards
StackOverflow discussion

Is there any better way to send information to child view?

I have a view controller which has a child view. I want to convey information from the view to the child view. To do so, I did this :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "visualize", sender: self)
invoiceNumber = indexPath.row
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PreviewViewController
vc.invoiceNumber = invoiceNumber
}
But the problem is that the value of invoiceNumber is not updated on the first iteration but on the second. I tried to see what the problem was and found that "invoiceNumber = indexPath.row" runs after "vc.invoiceNumber = invoiceNumber". Please help ! Thanks
Here is the code of printing :
import UIKit
class ViewController: UIViewController {
var invoiceNumber: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(invoiceNumber)
}
}
Sorry I have mistaken :
var customerData: [[customerInformation]] = []
var itemsData: [[Item]] = []
var totalData: [TotalInformation] = []
var invoiceNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
let tabBar = tabBarController as! baseTabBarController
customerData = tabBar.customerData
itemsData = tabBar.itemsData
totalData = tabBar.totalData
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return customerData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let invoiceCell = tableView.dequeueReusableCell(withIdentifier: "invoice", for: indexPath) as! invoiceTableViewCell
invoiceCell.textLabel?.text = (customerData[indexPath.row][0]).input
invoiceCell.detailTextLabel?.text = "Invoice n°" + String(indexPath.row)
invoiceCell.totalLabel.text = (totalData[indexPath.row]).total
return invoiceCell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let tabBar = tabBarController as! baseTabBarController
tabBar.customerData.remove(at: indexPath.row)
//itemData.remove(at: indexPath.row)
tabBar.totalData.remove(at: indexPath.row)
customerData = tabBar.customerData
itemsData = tabBar.itemsData
totalData = tabBar.totalData
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
invoiceNumber = indexPath.row
print(invoiceNumber, "yes")
performSegue(withIdentifier: "visualize", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! PreviewViewController
vc.invoiceNumber = invoiceNumber
}
}
It is the "vc.invoiceNumber = invoiceNumber" which comes before the "invoiceNumber = indexPath.row" as I tried to change the value of invoiceNumber a variable in the view and print out the value in the child view and if showed its value first (I tested -1).
I added the Preview view controller which receives the invoiceNumber :
import UIKit
import WebKit
class PreviewViewController: UIViewController {
#IBOutlet var webPreview: UIWebView!
var invoiceComposer: InvoiceComposer!
var HTMLContent: String!
var invoiceNumber: Int = -1
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(invoiceNumber)
createInvoiceAsHTML()
}
func createInvoiceAsHTML() {
invoiceComposer = InvoiceComposer()
if let tabBar = tabBarController as? baseTabBarController {
if let invoiceHTML = invoiceComposer.renderInvoice(invoiceNumber: String(invoiceNumber), invoiceDate: "", recipientInfo: tabBar.customerData[invoiceNumber][0].input, items: tabBar.itemsData[invoiceNumber], totalAmount: tabBar.totalData[invoiceNumber].total) {
webPreview.loadHTMLString(invoiceHTML, baseURL: NSURL(string: invoiceComposer.pathToInvoiceHTMLTemplate!)! as URL)
HTMLContent = invoiceHTML
}
}
else {
print("tabBarController is not of type baseTabBarController or either nil ")
}
}
}
Change the order of execution, like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
invoiceNumber = indexPath.row
performSegue(withIdentifier: "visualize", sender: self)
}
I should have connected the segue from the view controller to the child view and not from the cell to the child view.

Use segue for item selected in table view

I've looked at other possible solutions here on StackOverflow but don't quite think they apply to my issue or simple I don't understand is the greater possibility. Here is what I am trying to do. I have a tableview with sections and items in each section. I want to segue to a different view controller based on the selection made by the user in the tableview. I have set up a sample with the following code with segues "report1Segue" and "report2Segue" as an example.
class ViewController: UIViewController {
#IBOutlet weak var reportTableView: UITableView!
let reportGroup = ["Dietary", "Weight", "Sleep", "Meditation", "Fitness"]
let report = [["Calorie Breakdown", "Net Calories"], ["Last 3 Months"], ["Last 3 Months"],["Last 3 Months"],["Exercises by Type"]]
func numberOfSections(in tableView: UITableView) -> Int {
return report.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return reportGroup[section]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "reportSegue" {
if let reportVC = segue.destination as? Report1ViewController, let indexPath = reportTableView.indexPathForSelectedRow {
reportVC.dataToDisplay = report[indexPath.section][indexPath.row]
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return report[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = report[indexPath.section][indexPath.row]
return cell
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "report1Segue", sender: nil)
}
}
Any guidance is always greatly appreciated.
Swift 5.2
For fixed data in a UITableView you can do it like this:
// Hard code your IndexPath references
// This will make the code easier to read in the delegate methods
enum TableIndex {
static let calorieBreakdown = IndexPath(row: 0, section: 0)
static let netCalories = IndexPath(row: 1, section: 0)
static let weightLastThreeMonths = IndexPath(row: 0, section: 1)
static let sleepLastThreeMonths = IndexPath(row: 0, section: 2)
static let meditationLastThreeMonths = IndexPath(row: 0, section: 3)
static let fitnessExercises = IndexPath(row: 0, section: 4)
}
// Example - perform whatever Segues you actually need
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath {
case TableIndex.calorieBreakdown:
performSegue(withIdentifier: "report1Segue", sender: self)
case TableIndex.weightLastThreeMonths, TableIndex.sleepLastThreeMonths:
performSegue(withIdentifier: "report2Segue", sender: self)
default:
break
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let data = report[indexPath.section][indexPath.row]
switch segue.identifier {
case "report1Segue":
if let dest = segue.destination as? Report1ViewControler {
dest.dataToDisplay = data
}
case "report2Segue":
if let dest = segue.destination as? Report2ViewControler {
dest.dataToDisplay = data
}
default:
break
}
}
It seems like what needs to happen here is 1, in your didSelectRowAt, you need to figure out what is tapped, So based on how you are displaying the cells, you need to do the same here to find out what cell is tapped. Then based on that selection you will either have a switch case or if else statements that will each call its unique performSegue so that based on a cell selection it will go to a different selection.
For your example, you 'selection' is report[indexPath.section][indexPath.row]
then if switch or if else
if will be like
if selection == "Last 3 Months" || selection == "Net Calories" {
performSegue(withIdentifier: "report1Segue", sender: nil)
} else if selection == "Calorie Breakdown" {
performSegue(withIdentifier: "report2Segue", sender: nil)
}
Hope this helps you out.

How to programmatically segue from searchbar to display screen

So I have recently coded a search bar programmatically using tableview and navigation control. I am now having trouble finding information on how to segue the search bar after the user clicks on an item in the search bar.
I have tried using a view controller but that has not worked. I think my best bet is to do it programmatically.
EDIT There is no function yet in this code to implement a display screen. Im wondering what code is needed (im a very new developer) to get to another screen after clicking an element in the search bar. anything helps!!***
import UIKit
class SearchTableViewController: UITableViewController, UISearchBarDelegate {
let searchBar = UISearchBar()
let tableData = ["Boston University", "Boston College", "Northeastern University", "Suffolk University", "American University", "Harvard University", "Massachusetts Institute of Technology", "Tufts University", "Berklee College of Music", "Emerson College"]
//variables added for search function
var filteredArray = [String()]
var shouldShowSearchResults = false
override func viewDidLoad() {
super.viewDidLoad()
createSearcherBar()
}
func createSearcherBar() {
searchBar.showsBookmarkButton = false
searchBar.placeholder = "Search College"
searchBar.delegate = self
self.navigationItem.titleView = searchBar
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredArray = tableData.filter({ (names: String) -> Bool in
return names.range(of: searchText, options: .caseInsensitive) != nil
})
if searchText != "" {
shouldShowSearchResults = true
self.tableView.reloadData()
}
else{
shouldShowSearchResults = false
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if shouldShowSearchResults {
return filteredArray.count
}
else{
return tableData.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath)
if shouldShowSearchResults {
cell.textLabel!.text = filteredArray[indexPath.row]
return cell
}
else{
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
searchBar.endEditing(true)
self.tableView.reloadData()
}
/*
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
Hopefully I can get a user to click on one of the options in a search bar, and it brings them to a different page.
First of all your method textDidChange is unnecessarily expensive because you are always filtering the array even if the search text is empty. This is more efficient
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
shouldShowSearchResults = false
filteredArray.removeAll() // good practice to release memory when the search is finished
} else {
filteredArray = tableData.filter{ $0.range(of: searchText, options: .caseInsensitive) != nil }
shouldShowSearchResults = true
}
self.tableView.reloadData()
}
Second of all the declaration of filteredArray is slightly wrong. The parentheses must be outside the brackets. Your syntax declares a string array containing one empty string.
var filteredArray = [String]()
To answer your question implement didSelectRowAt and add the same shouldShowSearchResults logic to distinguish the arrays. Call performSegue and pass the string as sender
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = shouldShowSearchResults ? filteredArray[indexPath.row] : tableData[indexPath.row]
performSegue(withIdentifier: "MyIdentifier", sender: item)
}
and get it in prepare(for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "MyIdentifier" else { return }
let destinationController = segue.destination as! MyViewController
let item = sender as! String
...

Get cell index from tableviewcell

Hey I have this tableview controller that list restaurants from my database. what I want to do is if the cell/row is selected it opens a more detail page about the restaurant. for some reason I can't retrieve the index or its not going to the func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) to give me index here is what I have:
class HomeTableViewController: UITableViewController{
var restaurantArray = [Restaurant]()
var resTransfer: Restaurant?
var resName: String?
var dataBaseRef: FIRDatabaseReference! {
return FIRDatabase.database().reference()
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Home"
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Main Menu", style: .plain, target: self, action: #selector(SSASideMenu.presentLeftMenuViewController))
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
fetchRestaurants()
}
func fetchRestaurants(){
FIRDatabase.database().reference().child("AthensRestaurants/Restaurants").observe(.value, with: { (snapshot) in
var results = [Restaurant]()
for res in snapshot.children{
let res = Restaurant(snapshot: res as! FIRDataSnapshot)
results.append(res)
}
self.restaurantArray = results.sorted(by: { (u1, u2) -> Bool in
u1.name < u2.name
})
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
// MARK: - Table view data source
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return restaurantArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "restaurantsCell", for: indexPath) as! RestaurantsTableViewCell
// Configure the cell...
cell.configureCell(res: restaurantArray[indexPath.row])
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("Row \(indexPath.row)selected")
resTransfer = restaurantArray[indexPath.row]
resName = restaurantArray[indexPath.row].name
print(resName as Any)
performSegue(withIdentifier: "RestaurantDetailView", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "RestaurantDetailView") {
let vc = segue.destination as! RestaurantDetailViewController
vc.resNam = restaurantArray.first?.name //instead of first should be index of cell!
print(vc.resNam! as String)
}
}
}
I am doing it like this, without segue:
func showRestaurantViewControllerWith(_ id: String) {
let storyBoard = UIStoryboard(name: "RestaurantCard", bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: "RestaurantCard") as! RestaurantCardViewController
destinationVC.restaurant.id = id
self.navigationController?.pushViewController(destinationVC, animated: true)
}
and
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.showRestaurantViewControllerWith(self.allRestaurants[indexPath.row].id)
}
You have to use this function to fetch the index
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
print(indexPath.row)
}
It will give you the index.
First of all your code is obviously Swift 3 so the signature of didSelectRowAt is wrong.
Another solution is to pass the indexpath as sender
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Row \(indexPath.row)selected")
performSegue(withIdentifier: "RestaurantDetailView", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "RestaurantDetailView" {
let indexPath = sender as! IndexPath
let restaurant = restaurantArray[indexPath.row]
let resName = restaurant.name
print(resName!)
let vc = segue.destination as! RestaurantDetailViewController
vc.resNam = resName
}
}
or still easier to connect the segue to the table view cell. Then you can delete didSelectRow... because prepare(for is called directly and the cell is passed as the sender parameter.
PS: Why is the property name optional? In real life have you ever heard about a restaurant without a name?