pass data to previous view without segue,#IBaction swift - 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.

Related

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

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.

Xcode Cannot go back to TableViewController in navigation stack

I have controller1 -> TableViewController2 -> TableViewController3 in my storyboard. When I press a button in controller1, I want to jump to TableViewController3 and from there when I select a row, I want to go back to TableViewController2 and get some data and then go back to controller1.
In controller1 instantiate TableViewController3:
if segue.identifier == "MySegue" {
let viewController:TableViewController3 = segue.destination as! TableViewController3
viewController.addLocationToMap = true
}
In TableViewController3 instantiate TableViewController2 like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if addLocationToMap == true {
let navc:UINavigationController = self.navigationController!
let tvc:UITableViewController = TableViewController2()
let rootView = navc.viewControllers[0]
navc.setViewControllers([rootView, tvc], animated: true)
return
}
In TableViewController2 viewDidLoad, it fails in ViewDidLoad when I try to set a text field value because the textfield is nil. It cannot be because Textfield is already in the view. Looks like the TableViewController2 view never got loaded.
in TableViewController2
override func viewDidLoad() {
super.viewDidLoad()
locationPurposeTextField.text = "sometext"
}
Fails when setting text value because locationPurposeTextField is nil.
How to fix this?
EDITS:
On pressing a button In controller1:
let navc:UINavigationController = self.navigationController!
let alTvc = self.storyboard!.instantiateViewController(withIdentifier: "AddLocationID") as! UITableViewController
navc.pushViewController(alTvc, animated: false)
let cListTvc = self.storyboard!.instantiateViewController(withIdentifier: "ContactListID") as! UITableViewController
navc.pushViewController(cListTvc, animated: true)
The code takes me to TableViewController3 with storyboard ID: ContactListID as desired.
Next, in TableViewController3
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.myDelegate?.userSelectedContact(contactIdentifier: self.contactId, contactFullName: fullName, contactSelected: true)
let navc:UINavigationController = self.navigationController!
let tvc:UITableViewController = self.storyboard!.instantiateViewController(withIdentifier: "AddLocationID") as! UITableViewController
let rootView = navc.viewControllers[0]
navc.setViewControllers([rootView, tvc], animated: true)
}
Takes me to TableViewController2 with storyboard ID: AddLocationID
Based on the delegate sent TableViewController3, in ViewDidappear method of TableViewController2, I set some text in the view and call tableview.realoadData(). It does not load the view.
However if I select a button in TableViewController2 and load TableViewController3 and then comeback to TableViewController2 upon execution of the very same method didSelectRow it reloads the view in TableViewController2
How to fix it, please let me know?
There are a couple of things wrong with your approach.
If you want to go from controller1 to TableViewController3 but have your navigation stack contain controller1 -> TableViewController2 -> TableViewController3, you will have to have the button in controller1 first push TableViewController2 without animation, and then push TableViewController3 with animation. (You won't be able to have your button trigger a segue.)
Second problem: You can't create a copy of your TableViewController2 by just invoking it's initializer (TableViewController2()). When you do that it doesn't load its views from the storyboard. Instead, you should assign a storyboard identifier to it and use the UIStoryboard method instantiateViewController(withIdentifier:) to create a new instance of it.

Unexpectedly found nil when sending data with Delegates and protocols pattern

I have 2 viewcontrollers, firstVC and secondVC. I want to send data from the first one to update the secondVC's variable and UI.
I want to send tableView's indexPath.row with delegate when it is tapped in didSelectRowAt.
This is what I try, but when selecting a row the app crashes with error:
Unexpectedly found nil while implicitly unwrapping an Optional value
I tried to debug and saw that the delegate is nil, even if I put a method to make the delegate firstVC.
SecondVC:
func setupDelegate() {
let FirstVC = storyboard?.instantiateViewController(identifier: "FirstVC") as! FirstVC
FirstVC.selectionDelegate = self
}
}
extension SecondVC: setSelectionDelegate {
//this is never executed
func didChoose(index: Int) {
lbl.text = String(index)
}
}
FirstVC:
protocol setSelectionDelegate {
func didChoose(index: Int)
}
var selectionDelegate: setSelectionDelegate!
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let secondVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "secondVC") as? secondVC
secondVC?.setupDelegate()
self.selectionDelegate.didChoose(index: indexPath.row)
}
Each time you call instantiateViewController explicitly, you get a new instance of the view controller and this is NOT the same instance that you are seeing in your device/simulator.
With storyboard segues ( embed or present modally... ) you have override prepare(...) function to get a reference to desired view controller instance.

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.

Transition to ViewController Tapping a TableViewCell

How would one go about making a transition to a view controller by tapping on a table view cell? My current structure is:
I use a navigation controller as my only way of navigating through my app atm.
I use view models of type struct to represent individual cells. These view models conform to a custom protocol, which consists of a method I call cellSelected.
My question is then: How can I transition to a view controller by tapping on a cell using my cellSelected function? Is this possible?
Custom protocol:
protocol CellRepresentable
{
var reuseIdentier: String { get }
func registerCell(tableView: UITableView)
func cellInstance(of tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell
func cellSelected()
}
Example of a view model:
struct ProfileNameViewModel
{
}
extension ProfileNameViewModel: TextPresentable
{
var text: String
{
return "Name"
}
}
extension ProfileNameViewModel: CellRepresentable
{
var reuseIdentier: String
{
get
{
return ProfileTableViewCell<ProfileNameViewModel>.reuseIdentifier
}
}
func registerCell(tableView: UITableView)
{
tableView.register(ProfileTableViewCell<ProfileNameViewModel>.self, forCellReuseIdentifier: ProfileTableViewCell<ProfileNameViewModel>.reuseIdentifier)
}
func cellInstance(of tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentier, for: indexPath) as! ProfileTableViewCell<ProfileNameViewModel>
cell.configure(withDelegate: self)
return cell
}
func cellSelected()
{
// ??
}
}
I guess you have a couple of options, you can either pass a reference of your navigation controller to the view model, or you can define a closure that gets executed on cellSelected.
Option 1:
struct ProfileNameViewModel {
private let navigationController: UINavigationController
init(withNavigationController navigationController: UINavigationController) {
self.navigationController = navigationController
}
func cellSelected() {
navigationController.pushViewController(...)
}
}
Option 2:
struct ProfileNameViewModel {
private let selectedAction: () -> Void
init(withSelectedAction selectedAction: #escaping () -> Void) {
self.selectedAction = selectedAction
}
func cellSelected() {
selectedAction()
}
}
and on your view controller:
let vm = ProfileNameViewModel(withSelectedAction: { [weak self] in
self?.navigationContoller?.pushViewController(...)
})
1- Go to Storyboard click on your viewController and in the Utility Area which is on the left side of xcode, and give it a storyboard identifier.
2- go back to you code and execute this in cellSelected()
let say your viewController is called MusicListVC
func cellSelected() {
if let viewController = storyboard?.instantiateViewController(withIdentifier: "your Identifier") as? MusicListVC
{
navigationController?.pushViewController(viewController , animated: true)
}
}