Delegate not get called - swift

protocol WeatherManagerDelegate {
func didUpdateWeater(weather: ConsolidatedWeather)
func didFailWithError(error: Error)
}
ViewController: where i am setting value didSelectRowAt and using performSegue going to another viewController
class WeatherListViewController: UIViewController {
var delegate: WeatherManagerDelegate?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let index = weatherViewModel.didSelect(at: indexPath.row)
self.delegate?.didUpdateWeater(weather: index)
performSegue(withIdentifier: K.DetailsView.segueIndentifier, sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVc = segue.destination as! DetailsViewController
}
}
this is my ViewModel calss: from my ViewModel, I will send value to ViewController and Update UI
class DetailsWeatherViewModel{
var w = WeatherListViewController()
func a(){
print("aaa")
w.delegate = self
}
}
extension DetailsWeatherViewModel: WeatherManagerDelegate{
func didUpdateWeater(weather: ConsolidatedWeather) {
weatherData = weather
print("weatherData: \(String(describing: weatherData))")
}
func didFailWithError(error: Error) {
print(error)
}
}
what I am doing wrong...????

You should be careful of memory leaks when using delegate pattern. I think you can solve this problem by making protocol limit to class and declare property by weak var. Although WeatherListViewController disappeared, WeatherListViewController and DetailsWeatherViewModel are not likely to be deinit unless you use weak reference. Try this.
protocol WeatherManagerDelegate : class {
func didUpdateWeater(weather: ConsolidatedWeather)
func didFailWithError(error: Error)
}
weak var delegate: WeatherManagerDelegate?

If you are following MVVM architecture then you can create a viewModel object inside your viewcontroller and then use the updated values in VM directly using VM object.
Else if you want to use delegate then you need to write the protocols in viewModel and use it in VC. You shouldn't be creating Viewcontroller object inside the Viewmodel.

Related

Getting 'cannot convert value of type 'IndexPath.Type' to expected argument type 'IndexPath'

I'm trying to make a button do something in another view controller. I made a delegate, added a function with a requirement of having an indexPath so I could use it to delete items in an array, but when I make a variable indexPath equal to IndexPath.self and try feeding it into the actual IBAction, it spits out the error shown in the title. Here is my code for my first view controller, where the IBAction and protocol is defined.
import UIKit
protocol ToDoItemCellDelegate: AnyObject {
func didTapX(with indexPath: IndexPath)
}
class ToDoItemCell: UITableViewCell {
weak var delegate: ToDoItemCellDelegate?
static let identifier = "ToDoItemCell"
static func nib() -> UINib {
return UINib(nibName: "ToDoItemCell", bundle: nil)
}
#IBOutlet var xButton: UIButton!
private var indexPath = IndexPath.self
#IBAction func didTapX(_ sender: UIButton) {
delegate?.didTapX(with: indexPath)
}
override func awakeFromNib() {
super.awakeFromNib()
}
}
You have mistaken type and entity. Your delegate expects an entity and your var only provides a type. You stated in your comment that you do not need to provide any meaningfull value here so you have multiple possibilities:
Remove the argument from the delegate:
protocol ToDoItemCellDelegate: AnyObject {
func didTapX()
}
or create a default value while calling the delegate and remove the property:
#IBAction func didTapX(_ sender: UIButton) {
delegate?.didTapX(with: IndexPath(item: 0, section: 0))
}
or assign a defalut to the property and use it while calling the delegate:
private var indexPath = IndexPath(item: 0, section: 0)

My protocol is not working, what is wrong with it?

I have four view controllers and I am trying to send data between controllers via protocols although my two other protocols is working fine the last one is not working and I could not figure it out.
This is my first view controller that should send the data:
import UIKit
class ExercisesTableViewController: UITableViewController, ExerciseProtocol{
var exerciseToSend: Exercise? {
didSet{
print(exerciseToSend!) // This prints the result.
performSegue(withIdentifier: "showDetail", sender: self)
}
}
func getExercise() -> Exercise {
return exerciseToSend!
}
.
.
.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
exerciseToSend = exercises[indexPath.row]
}
.
.
.
}
This is the controller that should receieve the data
import UIKit
protocol ExerciseProtocol {
func getExercise() -> Exercise
}
class ExerciseDetailViewController: UIViewController {
var delegate:ExerciseProtocol?
override func viewDidLoad() {
print(delegate?.getExercise()) // This doesn't print the result.
super.viewDidLoad()
}
}
Add this inside ExercisesTableViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let des = segue.destination as? ExerciseDetailViewController else {return}
des.delegate = self
}

How to open View Controller in Table View with Button by using Objc in Swift?

Stackoverflow
I know how to make a button in the table view cells with website links, rate, mail, and many things. However, How could I open the view controller with the instantiateViewController in the #Objc func's statements?
For example.
Create a new Table View Cell folder called FeedBackButtonsTableViewCell
class FeedBackButtonsTableViewCell: UITableViewCell {
#IBOutlet weak var ButtonCells: UIButton!
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
}
}
Let create a new view controller folder called
class FeedbackViewController: UIViewController {
#IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Feedback"
}
}
add the extension to calling the view controller to UITableViewDataSource and UITableViewDelegate and create a obj func statements inside of the second FeedbackViewController with UITableViewDataSource and UITableViewDelegate under the cells.
extension FeedbackViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
if indexPath.row == 1 {
buttonCell = TableView.dequeueReusableCell(withIdentifier: "ButtonCells") as? FeedBackButtonsTableViewCell
buttonCell?.ButtonCells.addTarget(self,action: #selector(LearnMore),for: .touchUpInside)
buttonCell?.ButtonCells.tag = indexPath.row
return buttonCell!
}
#objc func LearnMore() {
// How could I write to open the view controller with UIButton in the Table View Cells?
}
}
Thank you for bring a kind of help! :)
Simple solution could be to use procol.
protocol CellActionDelegate{
func didButtonTapped(index: Int)
}
Now confirm the protocol in FeedbackViewController. Take index and actionDelegate properties in your UITableViewCell subclass.
class FeedBackButtonsTableViewCell: UITableViewCell{
var actionDelegate: CellActionDelegate?
var index: Int?
.....
// Take Action of UIButton here
#IBAction func more(_ sender: Any) {
if let delegate = self.actionDelegate{
delegate.didButtonTapped(index!)
}
}
}
Now in your FeedbackViewController set actionDelegate & Corresponding index in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
you can open anotherView controller from func didButtonTapped(index: Int) definition .
extension FeedbackViewController:CellActionDelegate{
func didButtonTapped(index: Int) {
let storybord = UIStoryboard(name: "Main", bundle: nil)
guard let controller = storybord.instantiateViewController(withIdentifier: "AnotherControllerIdentfier") as? AnotherViewController else{
fatalError("Could not finc another view controller")
}
self.present(controller, animated: true, completion: nil)
}
}
#objc func LearnMore() {
let viewController = FeedbackDetailsViewController()// creation of viewController object differs depends on how you fetch the UI, means either you are using storyboard or xib or directly making ui in code.
self.navigationController?.pushViewController(viewController, animated: true)
}

Delegates is not working?

here is my protocol definition.
protocol ActivityIndicatorDelegate: class {
func showIndicator()
func hideIndicator()
func barcodeError()
func categoryError()
func descError()
func reasonError()
func costError()
}
Then in my Custom cell class I create weak reference and I call delegate function
class ProductTableViewCell: UITableViewCell {
weak var indicatorDelegate: ActivityIndicatorDelegate?
#IBAction func stockUpdate(_ sender: Any) {
indicatorDelegate?.categoryError()
}
}
Then in my UITableViewController class
class ProductTableViewController:
UITableViewController,ActivityIndicatorDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let cellDelegate = ProductTableViewCell()
cellDelegate.indicatorDelegate = self
}
func categoryError() {
//self.showAlert(alertTitle: "Error!", alertMessage: "Category Should not be empty")
print("Error")
}
}
I have written all these in a single file. What I'm doing wrong here? Can some one help me to solve this. Thanks in advance.
You should not set the delegate in viewDidLoad. This will only set the delegate of the cell that you just created, instead of all the cells in the table view.
You should do this in celForRowAtIndexPath:
let cell = tableView.dequeue...
// configure the cell...
cell.indicatorDelegate = self

Calling a function in a view controller from another view controller

Here is the code with the delegate process suggested...
in main view controller...
protocol FilterDelegate: class {
func onRedFilter()
func onGreenFilter()
func onBlueFilter()
func onUnfiltered()
}
class ViewController: UIViewController, FilterDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
----
// Increase red color level on image by one.
func onRedFilter() {
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "filterSegue" {
let dest = segue.destinationViewController as! CollectionViewController
dest.filterDelegate = self
}
}
in collection view controller...
var filterDelegate: FilterDelegate?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("Cell \(indexPath.row) selected")
guard let filterDelegate = filterDelegate else {
print("Filter delegate wasn't set!")
return
}
switch indexPath.row {
case 0:
filterDelegate.onRedFilter()
case 1:
filterDelegate.onGreenFilter()
case 2:
filterDelegate.onBlueFilter()
case 3:
filterDelegate.onUnfiltered()
default:
print("No available filter.")
}
Right now...the code stops at the guard block and prints the error message. The switch block is not executed on any press of a cell.
Your theory in your second last sentence is correct - when you call storyboard.instantiateViewControllerWithIdentifier in the "child" view controller, you are actually creating an entirely new instance of your main view controller. You are not getting a reference to the existing main view controller, which is why the methods you're calling are not having any effect.
There are several ways to achieve what you're trying to do, including the delegate pattern or using closures. Here's a sketch of what it could look like using a delegate protocol:
protocol FilterDelegate: class {
func onRedFilter()
func onGreenFilter()
func onBlueFilter()
func onUnfiltered()
}
class MainViewController: UIViewController, FilterDelegate {
// implement these as required
func onRedFilter() { }
func onGreenFilter() { }
func onBlueFilter() { }
func onUnfiltered() { }
// when we segue to the child view controller, we need to give it a reference
// to the *existing* main view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dest = segue.destination as? ChildViewController {
dest.filterDelegate = self
}
}
}
class ChildViewController: UIViewController {
var filterDelegate: FilterDelegate?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
// ...
guard let filterDelegate = filterDelegate else {
print("Filter delegate wasn't set!")
return
}
switch indexPath.row {
case 0:
filterDelegate.onRedFilter()
case 1:
filterDelegate.onGreenFilter()
case 2:
filterDelegate.onBlueFilter()
case 3:
filterDelegate.onUnfiltered()
default:
print("No available filter.")
}
}
}
Another option would be to provide closures on ChildViewController for every function on MainViewController that the child needs to call, and set them in prepareForSegue. Using a delegate seems a bit cleaner though since there are a bunch of functions in this case.