how to use delegate to pass data to from one View Controller to another? - ios

Im trying to pass data from one View controller to another using a delegate
right now im struggling to pass data from the CartVC to ModifyVC when pressing the modifyButton in the CartCell. This is modeled similar to a previous question that I asked before(see link below). Im just struggling to pass data to the ModifyVC since I keep getting an error saying Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value and closes out the simulator
the modifybtn passes cell data for the cel that is selected in the CartVC
I dont want to use didSelectRowAt to pass the cell data since im using the modifyBtn in the CartCell to pass the data using the ModifyDelegate
I know that im close to my solution to making this work. Im just getting that one error that is preventing me from passing the data to the ModifyVC
How pass data from button in TableViewCell to View Controller?
class CartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var selectedProduct: Items!
var modifyItems: Cart?
var cart: [Cart] = []
var groupedItems: [String: [Cart]] = [:]
var brands: [String] = []
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { //segue code for delegate
if let vc = segue.destination as? ModifyViewController {
vc.modifyItems = self.modifyItems
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return brands.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let brand = brands[section]
return groupedCartItems[brand]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartCell = tableView.dequeueReusableCell(withIdentifier: "CartCell") as! CartCell
let brand = brands[indexPath.section]
let itemsToDisplay = groupedItems[brand]![indexPath.row]
cartCell.configure(withCartItems: itemsToDisplay.cart)
cartCell.modifyDelegate = self
cartCell.modifyItems = self.modifyItems
return cartCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeader") as! CartHeader
let headerTitle = brands[section]
cartHeader.brandName.text = "Brand: \(headerTitle)"
return cartHeader
}
}
class ModifyViewController: UIViewController {
private var counterValue = 1
var lastSelectedWeightButton = RoundButton()
var modifyItems: Cart!
#IBOutlet weak var price1: UILabel!
#IBOutlet weak var price2: UILabel!
#IBOutlet weak var price3: UILabel!
#IBOutlet weak var weight1: UILabel!
#IBOutlet weak var weight2: UILabel!
#IBOutlet weak var weight3: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.numberStyle = .decimal
price1.text = "$\(formatter.string(for: modifyItems.cart.price1)!)" // getting the error right here that causes the simulator to close out and prevents me from viewing the modify VC
price2.text = "$\(formatter.string(for: modifyItems.cart.price2)!)"
price3.text = "$\(formatter.string(for: modifyItems.cart.price3)!)"
weight1.text = modifyItems.cart.weight1
weight2.text = modifyItems.cart.weight2
weight3.text = modifyItems.cart.weight3
}
}
side note: The CartVC cells data is populated from the HomeVc when an item is selected it posted as a cell in the CartVC. the Items class populates the cells in the HomeVC.

Update following line in cellForRowAt function:
cartCell.modifyItems = self.modifyItems
to this:
cartCell.modifyItems = itemsToDisplay

Related

How would I be able to pass an image when data is passed from one View Controller to another?

How would I be able to show an image from HomeViewController to the CartViewController
I have the code setup in my cells to where the data passes from one VC to another,
Im trying to present the image when the data is passed
How would I be able to show the image when data is passed from the HomeVC to the CartVC after the atcBtn is pressed
all the data in my labels passes fine its just the image data that fails to pass
I have tried a few ways from stack but I still keep getting error codes on presenting the image in the CartVC
class CartViewController: UIViewController {
var items: Items!
#IBOutlet weak var cartTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
cartTableView.dataSource = self
cartTableView.delegate = self
}
}
extension CartViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Cart.currentCart.cartItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Cart.currentCart.CartItems[indexPath.row]
cell.store.text = cart.items.store
cell.lblMealName.text = (cart.items.name)
cell.lblSubTotal.text = "$\(cart.items.cost)"
cell.imageUrl.image = cart.imageUrl // can't figure out how to get this to code to work since it is an Image to String issue
return cell
class CartCell: UITableViewCell {
#IBOutlet weak var lblMealName: UILabel!
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var lblSubTotal: UILabel!
#IBOutlet weak var lblWeight: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
The code you posted doesn't quite match up:
In cellForRowAt in CartViewController, for example, you are using CartCell but your code is setting:
cell.store.text = cart.items.store
but there is no store label / property in your posted CartCell.
However, since you are doing very similar things with HomeCell class, just take the same approach for CartCell.
Something along these lines:
class CartCell: UITableViewCell {
#IBOutlet weak var lblMealName: UILabel!
#IBOutlet weak var imageUrl: UIImageView!
#IBOutlet weak var lblSubTotal: UILabel!
#IBOutlet weak var lblWeight: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure(withItems items: Items) {
//store.text = cart.items.store
lblMealName.text = (items.name)
lblSubTotal.text = "$\(items.cost)"
imageUrl.sd_setImage(with: URL(string: items.imageUrl))
}
}
and change `cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CartCell", for: indexPath) as! CartCell
let cart = Cart.currentCart.CartItems[indexPath.row]
//cell.store.text = cart.items.store
//cell.lblMealName.text = (cart.items.name)
//cell.lblSubTotal.text = "$\(cart.items.cost)"
//cell.imageUrl.image = cart.imageUrl // can't figure out how to get this to code to work since it is an Image to String issue
cell.configure(withItem: cart)
return cell
}
This appears to be where the problem is
cell.imageUrl.image = cart.imageUrl // can't figure out how to get
this to code to work since it is an Image to String issue
and as you noted, that code doesn't really make sense... If you're storing a url (a string) in your cart object, then you can't cast that to an image with cell.imageUrl.image, right?
You would need to assign it to the url
cell.imageUrl = cart.imageUrl
Of course that will just pass the url to the cell. The cell would then need some intelligence to get that associated image from the url.
Some pseudo code for your CartCell class...
cell.store.text = cart.items.store
cell.lblMealName.text = (cart.items.name)
cell.lblSubTotal.text = "$\(cart.items.cost)"
cell.setImageUrlAndDisplayImage( cart.imageUrl )
and then the function in the CartCell class
func setImageUrlAndDisplayImage( imageUrl: URL) {
self.setImage(with: URL(string: imageUrl))
}
or of course, you could just assign the image directly to the CartCell image property if it has one.
cell.store.text = cart.items.store
cell.lblMealName.text = (cart.items.name)
cell.lblSubTotal.text = "$\(cart.items.cost)"
cell.the_image = UIImage(with: URL(string: cart.imageUrl))
The above is just pseudo code since we don't know what you Cart class or CartCell class looks like.

How to pass selected row value as a public variable available to multiple view controllers?

How to have pass the value of a selected tableView to a public variable that can be accessed by multiple ViewControllers? Currently, in didSelectRowAt, I define the row selected as portfolio doing let portfolio = structure[indexPath.row] Now how can I save this value to perhaps some sort of variable that makes it avalible to multiple view controller?
I don't just mean pushing the value to whichever view controller is being presented when the cell is pressed, I need it be available to view controller past the .pushViewController.
In the past I tried using userdefaults, but this is not appropriate for values that are constantly changing and are not permanen.
import UIKit
class ScheduledCell: UITableViewCell {
#IBOutlet weak var ETALabel: UILabel!
#IBOutlet weak var cellStructure: UIView!
#IBOutlet weak var scheduledLabel: UILabel!
#IBOutlet weak var testingCell: UILabel!
#IBOutlet weak var pickupLabel: UILabel!
#IBOutlet weak var deliveryLabel: UILabel!
#IBOutlet weak var stopLabel: UILabel!
#IBOutlet weak var topBar: UIView!
}
class ToCustomerTableViewController: UITableViewController, UIGestureRecognizerDelegate {
var typeValue = String()
var driverName = UserDefaults.standard.string(forKey: "name")!
var structure = [AlreadyScheduledStructure]()
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
//Disable delay in button tap
self.tableView.delaysContentTouches = false
tableView.tableFooterView = UIView()
}
private func fetchJSON() {
guard let url = URL(string: "https://example.com/example/example"),
let value = driverName.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "driverName=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([AlreadyScheduledStructure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return structure.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "scheduledID", for: indexPath) as! ScheduledCell
let portfolio = structure[indexPath.row]
cell.stopLabel.text = "Stop \(portfolio.stop_sequence)"
cell.testingCell.text = portfolio.customer
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let portfolio = structure[indexPath.row]
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "scheduledDelivery")
print(portfolio.customer)
controller.navigationItem.title = navTitle
navigationController?.pushViewController(controller, animated: true)
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
}
You can use a function to pass an optional Value inside an extension, try the following:
From what I understood you want to pass values from your viewController and be able to get it from any other viewController..
extension UIViewController {
func passData(row: Int?) -> Int? {
var myValue = Int()
if row != nil {
myValue = row!
}
return myValue
}
}
in this function you can Pass the value you want and also retrieve it.
to pass data into the function simply use this :
passData(row: indexPath.row)
and if you want to retrieve the value of it from another viewController use this:
let myValue = passData(row: nil)
this way you could get the Data you pass from another viewController..
if that didn't work for you I'd suggest you use UserDefaults ..
I hope this could solve your problem.
You can use NSNotificationCenter and post value after selection and every subscribed controller will received a new value. For more info read this NSNotificationCenter addObserver in Swift

Allowing the user to create a tableViewCell with text from another viewController?

I'm creating an app, in which one of the functions is, that the user should be able to write a person's name and an answer to a question - and then when pressing the save-button he/she should be redirected to the previous controller again, which not have created a tableViewCell with this data as title. (Later on you can ofcourse click this cell and see the data in third viewcontroller.)
My way of tackling this was to let the "save" button save the name and the answer by using NSUserDefault. Then connecting a segue to the button at the same time to make it redirect the user to the previous controller - and finally to have the tableView in the previous controller refer to the newly created NSUserDefault-key in the cell.textfield.
I have two questions.
Why does this not work? My code from both viewControllers are underneeth. I don't get why it doesn't work.
If I do get this to work: How do I implement the effect, that every time you enter the "Creating viewController", in which you can write the name and the answer - the user gets the option of saving a NEW person and adding a NEW cell, instead of overriding the old one, which I'm afraid will happen if I get the current approach to work...
Code in the "Creating viewController", where you can write the name and the answer:
class CreateNewPerson: UIViewController {
let defaults = UserDefaults.standard
#IBOutlet weak var Question: UILabel!
#IBOutlet weak var ExtraIdentifier: UILabel!
#IBOutlet weak var PersonName: UITextField!
#IBOutlet weak var PersonAnswer: UITextField!
#IBOutlet weak var PersonExtraIdentifier: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
showDiaryIdentifiers () }
func showDiaryIdentifiers () {
let DiaryQuestion = self.defaults.string(forKey: "DiaryQuestionKey")
let ExtraIdentifer = self.defaults.string(forKey: "RandomIdentifierKey")
self.Question.text = DiaryQuestion
self.ExtraIdentifier.text = ExtraIdentifer
}
#IBAction func SavePerson () {
self.defaults.setValue(self.PersonName.text, forKey: "PersonNameKey")
self.defaults.setValue(self.PersonAnswer.text, forKey: "PersonAnswerKey")
self.defaults.setValue(self.PersonExtraIdentifier.text, forKey: "PersonExtraIdentiferKey")
} }
Code in the other viewController:
class AllPersonsInYourDiary: UIViewController, UITableViewDelegate, UITableViewDataSource {
let defaults = UserDefaults.standard
#IBOutlet weak var ShowingDiaryName: UILabel!
#IBOutlet weak var ShowingDiaryQuestion: UILabel!
#IBOutlet weak var ShowingExtraIdentifer: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
showDiaryIdentifiers()
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func showDiaryIdentifiers () {
let DiaryName = self.defaults.string(forKey: "DiaryNameKey")
let DiaryQuestion = self.defaults.string(forKey: "DiaryQuestionKey")
let ExtraIdentifer = self.defaults.string(forKey: "RandomIdentifierKey")
self.ShowingDiaryName.text = DiaryName
self.ShowingDiaryQuestion.text = DiaryQuestion
self.ShowingExtraIdentifer.text = ExtraIdentifer
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = self.defaults.string(forKey: "PersonNameKey")
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
In this code, I guess what is not working is the cellForRowAt method. What am I getting wrong? Right now it's not creating any cells at all.
Also, I know I should notr1 return 1 row and 1 section. It's just for now. I know I should in the end return Something.count - but I haven't yet figured out what this something is...
Thanks!
You already created a table view with only one row.
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
When returning to previous controller just reload tableview like(Make sure before reloading datasource have contain new data.)
tableView.reloadData()
If I understand correctly that you need the user to enter a set of values and then use these values to populate a table view in another view controller, then what you wanna do is:
1- create 2 dictionaries, an optional dictionary in AllPersonsInYourDiary that would carry the new values and one in your CreateNewPerson something like this let dic = [[String: String]]().
2- Instantiate the view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "#yourSegueIdentifier" {
let vc = segue.destination as! AllPersonsInYourDiary
vc.dic = self.dic
}
}
3- in your AllPersonsInYourDiary view controller, override the functions like this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dic.count
}
and populate the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Person1", for: indexPath)
cell.textLabel?.text = dic[indexPath.row]["#whateverKeyForValue"]
cell.textLabel?.numberOfLines = 0
cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}

Can't pass data via segue

I make app with news feed which has to open on other ViewController. But can't pass data via segue.
Viewcontroller with newsfeed
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var titlenews = ""
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "newsfeedCell", for: indexPath) as! NewsFeedCell
cell.newsfeed_title.text = self.news?[indexPath.item].headline
cell.newsfeed_topic.text = self.news?[indexPath.item].topic
cell.newsfeed_time.text = timetime(from: (self.news?[indexPath.item].time)!)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("tableview")
let vc = storyboard?.instantiateViewController(withIdentifier: "newsBody") as? NewsBody
vc?.labeltext = (self.news?[indexPath.item].headline)!
print((self.news?[indexPath.item].headline)!)
self.navigationController?.pushViewController(vc!, animated: true)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.news!.count
} //number of rows
#IBOutlet weak var tableview: UITableView!
var news: [Newsfeed]? = []
override func viewDidLoad() {
super.viewDidLoad()
getJSON()
}
func getJSON(){
///Here all do right
}
}
Viewcontroller which has to receive data from news feed
class NewsBody: UIViewController {
#IBOutlet weak var testLabel: UILabel!
var labeltext = ""
override func viewDidLoad() {
super.viewDidLoad()
print(labeltext)
testLabel.text = labeltext
}
}
print(labeltext) shows that NewsBody receive empty value or nothing.
But print((self.news?[indexPath.item].headline)!) inside of SecondViewController shows that I try to push proper value.
What I do incorrect between this actions? What wrong with segue and pass of data?
It seems that instantiateViewController(withIdentifier: "newsBody") triggers view load under the hood. It should not (in theory) but it might do just that in your case.
This means that viewDidLoad() will be called before the vc?.labeltext = (self.news?[indexPath.item].headline)! is executed.
I'd recommend you to do the following.
class NewsBody: UIViewController {
#IBOutlet weak var testLabel: UILabel!
var labeltext: String? {
didSet { updateUI() }
}
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
private func updateUI() {
testLabel.text = labeltext
}
}
This way if you set the labeltext property after the view is loaded, it will still trigger the UI update. And if you set the labeltext property before the view is loaded, as soon as viewDidLoad() is called.
BTW, you are not using segues here. But even if you do, you can easily use the same method as I proposed, because it allows you to stop thinking about whether property updates will update the UI.
Also please note that I made the property optional. It will allow you to avoid force casts and just do
vc?.labeltext = self.news?[indexPath.item].headline
UILabel.text is also an optional String property, so they will play well together.

Implementing Favourites Section Of Table View Using UserDefaults

I'm currently developing an application for IOS that has 2 tabs. A home view, which consists of a table of images with actions when you click on them, and a favourites view which allows users to view their favourite images and actions. I'm currently trying to figure out a way to save this favourites data once the application has been closed. It seems to simple for core data, yet I can't seem to get my head around UserDefaults.
I have a favourite button inside of a view that appears when the user clicks the favourite button, it adds the image title to a list and reloads the table using that list inside of the favourites tab. This is working fine with a normal array variable. When I try implementing UserDefaults, it doesn't seem to reload the table when I click on the favourites tab, however when I close the app and restart it using multitasking (swipe up) and restart the app the table forms and all the data is remembered and set up. Is there any way for the data to be remembered and stored in the UserDefaults variable when the app closes and when the app restarts use that variable, and while the app is running use the normal variable to reload the table from?
Here's my favourites tab code for the table:
import UIKit
var favRow = 0
class FavouritesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let favouritesavedimages = userDefaults.object(forKey: "Data") as? [String] ?? [String]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
tableView.reloadData()
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return favouritesavedimages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell2") as? FavouritesImageCellTableViewCell {
cell.configureCellFavourites(image: UIImage(named: favouritesavedimages[indexPath.row])!)
return cell
} else {
return FavouritesImageCellTableViewCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
favRow = indexPath.row
}
override func viewWillAppear(_ animated: Bool) {
let favouritesavedimages = userDefaults.object(forKey: "Data") as? [String] ?? [String]()
super.viewWillAppear(animated)
tableView.reloadData()
}
}
Here's my code for the page with the favourite button:
import UIKit
let userDefaults = UserDefaults.standard
var favouriteImages: [String] = []
var isFavourite = [false,false,false,false,false,false,false,false,false,false,false,false,false,f alse,false,false,false,false,false,false,false,false,false,false,false]
class SecondViewController: UIViewController {
let favourites = userDefaults.object(forKey: "FavData") as? [Bool] ?? [Bool]()
override func viewDidLoad() {
super.viewDidLoad()
if !isFavourite[row]
{
favouriteButton.image = UIImage(named: "favourite")
} else {
favouriteButton.image = UIImage(named: "defavourite")
}
secondImageView.image = secondImages[row]
secondTitle.text = tutorialTitles[row]
secondTutorialText.text = tutorialText[row]
}
#IBAction func favouriteButtonTapped(_ sender: Any) {
if !isFavourite[row] {
setOnFavourite()
} else {
setOnDeFavourite()
}
}
#IBOutlet weak var favouriteButton: UIBarButtonItem!
#IBOutlet weak var secondTutorialText: UITextView!
#IBOutlet weak var secondTitle: UILabel!
#IBOutlet weak var secondImageView: UIImageView!
func setOnFavourite()
{
isFavourite[row] = true
favouriteButton.image = UIImage(named: "defavourite")
favouriteImages.append(String(row + 1))
userDefaults.set(favouriteImages, forKey: "Data")
userDefaults.set(isFavourite, forKey: "FavData")
}
func setOnDeFavourite()
{
isFavourite[row] = false
favouriteButton.image = UIImage(named: "favourite")
favouriteImages = favouriteImages.filter{$0 != String(row)}
userDefaults.set(favouriteImages, forKey: "Data")
userDefaults.set(isFavourite, forKey: "FavData")
}
}
Thanks, any help would be appreciated.

Resources