Manipulating from grandchild view controller - Swift Xcode - ios

This is my first time using swift or Xcode.
I'm trying to make a simple transaction register app
The first view has a table, in which each row represents an account and it's balance.
When you click on a row, it opens up a second view, via segue, which contains a table of all transactions for that account. At the top of this view there is an 'Add Transaction' button, which opens up a third view, that has a form and an 'Add' button. When the 'Add' button is pressed, I use the .reloadData() on the table in the second view and the third view is dismissed.
But, the table, visually, does not have an additional row in it. Which is because after the 3rd view closes, the newly added transaction is no longer in the transactions array.
Am I doing something wrong? My attempt and images are below.
First view
import UIKit
class AccountsViewController: UIViewController {
#IBOutlet weak var newAccountNameUITextField: UITextField!
#IBOutlet weak var newAccountBalanceUITextField: UITextField!
#IBOutlet weak var addNewAccountUIButton: UIButton!
#IBOutlet weak var accountsUITableView: UITableView!
var selectedAccount: Account = Account(name: "", balance: "")
var accounts = [Account(name: "PNC", balance: "45.93")]
override func viewDidLoad() {
super.viewDidLoad()
accountsUITableView.delegate = self
accountsUITableView.dataSource = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let transactionsViewController = segue.destination as? TransactionsViewController {
transactionsViewController.modalPresentationStyle = .fullScreen
transactionsViewController.account = selectedAccount
}
}
}
extension AccountsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedAccount = accounts[indexPath.row]
performSegue(withIdentifier: "trasactionsSegue", sender: self)
}
}
extension AccountsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return accounts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "account", for: indexPath) as! AccountCell
cell.selectionStyle = .none
cell.nameUILabel?.text = accounts[indexPath.row].name
cell.balanceUILabel?.text = accounts[indexPath.row].balance
return cell
}
}
Second View
import UIKit
class TransactionsViewController: UIViewController {
#IBOutlet weak var nameUILabel: UILabel!
#IBOutlet weak var TransactionsUITableView: UITableView!
#IBOutlet weak var balanceUILabel: UILabel!
var account: Account = Account(name: "", balance: "", transactions: [])
override func viewDidLoad() {
super.viewDidLoad()
TransactionsUITableView.dataSource = self
nameUILabel.text = account.name
balanceUILabel.text = account.balance
}
//Pass data to newTransactionViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let newTransactionViewController = segue.destination as? NewTransactionViewController {
newTransactionViewController.account = account
}
}
//Dismiss this view when Accounts button is pressed
#IBAction func backToAccountsTouchUpInside(_ sender: UIButton) {
self.dismiss(animated: true, completion: {
self.presentingViewController?.dismiss(animated: true, completion: nil)
})
}
#IBAction func addTransactionTouchUpInside(_ sender: UIButton) {
performSegue(withIdentifier: "addTransactionSegue", sender: self)
}
#IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) {
DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
//At this point the newly added transaction is missing
self.TransactionsUITableView.reloadData()
}
}
}
}
extension TransactionsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return account.transactions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "transaction", for: indexPath) as! TransactionCell
cell.selectionStyle = .none
cell.descriptionUILabel.text = account.transactions[indexPath.row].description
cell.amountUILabel.text = account.transactions[indexPath.row].amount
cell.balanceUILabel.text = account.transactions[indexPath.row].balanceAfterAmount
return cell
}
}
Third View
import UIKit
class NewTransactionViewController: UIViewController {
#IBOutlet weak var clearedUISegmentedControl: UISegmentedControl!
#IBOutlet weak var depositingUISegmentedControl: UISegmentedControl!
#IBOutlet weak var descriptionUITextField: UITextField!
#IBOutlet weak var amountUITextField: UITextField!
#IBOutlet weak var addTransactionUIButton: UIButton!
var account: Account? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addTransactionTouchUpInside(_ sender: UIButton) {
let depositing = depositingUISegmentedControl.selectedSegmentIndex == 0 ? true : false
let cleared = clearedUISegmentedControl.selectedSegmentIndex == 0 ? true : false
let description = descriptionUITextField.text
let amount = amountUITextField.text
let balanceAfterAmount = operationOnCurrency(depositing: depositing, amount: amount!, balance: account!.balance)
let newTransaction = Transaction(depositing: depositing, amount: amount!, balanceAfterAmount: balanceAfterAmount, description: description!, cleared: cleared)
account?.transactions.append(newTransaction)
self.performSegue(withIdentifier: "backToTransactions", sender: self)
}
}
func operationOnCurrency (depositing: Bool, amount: String, balance: String) -> String {
//Return empty string for now
return ""
}

The problem is that you are appending a new Transaction in the Account instance that was created in your NewTransactionViewController, rather than updating the data in the instance held by the TransactionsViewController or the root data source in the AccountsViewController (assuming that is the root data source). You need to pass the updated data backwards when the add button is pressed. You can create a delegate protocol to take care of this. Using your transition from NewTransactionViewController to TransactionsViewController example, first create the protocol:
protocol NewTransactionDelegate {
func transactionAddedToAccount(account: Account)
}
Then inside of your NewTransactionViewController you will want to create a delegate property:
class NewTransactionViewController: UIViewController {
#IBOutlet weak var clearedUISegmentedControl: UISegmentedControl!
#IBOutlet weak var depositingUISegmentedControl: UISegmentedControl!
#IBOutlet weak var descriptionUITextField: UITextField!
#IBOutlet weak var amountUITextField: UITextField!
#IBOutlet weak var addTransactionUIButton: UIButton!
var account: Account? = nil
**var delegate: NewTransactionDelegate?**
override func viewDidLoad() {
super.viewDidLoad()
}
And inside of your addTransactionTouchUpInside method call the delegate method:
#IBAction func addTransactionTouchUpInside(_ sender: UIButton) {
let depositing = depositingUISegmentedControl.selectedSegmentIndex == 0 ? true : false
let cleared = clearedUISegmentedControl.selectedSegmentIndex == 0 ? true : false
let description = descriptionUITextField.text
let amount = amountUITextField.text
let balanceAfterAmount = operationOnCurrency(depositing: depositing, amount: amount!, balance: account!.balance)
let newTransaction = Transaction(depositing: depositing, amount: amount!, balanceAfterAmount: balanceAfterAmount, description: description!, cleared: cleared)
account?.transactions.append(newTransaction)
**delegate?.transactionAddedToAccount(account: account)**
self.performSegue(withIdentifier: "backToTransactions", sender: self)
}
Now back in your TransactionsViewController you will want to conform to the NewTransactionDelegate protocol and implement the required method declared in the protocol:
class TransactionsViewController: UIViewController, NewTransactionDelegate {
func transactionAddedToAccount(account: Account) {
self.account = account
tableView.reloadData()
}
Then when you perform the segue to transition from TransactionsViewController to NewTransactionViewController you will want to set the destination view controller's delegate property to self:
//Pass data to newTransactionViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let newTransactionViewController = segue.destination as? NewTransactionViewController {
**newTransactionViewController.delegate = self**
newTransactionViewController.account = account
}
}
Now when the add button is tapped the delegate method is called and passed the new instance of account, which is then passed back to the previous view controller and updated.
Note that this will only update in account instance in the TransactionsViewController and you will also need to update the data for this account at the source or it will be lost when TransactionsViewController is deallocated. Pass the new account back to AccountsViewController, save to device, update database, etc.

Related

How to pass the data from view controller to the table view controller?

How to pass data from view controller to table view controller? and also how to store the selected data to the table view controller? but The output shows multiple row, how to make it based on the user click at the bag? and how to pass the data inside it?
! ]2
Here my Item Detail View Controller
import UIKit
class ItemDetailViewController: UIViewController {
var items = [item]()
var name : String = ""
var price : String = ""
var imagee : String = ""
#IBOutlet weak var labelname: UILabel!
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var labelprice: UILabel!
//here the button to add to the table view
#IBAction func addtobag(_ sender: Any) {
let viewController = storyboard?.instantiateViewController(withIdentifier: "BagViewController") as? BagViewController
viewController?.name = self.name
viewController?.imagee = self.imagee
viewController?.price = self.price
viewController?.items = self.items
navigationController?.pushViewController(viewController!, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
labelname.text = name
labelprice.text = price
image.image = UIImage(named: imagee)
}
}
And here my Bag View Controller
import UIKit
class BagViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var totalprice: UILabel!
#IBOutlet weak var tableview: UITableView!
var items = [item]()
var name : String = ""
var price : String = ""
var imagee : String = ""
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
// Do any additional setup after loading the view.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return name.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath) as! ShoppingTableViewCell
return cell
}
}
and here my Shopping Table View
import UIKit
class ShoppingTableViewCell: UITableViewCell {
#IBOutlet weak var dfs: UIImageView!
#IBOutlet weak var labelname: UILabel!
#IBOutlet weak var labelprice: UILabel!
#IBOutlet weak var stepperlabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func stepper(_ sender: UIStepper) {
stepperlabel.text = String(Int(sender.value))
}
}
I think your logic is kind of bad, you're instantiating a VC in code but you have a segue, I recommend you pass data through the prepare function:
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
if let vc = segue.destination as? BagViewController {
vc.name = self.name
vc.imagee = self.imagee
vc.price = self.price
vc.items = self.items
}
// Pass the selected object to the new view controller.
}
And in your addtobag IBAction you just will call the segue, I recommend you to use a String based segue extension String+PerformSegue.swift it lets you easily perform segue in a given ViewController like this:
#IBAction func addtobag(_ sender: Any) {
"nameOfTheSegue".performSegue(on: self)
// If you don't want to use String+PerformSegue.swift uncomment
// the next line and comment the last one.
// self.performSegue(withIdentifier: "nameOfTheSegue", sender: nil)
}

Why i only see the last element of array in a table view in swift 5?

I have a UI as given below and when i click save button in UI i want to add three values on top of the view to a table view, in which has three different labels for representing them and a custom structure to define the model. But my problem is that i can only append one element but what i want is to keep previously added elements in that array and show them in a tableView.
Here is the UI image
Here is the code:
MainViewController.swift
class MainViewController: UIViewController {
#IBOutlet weak var minDbLabel: UILabel!
#IBOutlet weak var averageDbLabel: UILabel!
#IBOutlet weak var maximumDbLabel: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "saveRecord" {
let recordVC = segue.destination as! RecordTableViewController
recordVC.record.minimumValue = (minDbLabel.text! as NSString).floatValue
recordVC.record.averageValue = (averageDbLabel.text! as NSString).floatValue
recordVC.record.maximumValue = (maximumDbLabel.text! as NSString).floatValue
recordVC.recordsArray.append(recordVC.record)
}
}
#IBAction func save(_ sender: UIButton){
self.performSegue(withIdentifier: "saveRecord", sender: nil)
}
}
RecordTableViewController.swift:
class RecordCell: UITableViewCell {
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var minimumValueLabel: UILabel!
#IBOutlet weak var averageValueLabel: UILabel!
#IBOutlet weak var maximumValueLabel: UILabel!
}
class RecordTableViewController: UITableViewController {
let cellIdentifier: String = "cellID"
var recordsArray = [Record]()
var record: Record = Record()
override var shouldAutorotate: Bool {
return false
}
override func viewDidLoad() {
super.viewDidLoad()
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeRight(_:)))
swipe.direction = .right
self.view.addGestureRecognizer(swipe)
tableView.insertRows(at: [IndexPath(row: recordsArray.count - 1, section: 0)], with: .automatic)
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recordsArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RecordCell
cell.minimumValueLabel.text = "\(recordsArray[indexPath.row].minimumValue)"
cell.averageValueLabel.text = "\(recordsArray[indexPath.row].averageValue)"
cell.maximumValueLabel.text = "\(recordsArray[indexPath.row].maximumValue)"
return cell
}
}
Record.swift
struct Record {
var minimumValue: Float = .nan
var averageValue: Float = .nan
var maximumValue: Float = .nan
}
Thanks in advance.
Note: I already have searched on Google to find an answer but to no avail.
you should append the data in recordsArray in MainViewController first before performing the segue. See the code below
class MainViewController: UIViewController {
#IBOutlet weak var minDbLabel: UILabel!
#IBOutlet weak var averageDbLabel: UILabel!
#IBOutlet weak var maximumDbLabel: UILabel!
var recordsArray = [Record]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "saveRecord" {
let recordVC = segue.destination as! RecordTableViewController
var record = Record()
record.minimumValue = Float(minDbLabel.text!) ?? 0.0
record.averageValue = Float(averageDbLabel.text!) ?? 0.0
record.maximumValue = Float(maximumDbLabel.text!) ?? 0.0
self.recordsArray.append(record)
recordVC.recordsArray = self.recordsArray
}
}
#IBAction func save(_ sender: UIButton){
self.performSegue(withIdentifier: "saveRecord", sender: nil)
}
}
Just replace your MainViewController with the code above and it should work.

iOS Table View click event not working

When i click table view cell click event not working. I am assigned the data to view in custom tableView cell class and passed the value from viewController class. Is there is any problem in assigning data to views in custom table view cell class
ViewController.class
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.ViewAllTableView.dequeueReusableCell(withIdentifier: "ViewAllTableViewCell", for: indexPath) as! ViewAllTableViewCell
let products = self.allProducts[indexPath.row]
cell.setData(products: products)
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "allDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ProductDetailViewController{
let products = self.allProducts[(ViewAllTableView.indexPathForSelectedRow?.row)!]
destination.productID = products.id
}
}
This is Custom Table View Cell class
ViewAllTableViewCell.class
class ViewAllTableViewCell: UITableViewCell {
#IBOutlet weak var ItemImage: UIImageView!
#IBOutlet weak var ItemName: UILabel!
#IBOutlet weak var ItemOfferPrice: UILabel!
#IBOutlet weak var ItemOriginalPrice: UILabel!
#IBOutlet weak var ItemWeight: UILabel!
#IBOutlet weak var ItemCountLabel: UILabel!
var delegate : ViewAllTableViewCellDelegate?
var allProduct: ViewAllProductsData!
func setData(products: ViewAllProductsData){
self.allProduct = products
self.ItemName.text = allProduct.name
self.ItemWeight.text = "\(allProduct.quantity) \(allProduct.unit)"
self.ItemOfferPrice.text = "\(allProduct.price)"
self.ItemOriginalPrice.text = "\(allProduct.originalPrice)"
self.ItemCountLabel.text = "\(allProduct.count)"
let url: URL = NSURL(string: allProduct.image)! as URL
self.ItemImage.af_setImage(withURL: url)
}
#IBAction func ViewAllMinusButton(_ sender: UIButton) {
delegate?.minusCount(data: allProduct)
}
#IBAction func ViewAllPlusbutton(_ sender: UIButton) {
delegate?.addCount(data: allProduct)
}
}
protocol ViewAllTableViewCellDelegate{
func addCount(data: ViewAllProductsData)
func minusCount(data: ViewAllProductsData)
}
It happens because either you haven't conform properly to UITableViewDelegate or you have UITapGestureRecognizer somewhere in your view controller.
It worked for me after changing tableView attribute selection from no selection to single selection

pass data in tableViewCell to another VC failed

I know it's kinda frequently ask question, but I did some research and none of the solutions work.
so here's my controller with table view
class MainPage: UIViewController, UITableViewDelegate, UITableViewDataSource, YoubikeManagerDelegate {
#IBOutlet weak var tableView: UITableView!
let mainPageCell = MainPageCell()
let mapPage = MapViewController()
var stations: [Station] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "infoCell") as! MainPageCell
cell.stationNameLabel.text = stations[indexPath.row].name
cell.stationLocationLabel.text = stations[indexPath.row].address
cell.numberOfRemainingBikeLabel.text = stations[indexPath.row].numberOfRemainingBikes
cell.printer = stations[indexPath.row].name
cell.mapBtn.tag = indexPath.row
cell.mapBtn.addTarget(self, action: #selector(moveToMapPage), for: .touchUpInside)
return cell
}
func moveToMapPage(sender: UIButton) {
self.performSegue(withIdentifier: "toMap", sender: self)
let nameToPass = stations[sender.tag].name
mapPage.stationName = nameToPass
}
}
there is a UIButton in my tableView cell
class MainPageCell: UITableViewCell {
var printer: String!
let mapPage = MapViewController()
#IBOutlet weak var stationNameLabel: UILabel!
#IBOutlet weak var mapBtn: UIButton!
#IBOutlet weak var stationLocationLabel: UILabel!
#IBOutlet weak var numberOfRemainingBikeLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
mapBtn.addTarget(self, action: #selector(mapBtnTapped), for: .touchUpInside)
}
func mapBtnTapped (sender: UIButton) {
print (printer)
}
}
and this is my other vc
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var stationName: String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.title = stationName
}
}
I will elaborate my problem that i am now facing here !
the thing I want to do is when I tap the button in tableView cell, I want to go to MapViewController and make the title of this vc "the station name" in the same cell.
so in VC with tableView, in cellforRowAt function I called addTarget.
with moveToMapPage function
but when I tapped the button and goes to MapView VC, the stationName is still nil
I have no clue what goes wrong,
any hints are appreciated
mapPage is not the instance that you are navigating to, so you're setting a variable on an unrelated controller.
You need to use prepare(for segue... if you want to get a link to the new controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
// if you have multiple segues, you can set up different actions for each
if segue.identifier == "segueToMap"
{
let mapPage : MapViewController = segue.destination as! MapViewController
let mapPage : MapViewController = segue.destination as! MapViewController
mapPage.stationName = nameToPass
mapPage.delegate = self // if required
}
}
use navigation controller
let vc = self.storyboard?.instantiateViewController(withIdentifier:"toMap") as! toMapViewController
vc.stationNam = stations[sender.tag].name
self.navigationController?.pushViewController(vc, animated: true)

Button on tableview cell is not working - swift

I have a button on my custom tableview cell that is showing the users name. When you click on it, you should be taken to his/her profile but nothing happens?
Here is my custom tableview cell class (commentsTableViewCell.swift) :
import UIKit
protocol commentsTableViewCellDelegate {
func namesIsTapped(cell: commentsTableViewCell)
}
class commentsTableViewCell: UITableViewCell {
#IBOutlet weak var nameButton: UIButton!
#IBOutlet weak var commentLabel: UILabel!
#IBOutlet weak var profilePic: UIImageView!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var uidLabel: UILabel!
var delegate: commentsTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func nameIsTapped(sender: AnyObject) {
//4. call delegate method
//check delegate is not nil
if let _ = delegate {
delegate?.namesIsTapped(self)
} else {
print("Delegate is \(delegate)")
}
}
}
Here is the important part of commentsTableViewController.swift:
class commentsTableViewController: UITableViewController, commentsTableViewCellDelegate, UITextViewDelegate {
#IBOutlet weak var commentLabel: UITextView!
#IBOutlet weak var theLabel: UILabel!
var dataGotten = "Nothing"
var updates = [CommentSweet]()
func namesIsTapped(cell: commentsTableViewCell) {
//Get the indexpath of cell where button was tapped
//let indexPath = self.tableView.indexPathForCell(cell)
let allDataSend = cell.nameButton.titleLabel?.text!
self.performSegueWithIdentifier("toDetailtableViewController", sender: allDataSend)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toDetailtableViewController" {
if let nextVC = segue.destinationViewController as? theProfileTableViewController {
nextVC.viaSegue = sender! as! String
}
}
}
override func viewDidLoad() {
Kindly set the delegate to self.
In cellforRowIndexPath
commentsTableViewCell.delegate = self
Where commentsTableViewCell is the custom UITableViewCell object
I suggest to handle the action of the cell's button in the viewController. You can recognize which button has been tapped by setting a tag for it.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! TableViewCell
cell.myButton?.tag = indexPath.row
cell.myButton?.addTarget(self, action: #selector(), for: .touchUpInside)
return cell
}
func namesIsTapped(tappedButton: UIButton) {
// get the user (from users array for example) by using the tag, for example:
let currentUser = users[tappedButton.tag]
// do whatever you want with this user now...
}

Resources