Any idea as to why my data isn't being passed to the new tableview? - swift

I am passing data from one tableview to another. I want the category data that the tableviewA contains to be passed to tableviewB. When I perform the segue, the print data that I have for TableviewB is empty.
This is tableviewA
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let category = listOfCategories[indexPath.row].strCategory
let vc = MealsByCategoryVC()
vc.mealCategory = category
print(category) // Properly returns the category
performSegue(withIdentifier: "searchMeals", sender: nil)
}
This is tableview2
class MealsByCategoryVC: UITableViewController {
var mealCategory : String = ""
var listOfMeals : [Meals] = []
override func viewDidLoad() {
super.viewDidLoad()
print("Meal category is \(mealCategory)") //This statement returns "Meal category is "
}

This:
let vc = MealsByCategoryVC()
vc.mealCategory = category
performSegue(withIdentifier: "searchMeals", sender: nil)
...is not how you pass a value into a view controller that you are creating by calling performSegue. The first two lines of that code do nothing at all! The view controller created by the segue is different from the one you are creating by saying MealsByCategoryVC(); in fact, the latter is just thrown away, uselessly. You are setting the mealCategory of the wrong view controller instance.
Instead, implement prepare(for:sender:). That's what it's for. You receive the segue and its destination view controller. That is the view controller whose mealCategory you need to set.

Related

Retain The State Of On Off Button In A UITableViewCell - Swift

I have been searching through many posts on SO but couldn't find an answer to this one.
I have a Table view listing various items. Each cell in the table view has a button that swaps an image around when clicked on, effectively working as an "on" or "off" button to show a user which items in the list they have selected. I have a variable inside my custom Cell Prototype class which stores a value of true or false which is updated every time the button is clicked on.
There is a "Done" button in the Table View that when tapped on calls an unwind Segue to go back to the first View Controller.
When the user taps on the Enter Table View button (on the first View Controller) to display the Table the buttons all go back to their default state, am guessing because each time the segue to the Table View Screen happens it creates a new instance of the Table to be displayed.
What I'm trying to achieve is that the state of the button (either on or off) is retained when going back into the Table screen. I've tried for a while sending an Integer value back from the TableCell Class (using a delegate) to the first View controller and then passing that value back into the Table View controller when the forward segue is called in order to have a "retained from the previous state value" that can be compared against when the cells are created to indicate if a button had been clicked or not. Couldn't get it to work though to save the state of the buttons. The list of Items in the Table will also change depending on what a user adds.
Some Screen shots and the code are below. The code hasn't got the delegate i was trying included (as it didn't work) but if its needed I will edit the post to include it. Any help would be hugely appreciated. Many Thanks!!
Code I have so far is:
//Main View Controller//
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
//segue to the Table View Screen
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EnterTableView" {
let newTableView = segue.destination as! TableViewController
}
}
// Unwind Segue Called on Exit From Table View
#IBAction func unwindToMainViewController (_segue:UIStoryboardSegue) {
}
}
// Table View Controller //
class TableViewController: UITableViewController {
var dataArray = ["A", "B", "C", "D"]
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
//create one section for table
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//create number of rows based on the number of items in the dataArray set above
return dataArray.count
}
// an array that will contain all the cells
var cellArray = [UITableViewCell] ()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create a new cell based on the cell class "TableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell
// variable to contain a single element from the dataArray - updates every time a new cell is created
let dataArrayForCells = dataArray [indexPath.row]
//set cell label text to show the value from the dataArrayForCells
cell!.label.text = dataArrayForCells
cellArray.append(cell!)
return cell!
}
// Table Cell Class//
class TableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel! // label to hold display some text
#IBOutlet weak var buttonImage: UIImageView! //image for button
var buttonClicked = true //variable to contain weather the has been clicked
//if button is tapped on run the below
#IBAction func aButton(_ sender: Any) {
//if button is clicked is true, swap image to red ("on") button, set buttonClicked value to false
if buttonClicked {
buttonImage.image = #imageLiteral(resourceName: "Rec Button Red")
buttonClicked = false
}
// if buttonClicked value is false swap image to grey ("off") button set buttonClicked value back to true
else {
buttonImage.image = #imageLiteral(resourceName: "Rec Button Grey")
buttonClicked = true
}
}

How to pass an image as a variable to UIImageView in separate View Controller?

I'm pulling in some JSON data and displaying it in a UITableView (iOS/iPhone 8). It displays the image, title, and description of each value. I've successfully got that down, however, are having trouble pulling the image into a separate View Controller.
By that, I mean, when a cell on the first View Controller is tapped, another view controller opens to display just the information from that cell.
I've been able to make the title and description accessible via a global variable and an indexPath. But the same won't apply to an image, due to a conflict with strings.
I've listed below what I have successfully done with the title and description strings and then show my proposition (which doesn't work of course).
How can I get an image that has already been loaded and is in an array, to be accessible like I already have with the title and description, for use in another View Controller?
The code that formats and gathers values from the JSON:
if let jsonData = myJson as? [String : Any] { // Dictionary
if let myResults = jsonData["articles"] as? [[String : Any]] {
// dump(myResults)
for value in myResults {
if let myTitle = value["title"] as? String {
// print(myTitle)
myNews.displayTitle = myTitle
}
if let myDesc = value["description"] as? String {
myNews.displayDesc = myDesc
}
if let mySrc = value["urlToImage"] as? String {
// print(mySrc)
myNews.src = mySrc
}
self.myTableViewDataSource.append(myNews)
// dump(self.myTableViewDataSource)
// GCD
DispatchQueue.main.async {
self.myTableView.reloadData()
}
}
}
}
Two variables I have outside of the class, in order to use them globally:
var petsIndex = ""
var petsDesc = ""
The code that works with the UITableView and its cells:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath)
let myImageView = myCell.viewWithTag(2) as! UIImageView
let myTitleLabel = myCell.viewWithTag(1) as! UILabel
myTitleLabel.text = myTableViewDataSource[indexPath.row].displayTitle
let myURL = myTableViewDataSource[indexPath.row].src
loadImage(url: myURL, to: myImageView)
return myCell
}
The code that I'm using to send the JSON values to another View Controller. I achieve this by utilizing those global variables:
// If a cell is selected, view transitions into a different view controller.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
petsIndex = self.myTableViewDataSource[indexPath.row].displayTitle
petsDesc = self.myTableViewDataSource[indexPath.row].displayDesc
// Img needs to go here
myIndex = indexPath.row
performSegue(withIdentifier: "segue", sender: self)
}
Here's what I was doing to maybe solve my problem. I converted the string to a UIImage? or a UIImageView? and passed it as data. I left the variable empty and would change it as the data came available. That would occur, when the cell was clicked. Then inside of the second View Controller, I would utilize a an IBOutlet for the UIImageView:
// Variable outside of the class
var petsImg: UIImageView? = nil
petsImg = self.myTableViewDataSource[indexPath.row].src
I'm stumped at this point. I have gotten errors about the image being a string and needed to be converted. And when it was converted, the variable always came back is empty or nil.
Update:
I just tried doing this. It works and doesn't throw any errors. However, I still get a value of nil
petsImg = UIImage(named: self.myTableViewDataSource[indexPath.row].src)
When you perform a segue, you can intercept the call so to prepare the view controller it is showing. The view of this controller at this point has not loaded yet, and so you will need to create properties inside your PostViewController; you could create properties for the title, description, and image.
However, it will be a lot easier passing this information around as your NewsInfo object, for example:
struct NewsInfo {
let displayTitle: String
let displayDesc: String
let src: String
var imageURL: URL { return URL(string: src)! }
}
As well as a custom cell class that takes a NewsInfo object as an argument to populate the outlets.
class NewsInfoTableViewCell: UITableViewCell {
var newsInfo: NewsInfo? {
didSet {
updateOutlets()
}
}
override func prepareForReuse() {
super.prepareForReuse()
newsInfo = nil
}
private func updateOutlets() {
textLabel?.text = newsInfo?.displayTitle
detailTextLabel?.text = newsInfo?.displayDesc
// loadImage()
}
}
Setup the custom table cell class and set the newsInfo property after dequeuing.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath) as! NewsInfoTableViewCell
cell.newsInfo = myTableViewDataSource[indexPath.row]
return cell
}
When the cell is selected, you can pass it as the sender for performing the segue rather than setting the global variables to populate the PostViewController on viewDidLoad.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? NewsInfoTableViewCell {
performSegue(withIdentifier: "segue", sender: cell)
}
}
You can remove this whole method if you replace your existing segue by ctrl-dragging from the prototype cell to the PostViewControllerin IB to make the cell the sender of the segue.
We want this because we will intercept the segue to prepare the destination view controller by passing it the NewsInfo object of the cell that was selected and triggered the segue.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (segue.destination) {
case (let controller as PostViewController, let cell as NewsInfoTableViewCell):
controller.newsInfo = cell.newsInfo
default:
break
}
}
Similar to how we pass a NewsInfo object to the cell to populate the outlets, you can do the same thing for the PostViewController.
Quick and dirty solution
In your second view controller load the image using:
// With petsImg being the url to your image:
if let url = URL(string: petsImg), let imageData = Data(contentsOf: url) {
imagePost.image = UIImage(data: data)
}
Proper solution
You should not work with global variables to pass data from one view controller to another (read why). Rather look at this question's answer to find out how to transfer data (in your case: the myNews object) from the table view to a detail view:
Send data from TableView to DetailView Swift
If this is too abstract, you can look at this tutorial. It covers what you want to do: https://www.raywenderlich.com/265-uisplitviewcontroller-tutorial-getting-started
It looks like your image is not an image but a url to an image. You can load images into image views using a library like nuke: https://github.com/kean/Nuke
Since network requests are called asynchronously, its a bit difficult to see at which point you're trying to configure your UIImageView. But once you have your network response you will do one of the two:
If your view controller is not yet loaded, (ie you load it once your network response is complete) you can configure the UIImageView in the prepare for sequel method.
If your view controller is already loaded (which is perfectly fine), you will need to set a reference to that view controller in the prepare for segue method. Then you can configure the view controller once the network request is made. I would make the reference to that VC weak, as the system (navigation stack) is already holding on to the VC strongly.
PS: I suggest you de-serialize your JSON response to an object. It will go a long way to help us understand your code. It's hard to see your issue when you're passing dictionary objects around. I suggest you use one of the following:
1. Codable protocol
2. ObjectMapper
3. MapCodableKit (this one is my library which I use personally)
PPS: I assumed you use storyboards.

Xcode 8.3.3 Swift 3 - Variable not updating between files

I'm having a issue with xcode, since I updated to 8.3.3. I usually work with the MCV (Model - View - Controller) method, and now, my variables aren't updating between them.
Situation: I Have a Model (store all major variables and calculations functions); a TableView Controller (Control Tableview) and TableViewCell (set Outlet and actions)
Goal: When a button is pressed in a cell, it should add a row in TableView.
Problem: Why isn't table view getting the new value of Model() variable.
To make it better to understand, here is a timeline of what is going on :
Run > run viewDidLoad in TableViewController > update variable test in Model() > cellForRowAt is called and prints ["1"] > show tableView with 1 row > press button > print ["1"] > add ["2"] to Model() > print ["1","2"] > post notification > viewDidLoad gets Notification and prints "reloading table" > cellForRowAt is called and prints ["1"] > tableView keeps 1 row.
Here is one example of my code:
I have my Model.Swift:
class Model {
var test : [String] = []
}
My TableViewController:
class BudgetTableViewController: UITableViewController {
let model = Model()
override func viewDidLoad() {
super.viewDidLoad()
model.test.append("1")
center.addObserver(forName: NSNotification.Name(rawValue: "reloadTableVIew"), object: appDelegate, queue: queue) {[unowned self] (_) in
print("reloading table")
self.tableView.reloadData()
}
}
To simplify.. in Sections I keep returning "1" and for rows I count the variable test in Model().
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(model.test) // **ALWAYS PRINT ["1"]**
let cell = tableView.dequeueReusableCell(withIdentifier: "buttonCell", for: indexPath) as! TableViewCell
return Cell
}
And my TableViewCell:
class TableViewCell: UITableViewCell{
let model = BudgetModel()
let notification = Notification(name: Notification.Name(rawValue: "reloadTableVIew"), object: appDelegate)
#IBAction func okButton(_ sender: UIButton) {
print(model.test)
model.test.append("2")
print(model.test)
NotificationCenter.default.post(notification)
}
}
I hope it is clear enough.
The point is, if I press the button 5 times, it will add the string 5 times to the array (that is confirmed in the print) but when cellForRowAt is called, it will print ["1"] always.
THank you for your help
The two model vars are in different classes. You have var model in your viewController, & var model in your cell. Changing one isn't going to affect the other. You'd be best to implement a delegate for your cells, where the viewController is the delegate, and the cell calls it when pressed -
protocol MyCellDelegate: class {
func cellWasPressed()
}
In the cell -
weak var delegate: MyCellDelegate?
Set this to the viewController when you create the cell.
Then in the button pressed method, add -
self.delegate?.cellWasPressed()
In the viewController, implement this -
func cellWasPressed() {
self.model.test.append("2")
self.tableView.reloadData()
}
I don't think you need to be using a notification, this is far simpler.

pass data to previous view without segue,#IBaction swift

I have a View1 when i click on textbox i am going to view2(table view) to pick a value. I want to send the picked value to view1 for that textbox.
The controls are created programmatically so i am not using segue,IBActions.
I am trying to use the protocol methods still no success. Here is what i have tried.
class DynamicSuperView: UIViewController,UITableViewDelegate,UITableViewDataSource,dropdownDelegate,UITextFieldDelegate
{
func setValue(value:AnyObject)
{
print("dynamic view delegate method executed")
self.labelText = value as! String
//return selectedValue;
}
override func viewDidLoad() {
}
}
The second class is here with delegate method..
protocol dropdownDelegate {
func setValue(value: AnyObject);
}
class testClass: UIViewController,UITableViewDataSource,UITableViewDelegate,UISearchBarDelegate {
var delegate:dropdownDelegate! = nil
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vcName = names[indexPath.row]
print ("Table view cell clicked and value passed: \(vcName)")
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
self.navigationController?.popViewController(animated: true)
delegate.setValue(value: "Testing delegate")
}
}
Once i select the value in the tableview i am calling the delegate method and trying to pass that value but no success.
I don't want to create the new instance of the previous view controller because i will lose the data already entered by the user, so i am popping the current view controller and going to the previous view controller.
Please suggest if my approach i correct or not?
ERROR:
fatal error: unexpectedly found nil while unwrapping an Optional value
Thank you in advance
Try this:
view func ButtonPressed() {
print("Button Pressed!!") // This will create the new instance of the view controller.
let vc = UIStoryboard(name:"Main", bundle:nil).instantiateViewController(withIdentifier: "Storage") as! testClass
vc.delegate = self
self.navigationController?.pushViewController(vc, animated:true)
}
You are not setting the delegate to first view controller.

Passing values between ViewControllers based on list selection in Swift

I'm trying to pass the selected index number of a listView selection from one ViewController to another but am running into an issue with the tableView didSelectRowAtIndexPath delegate runs slightly later than the prepareForSegue function.
Basically, in didSelectRowAtIndexPath, I seta variable, which is then picked up in the prepareForSegue. The issue is that prepareForSegue seems to run the second that the cell is selected and before the didSelectRowAtIndexPath function is called so my variable is not passed.
My main bits of code are:
tableView didSelectRowAtIndexPath delegate, which sets 'selectedResult'...
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
selectedResult = indexPath.item
txtNameSearch.resignFirstResponder() //get rid of keyboard when table touched.
println("saved result")
//tableView.deselectRowAtIndexPath(indexPath, animated: false)
//var tappedItem: ToDoItem = self.toDoItems.objectAtIndex(indexPath.row) as ToDoItem
//tappedItem.completed = !tappedItem.completed
//tableView.reloadData()
}
prepareForSegue function which sends the variable as 'toPass' to the other ViewController ('detailViewController'):
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!){
if (segue.identifier == "detailSeque") {
println("preparing")
var svc = segue!.destinationViewController as detailViewController
svc.toPass = selectedResult
//println(tableView.indexPathsForSelectedRows().
//svc.toPass = tableView.indexPathForSelectedRow()
}
}
Thanks in advance
If you have an outlet to your tableView in your ViewController, you can just call indexPathForSelectedRow in prepareForSegue.
If your ViewController is a subclass of UITableViewController, then you can do:
let row = self.tableView.indexPathForSelectedRow().row
println("row \(row) was selected")
If your ViewController is not a subclass of UITableViewController, set up an IBOutlet to the UITableView in your view controller:
#IBOutlet var tableView: UITableView!
and wire that up in Interface Builder and call it from prepareForSegue as show above.
Instead of triggering your segue directly from a storyboard action, why don't you try programmatically calling performSegueWithIdentifier in your didSelectRowAtIndexPath method, after selectedResult is set?