How to change the label of a different view controller when selecting data from a tableviewcontrollerin Swift 3 / xCode 8? - ios

I have a view controller which is used as a table view. From the table view I wish to select an item from the rows available and once selected I wish for that chosen thing to be the name of a label in a second view controller.
So
tableViewController - select an item from the list in the table
secondViewcontroller - label name is what is selected in the tableViewController
I have looked around and there is talk of using NSNotification but I can't seem to get it to work, in addition to using prepareForSegue.
My relevant code for the tableViewContrller is this:
//MARK Table View
//number of sections the table will have
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//number of rows each section of the table will have
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return peripheralArray.count
}
//the way the data will be displayed in each row for the sections
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let BluetoothNaming = peripheralArray[indexPath.row].peripheral.name
cell.textLabel?.text = BluetoothNaming
return cell
}
//what happens when we select an item from the bluetooth list
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
stopScanning()
peripheral = peripheralArray[(indexPath as NSIndexPath).row].peripheral
print ("connecting to peripheral called \(peripheral)")
//store the name of the connected peripeheral
let connectedPeripheral = peripheral
manager?.connect(connectedPeripheral!, options: nil)
performSegue(withIdentifier: "segueBackwards", sender: nil)
}
My secondViewController does not have much on its just the label:
import UIKit
class HomepageViewController: UIViewController {
#IBOutlet var ConnectionLabel: UILabel!
func changeBlueoothLabel() {
self.ConnectionLabel.text = "aaaa"
}
}
What do I need to do so that when I select a row in the table that the label in the secondViewController changes its label to reflect it.
Furthermore, if I wanted to change it so that I have another label on the secondViewController and I wanted to change it from disconnected to connected, would there be much more work involved?
Many thanks

to pass data add a string variable in the HomepageViewController and implement prepareForSegue method to pass selected name from tableViewController to HomepageViewController
in HomepageViewController
var selectedName: String?
in tableViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showHomePage" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let peripheral = peripheralArray[indexPath.row].peripheral.name
let homeController = segue.destination as! HomepageViewController
homeController.selectedName = peripheral
}
}
in HomepageViewController
import UIKit
class HomepageViewController: UIViewController {
#IBOutlet var ConnectionLabel: UILabel!
var selectedName: String?
override func viewDidLoad() {
super.viewDidLoad()
// Used the text from the First View Controller to set the label
if let name = selectedName {
ConnectionLabel.text = name
}
}
}

Related

UITableView Not Loading Data From Segue

I have a sequence where the user logs in and then they are brought to view showing a UITableView via a segue. I am trying to inject data from the login screen to the table after a successful login.
In the login view...
func transitionToHome() {
print("Hey you logged in!")=
performSegue(withIdentifier: "loginSuccess", sender: self)
self.navigationController!.setNavigationBarHidden(false, animated: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let vc = segue.destination as! ViewController
vc.models = [(title: "title", note: "Note")]
}
And in the Home Screen view
#IBOutlet var table: UITableView!
#IBOutlet var label: UILabel!
#IBOutlet weak var newNoteButton: UIButton!
var models: [(title: String, note: String)] = []
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
title = "Notes"
}
I have tried to call table.reloadData() in the the viewDidLoad, as well as in viewDidAppear and viewWillAppear. Neither has worked.
I also have printed out models in the viewDidLoad and I saw that the data is being correctly passed to the view controller. But,I cannot get the table to load this data when the view controller is loaded from the segue.
If you models data prints correctly in viewDidLoad() you should be able to configure a tableview cell with the data in the dataSource implementation. See sample extension for the dataSource implementation below. Make sure you provide a reuse identifier to your prototype cell in the storyboard.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var content = cell.defaultContentConfiguration()
content.text = "This is a cell primary text"
cell.contentConfiguration = content
return cell
}

Swift Tableview Load Data from Firebase Database

I am currently trying to display data from my real-time database of Firebase in different tableviews.
In my first tableview I load my first level of my database structure (this already works).
Now I want to see what the user selects in the first tableview, and then display the next level of my database in a new TableView.
I have a function in my second TableViewController.swift file where to save in the selected row from the first TableView.
This way I want to save the next level from my database into an array so that this data will be displayed in my second tableview. When I then debug my new array, I also see the correct data in the new array. However, the data is not displayed in the second TableView.
I guess it's because the data is not 100% ready before the TableView loads.
Do you have a tip?
Firebase Structure:
-sports
-Bicycle
-BMX
-Bike 1
-img: „bike1.png“
-text: „bike 1“
-Bike 2
-img: „bike2.png“
-text: „bike 1“
-Car
-Audi
-Car 1
-img: „car1.png“
-text: „car 1“
-Car 2
-img: „car2.png“
-text: „car 2“
FirstTableViewController:
import UIKit
import Firebase
class FirstTableViewController: UITableViewController {
var categorie = [String]()
func loadData() {
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("sports").observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.categorie.append(title)
self.tableView.reloadData()
}
}
})
}
override func viewDidLoad() {
loadData()
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categorie.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sportCell")
cell?.textLabel?.text = categorie[indexPath.row]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "firstToSecond", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "firstToSecond" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categorie[indexPath.row]
}
}
}
SecondTableViewController:
import UIKit
import Firebase
class SecondTableViewController: UITableViewController {
var detailedValue: String?
var secondArray = [String]()
func setIndex(value: String) {
loadData(index: value)
}
func loadData(index: String) {
var ref: DatabaseReference!
ref = Database.database().reference()
if (index != "") {
ref.child("sports").child(index).observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.secondArray.append(title)
self.tableView.reloadData()
}
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
loadData(index: detailedValue)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return secondArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sorteCell")
cell?.textLabel?.text = secondArray[indexPath.row]
return cell!
}
}
UPDATE 1:
Thanks to #Jay Lee for the above code.
UPDATE 2:
You are not loading the data to the SecondTableViewController instance that is presented on your screen, but to a new SecondTableViewController instance that you created in the func tableView(_:=,cellForRowAt:) method in your FirstTableViewController.
The logs are printed from the multiple instances you created from it.
This is not what you want, as you are creating multiple SecondTableViewController instances every time a new cell shows in your FirstTableViewController.
You should rather get a reference to the actual SecondTableViewController that is presented and supply the data it.
If you are using a storyboard, you can use prepare(for:sender:) to do that.
We have two choices: provide the entire data from the FirstTableViewController to SecondTableViewController using a delegate design pattern, or just provide value to SecondTableViewController and leave the fetching to it.
Based on your code, you can just supply the SecondTableViewController with value that your setIndex(value:) method in the SecondTableViewController uses, and get the data after the SecondTableViewController loads.
For example, in your SecondTableViewController:
class SecondTableViewController: UITableViewController {
...
var detailedValue: String?
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
setIndex(value: detailedValue)
}
}
...
}
and in your FirstTableViewController:
class FirstTableViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "yourIdentifier", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categories[indexPath.row]
}
}
}
But note that you already have a data to be shown on SecondTableViewController in your FirstTableViewController, so you should probably make a protocol and set FirstTableViewController its delegate.
EDIT:
Your segue should not be connected like this:
but like this:

How to press on a tableview cell to present a view controller with the text in navigation controller

Essentially I have a view controller called FirstViewController, this view controller contains a table view within it called listTableView.
I would like to tap on one of the cells in the table view listTableView and present whatever text was in the cell as the navigation controller title.
The navigation controller that appears when the cell is tapped is called showDetailsViewController.
How can this be done?
The following is what I have written in the FirstViewController
import UIKit
import AudioToolbox
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FeedModelProtocol {
var feedItems: NSArray = NSArray()
var selectedStock : StockModel = StockModel()
let tableView = UITableView()
#IBOutlet weak var listTableView: UITableView!
#IBOutlet weak var refreshButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
//set delegates and initialize FeedModel
self.listTableView.delegate = self
self.listTableView.dataSource = self
let feedModel = FeedModel()
feedModel.delegate = self
feedModel.downloadItems()
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
#IBAction func reloadData(_ sender: Any) {
print("reload pressed")
listTableView.reloadData()
viewDidLoad()
_ = AudioServicesPlaySystemSound(1519)
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
print("item feed loaded")
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "stockCell"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .center
myCell.textLabel?.font = .boldSystemFont(ofSize: 18)
// Get the stock to be shown
let item: StockModel = feedItems[indexPath.row] as! StockModel
// Configure our cell title made up of name and price
let titleStr = [item.customer].compactMap { $0 }.joined(separator: "-")
print(titleStr)
// Get references to labels of cell
myCell.textLabel!.text = titleStr
return myCell
}
}
UPDATE:
What is the issue with this code:
NOTE:
The restoration id of the tableview is scheduleTable
var homeworkIdentifierFromTableViewCell = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
homeworkIdentifierFromTableViewCell = feedItems[indexPath.row].myCell
self.performSegue(withIdentifier: "scheduleTable", sender: self)
listTableView.deselectRow(at: indexPath, animated: true)
}
UPDATE 2
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item: StockModel = feedItems[indexPath.row] as! StockModel
let titleStr = [item.customer].compactMap { $0 }.joined(separator: "-")
print(titleStr)
}
You can use the didSelectRowAt to notice what cell was clicked and store what the text in the cell was (homeworkArray is the list of cells from a struct. Homeworkidentifier is a value in the struct).
var homeworkIdentifierFromTableViewCell = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
homeworkIdentifierFromTableViewCell = homeworkArray[indexPath.row].homeworkIdentifier
self.performSegue(withIdentifier: "homeworktoExpandHomework", sender: self)
homeworkTableView.deselectRow(at: indexPath, animated: true)
}
Then, you could use a prepare for a segue function to pass the text of the table view cell to the next view controller. You do this by creating a variable in the other view controller (the one that you are going to pass data to) and later accessing it from the other view controller and changing its value.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "reportBug" {
let destinationViewController = segue.destination as! WebViewController
destinationViewController.reason = "reportBug"
}
else if segue.identifier == "provideFeedback" {
let destinationViewController = segue.destination as! WebViewController
destinationViewController.reason = "provideFeedback"
}
}
Here is more about passing data between viewcontrollers : Passing data between View Controllers in Swift (From TableView to DetailViewController)
Hope this helps
EDIT:
Here is the struct I am using :
struct homeworkTableViewCellData {
let homeworkName : String!
let className : String!
let dateName : String!
let colorImage : UIImage!
let homeworkIdentifier : String!
}
I have initialized my homeworkArray with this struct. When I am calling a value from the cell, I am picking one from in the struct.
To set the table view with a struct is more organized. This is a good video that teaches you how to set it up (if you are want to do that) : https://www.youtube.com/watch?v=zAWO9rldyUE&list=LL--UalPCi7F16WzDFhMEg7w&index=20&t=921s

How do you test UIStoryBoard segue is triggered by didSelectRow(at:)

So I have a storyboard with a UITableView. There is a prototype cell with a show segue hooked up to another UIViewController
Example
The cell identifier is "CellOne"
The segues has no identifier
My class is the dataSource and delegate for the tableView.
class looks like this:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "CellOne", for: indexPath)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected the row")
}
}
Normally I would test it by swizzling prepare for segue to capture the destination ViewController and whatever else I need but calling tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) programmatically doesn't trigger the code in prepare for segue.
Is it possible to test that selecting Cell One triggers the storyboard segue without adding a segue identifier and calling it explicitly from prepareForSegue?
If your tableViewCell is the only thing that triggers a segue to the destination you can use is or as:
if let destination = segue.destination as? MyViewController,
let indexPath = tableView.indexPathForSelectedCell {
destination.detail = model[indexPath.row]
}
Otherwise if you need to disambiguate you can check the class of the sender with is or as
Updated:
Long story short there isn't a great way to do this. What makes this difficult is that while most controls we're used to testing have an action and a sender, a touch on a UITableViewCell is a different paradigm.
That said, having a segue identifier is basically a pre-requisite for any strategy.
One way is to get a reference to the cell and call performSegue(withIdentifier:,sender:):
class ViewControllerTests: XCTestCase {
func testClickingACell() {
let controller = UIStoryboard(name: "ViewController", bundle: nil).instantiateInitialViewController() as! ViewController
let cell = controller.tableView.dataSource?.tableView(controller.tableView, cellForRowAt: IndexPath(row: 0, section: 0))
controller.performSegue(withIdentifier: "MySegue", sender: cell)
XCTAssertNotNil(controller.presentedViewController as? TheDestinationViewController)
}
}
Another (completely overkill) way would be to have a custom cell where you handle all of your own touch logic. This would be insane but it's a possibility and it would open up more options for testing. I'm not going to show this way because it would be an insane way to do it.
Another way is to use a different architecture that gets rid of UIKit and allows for testing just the performSegue logic. Example:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var myTableView: UITableView!
var navigator: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
navigator = self
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
navigator?.performSegue(withIdentifier: "MySegue", sender: nil)
}
}
This allows you to do something like this in your tests:
class MockNavigator: ViewController {
var performSegueCalled = false
var performSegueIdentifier: String?
override func performSegue(withIdentifier identifier: String, sender: Any?) {
performSegueCalled = true
performSegueIdentifier = identifier
}
}
func testExample() {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController
controller.loadViewIfNeeded()
// Need to keep a reference to be able to assert against it
let mockNavigator = MockNavigator()
controller.navigator = mockNavigator
controller.tableView(controller.myTableView, didSelectRowAt: IndexPath(row: 0, section: 0))
XCTAssertTrue(mockNavigator.performSegueCalled)
XCTAssertEqual(mockNavigator.performSegueIdentifier, "MySegue")
}
Another way to structure your code to avoid UIKit is to use something like a view model-coordinator pattern to create and test a viewModel. Basically you'd tell your coordinator that a cell was selected and the coordinator would update a view model with the desired segue identifier. This way you could test your coordinator object to and be mostly sure that you'll trigger the correct segue if the coordinator is hooked up. A simple manual test would tell you that.
In pseudocode:
struct ViewModel {
let labelText: String
let segueIdentifier: String
}
class Coordinator {
var data = [YourObject]()
var viewModel = ViewModel(labelText: "", segueIdentifier: "")
func selectedItem(at row: Int) {
let item = data[row]
// Do some logic to figure out which identifier you want
var segueIdentifer: String
if item == whatever {
segueIdentifier = "something"
}
viewModel = ViewModel(labelText: item.text, segueIdentifier: segueIdentifier)
}
}
Probably the best way is a combination of approaches. Use a coordinator with a view model that's tested on its own. Then have a test where you use UIKit to select a cell and make sure that a mocked implementation of that coordinator is used as expected. The smaller units you're testing at a time the easier it will be.
For just testing a segue you can do:
In your VC:
open let segueToSomewhere = "segueToSomewhere"
open var calledSegue: UIStoryboardSegue!
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
calledSegue = segue
}
In your Tests:
func testYourSegue() {
//Given
let storyboard = UIStoryboard(name: "Your_storyboard", bundle: nil)
let yourVC = storyboard.instantiateViewController(withIdentifier: "YourVC") as? YourVC
//When
yourVC.performSegue(withIdentifier: previousVC.segueToSomewhere, sender: nil)
//Then
XCTAssertEqual(previousVC.calledSegue.identifier, yourVC.segueToSomewhere, "The selected segue should be \(previousVC.segueToSomewhere)")
}

How do I send specific data based on which cell is clicked?

I have a tableview with a bunch of concerts, and when x cell is clicked, I want the artist of that concert to populate the new tableView. Below is the code for the first view controller (the view controller with all the concerts).
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView1: UITableView!
var arrayOfConcerts: [concert] = [concert]()
override func viewDidLoad()
{
super.viewDidLoad()
self.setUpConcerts()
self.tableView1.rowHeight = 145.0
}
func setUpConcerts()
{
var ACL = concert(imageName: "ACL2015.png")
let Landmark = concert(imageName: "Landmark.png")
let BostonCalling = concert(imageName: "BostonCalling.png")
let Lolla = concert(imageName: "Lolla.png")
arrayOfConcerts.append(ACL)
arrayOfConcerts.append(Landmark)
arrayOfConcerts.append(BostonCalling)
arrayOfConcerts.append(Lolla)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfConcerts.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = self.tableView1.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
let concerts = arrayOfConcerts[indexPath.row]
cell.setCell(concerts.imageName)
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("concertartist", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
}
}
Below is the code for the Artist View Controller (the second
viewcontroller). This tableView should be populated with specific
artists.
How would I go about doing that?
class ArtistConcerts: UIViewController, UITableViewDataSource, UITableViewDelegate {
var arrayOfArtists: [artist] = [artist]()
#IBOutlet weak var tableViewArtists: UITableView!
override func viewDidLoad()
{
super.viewDidLoad()
self.setUpArtists()
}
func setUpArtists()
{
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayOfArtists.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableViewArtists.dequeueReusableCellWithIdentifier("", forIndexPath: indexPath) as! CustomCell
let artists = arrayOfArtists[indexPath.row]
cell.setCell(artists.imageNameArtist)
return cell
}
}
You need to do this in the prepareForSegue method.
let vc = segue!.destinationViewController as! ArtistConcerts
if let rowSelected = tableView.indexPathForSelectedRow()?.row {
vc.arrayOfArtists = artistsPerforming[0]//this is an array
}
then, set whatever data you need to actually populate the tableView in that new view controller. I am not 100% sure on what you are trying to do though. Does my answer make sense?
you can send data once the cell is pressed to another viewcontroller by using didSelectRowAtIndexPath or prepareForSegue or didSelectRowAtIndexPath + prepareForSegue
let's say you are using prepareForSegue, things you will need
the segue identifier name
cast the destinationController to the controller you want to send the data
the indexPath.row where the cell was selected
then set the variable or variables or data structure that should receive data
Create a segue when you pressed CTRL + Drag from your cell into another viewcontroller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "Name_Of_Segue"
{
let destination = segue.destinationViewController as! NAME_Of_ViewController
let indexPath = mytableview.indexPathForSelectedRow!
let data = arrayOfArtists[indexPath.row]
destination.artist = data
}
mytableview is an IBOutlet from your tableview
artist is a variable that is declared in your destination controller
destination.artist = data this line of code is passing data from the specific cell then send it to your destination cell

Resources