Why delegate method is not called? - swift

I am trying to notify ChatViewController that a chat was deleted in MessagesViewController using a protocol, but the delegate method implemented in ChatViewController is never called.
In the navigationController hierarchy ChatViewController is on top of MessagesViewController.
protocol MessagesViewControllerDelegate:class {
func chatWasDeletedFromDatabase(chatUID: String)
}
class MessagesViewController: UITableViewController {
weak var delegate: MessagesViewControllerDelegate?
func observeChatRemoved() {
print("it is gonna be called")
//inform ChatViewController that a chat was deleted.
self.delegate?.chatWasDeletedFromDatabase(chatUID: chat.chatUID)
print("was called here") //prints as expected
}
}
class ChatViewController: JSQMessagesViewController {
var messagesVC: MessagesViewController?
override func viewDidLoad() {
super.viewDidLoad()
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
}
}
extension ChatViewController: MessagesViewControllerDelegate {
func chatWasDeletedFromDatabase(chatUID: String) {
print("chatWasDeletedFromDatabase called") //never prints out
if self.chatSelected.chatUID == chatUID {
//popToRootViewController
}
}

It seems
weak var delegate: MessagesViewControllerDelegate?
is nil you have to set it to the ChatViewController presented instance what ever how you present it
let chat = ///
self.delegate = chat
self.navigationController?.pushViewController(chat,animated:true)
Also do
chat.messagesVC = self
as this
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
isn't the currently presented messagesVC , so comment the above 2 lines

Related

How to listen for data change with #Published variable then reload tableView

The most difficult task I face is to know the correct terminology to search for. I'm used to SwiftUI for an easy way to build an app in the fastest time possible. With this project I have to use UIKit and for this specific task.
Inside a view controller I created a tableView:
private let tableView: UITableView = {
let table = UITableView()
table.register(ProfileCell.self, forCellReuseIdentifier: ProfileCell.identifier)
return table
}()
Later I reload the data inside viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
try await viewModel.getProfiles()
// Here I reload the table when data comes in
self.tableView.reloadData()
} catch {
print(error)
}
}
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
So what is viewModel? In SwiftUI I'm used to having this inside a view struct:
#ObservedObject var viewModel = ProfilesViewModel()
..and that's what I have inside my view controller. I've searched for:
observedobject in uitableview
uitableview reload data on data change
..and more but noting useful for me to "pick up the pieces" with.
In same controller, I'm showMyViewControllerInACustomizedSheet which now uses UIHostingController:
private func showMyViewControllerInACustomizedSheet() {
// A SwiftUI view along with viewModel being passed in
let view = ProfilesMenu(viewModel: viewModel)
let viewControllerToPresent = UIHostingController(rootView: view)
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(viewControllerToPresent, animated: true, completion: nil)
}
For the ProfilesViewModel:
class ProfilesViewModel: ObservableObject {
// ProfilesResponse is omitted
#Published var profiles = [ProfilesResponse]()
public func getProfiles(endpoint: String? = nil) async throws -> Void {
// After getting the data, I set the profiles variable
self.profiles = [..]
}
}
Whenever I call try await viewModel.getProfiles(endpoint: "..."), from ProfileMenu, I'd like to reload the tableView. What additional setup is required?
In the comments, Vadian mentioned "Combine" where I did a Google search and found this. What works, for a basic demonstaration:
[..]
import Combine
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let viewModel = ProfilesViewModel()
private var cancellable: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
try await viewModel.getProfiles()
// Remove this
// self.tableView.reloadData()
} catch {
print(error)
}
}
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
// Add this
cancellable = viewModel.objectWillChange.sink(receiveValue: { [weak self] in
self?.render()
})
}
// Also add this
private func render() {
// TODO: Implement failures...
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
...
}
objectWillChange was the key to my problem.

Update the viewController from the controller in MVC with SWIFT

I'm creating an app with swift. The app get weather from openweathermap.com api in the class WeatherDataModel then when the data are loaded, the model ask the viewController to update the datas
I'm on Xcode 10.2.1 with swift 5
I've create a protocol called in the model to update the data but the updateDisplayDelegate?.updateWeatherDataOnDisplay() is always nil and even if I get the data from the JSON in the console it won't update on the screen
class WeatherDataModel {
var updateDisplayDelegate: ProtocolUpdateDisplay?
func updateWeaterData(json : JSON) {
updateDisplayDelegate?.updateWeatherDataOnDisplay()
}
}
public protocol ProtocolUpdateDisplay {
func updateWeatherDataOnDisplay()
}
class MainViewController: UIViewController {
let weatherDataModel = WeatherDataModel()
override func viewDidLoad() {
super.viewDidLoad()
weatherDataModel.updateDisplayDelegate = self
}
extension MainViewController: ProtocolUpdateDisplay {
func updateWeatherDataOnDisplay() {
cityLabel.text = weatherDataModel.city
tempLabel.text = weatherDataModel.temperature
weatherIcon.image = UIImage(named: weatherDataModel.weatherIconName)
}
}
You should not use delegation pattern for model. Consider using notification:
func updateWeaterData(json : JSON) {
NotificationCenter.default.post(Notification(name: Notification.Name("WeatherDidUpdate")))
}
and observe in any controller you want to respond to this notification:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateWeatherDataOnDisplay(_:)), name: Notification.Name("WeatherDidUpdate"), object: nil)
}
#objc func updateWeatherDataOnDisplay(_ notification: Notification) {
cityLabel.text = weatherDataModel.city
tempLabel.text = weatherDataModel.temperature
weatherIcon.image = UIImage(named: weatherDataModel.weatherIconName)
}
and remove observer at last:
deinit {
NotificationCenter.default.removeObserver(self)
}

Why is my data not passing between View Controllers using closure?

I am trying to pass data receive from a network call to another view controller when user has clicked on a button. When making printing on the FirstVC, data is in, but when printing the result in the SecondVC, there is no more value. I don' t want to use delegate but closure instead.
Also, when trying to retain the memory cycle, an error appear...
class APIsRuler {
static var oneRecipeFound: ((OneRecipeSearch) -> ())?
}
class FirstVC: UIViewController {
func cellIsClicked(index: Int) {
APIsRuler.shared.getRecipe(from: recipeID) { (success, oneRecipe) in
if success, let oneRecipe = oneRecipe {
APIsRuler.oneRecipeFound?(oneRecipe)
self.performSegue(withIdentifier: "goToSecondVC", sender: self)
}
}
}
}
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in
print(result)
}
}
}
Doing this in SecondVC
APIsRuler.oneRecipeFound = { result in
print(result)
}
and this in first
APIsRuler.oneRecipeFound?(oneRecipe)
have no inner communications , you need to read your data directly from the shared class in the secondVc after the segue or send it in
self.performSegue(withIdentifier: "goToSecondVC", sender: <#Herererere#>)
and implement prepareForSegue
Let’s think about the order in which things are happening:
class APIsRuler {
static var oneRecipeFound: ((OneRecipeSearch) -> ())? // 1
}
class FirstVC: UIViewController {
func cellIsClicked(index: Int) {
APIsRuler.shared.getRecipe(from: recipeID) { (success, oneRecipe) in
if success, let oneRecipe = oneRecipe {
APIsRuler.oneRecipeFound?(oneRecipe) // 2
self.performSegue(withIdentifier: "goToSecondVC", sender: self)
}
}
}
}
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in // 3
print(result)
}
}
}
oneRecipeFound starts out life empty: it is nil.
In FirstVC, the cell is clicked. We call oneRecipeFound. It is still nil, so nothing happens.
In SecondVC, we set the value of oneRecipeFound. Now it has a value, but the call has already happened.
So unless you have a time machine in your pocket, so that you can reverse that order of events somehow, the strategy you’ve outlined is doomed to failure. Of course, if you call oneRecipeFound after setting it, it will work. For example:
Class SecondVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIsRuler.oneRecipeFound = { result in
print(result)
}
APIsRuler.oneRecipeFound?(oneRecipe) // prints
}
}

Passing data from view controller to view controller with a delegate

Tried to send data from one view controller (from an alamofire request) to the next view controller in a navigation controller.
I tried to this with a delegate, but I do not get it working. I allready know this is not the way, but i need to find a solution to get it working.
See below for the code, from view controller that sends variabels:
protocol SendDataToScanInfo {
func sendData (vendorname01 : String, productname01: String, productstatus01: String, productdescription01: String)
}
class ScanController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, CLLocationManagerDelegate{
var delegate:SendDataToScanInfo?
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
Alamofire.request(URL_SCAN_ID, method: .post, parameters: ScanParameters, encoding: JSONEncoding.default) .responseJSON
{
response in
//printing response
print(response.request!)
print(response.response!)
print(response.data!)
print(response.result)
print(response.error)
//getting the json value from the server
let value = response.result.value
print(value!)
let json = JSON(value!)
let productdesc0:JSON = json["productdesc"]
let productdescString = productdesc0.string
let productname0:JSON = json["productname"]
let productnameString = productname0.string
let tagstate0:JSON = json["tagstate"]
let tagstateString = tagstate0.string
let vendorname0:JSON = json["vendorname"]
let vendornameString = vendorname0.string
//self.performSegue(withIdentifier: "ScanInfo", sender: productdescString)
self.delegate?.sendData(vendorname01: vendornameString!, productname01: productnameString!, productstatus01: tagstateString!, productdescription01: productdescString!)
print(vendornameString)
}
if code != nil
{
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let destination = mainStoryboard.instantiateViewController(withIdentifier: "ScanInfo")
navigationController?.pushViewController(destination, animated: true)
}
captureSession.stopRunning();
//self.dismiss(animated: true, completion: nil)
}
}
}
Next Viewcontroller should receive it:
class ScanInfoViewController: UIViewController, SendDataToScanInfo {
#IBOutlet weak var Vendor: UILabel!
#IBOutlet weak var VendorScan: UILabel!
#IBOutlet weak var Product: UILabel!
#IBOutlet weak var ProductScan: UILabel!
#IBOutlet weak var Status: UILabel!
#IBOutlet weak var DescriptionScan: UILabel!
#IBOutlet weak var Description: UILabel!
#IBOutlet weak var StatusScan: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
DescriptionScan.text = descriptionBLA
print("jddjd", descriptionBLA)
let URL_SCAN_INFO = "http://makeitrain.get-legit.com:8998/checktag"
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func sendData(vendorname01: String, productname01: String, productstatus01: String, productdescription01: String) {
VendorScan.text = vendorname01
ProductScan.text = productname01
DescriptionScan.text = productdescription01
StatusScan.text = productstatus01
print("MMMM", StatusScan.text)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ScanInfo" {
let sendingVC: ScanController = segue.destination as! ScanController
sendingVC.delegate = self
}
}
}
I hope some one can help me!
To pass data forward, like williej926 said, segues are the way to go. To pass data forward from one viewcontroller to another, you need to create a segue between these two viewcontrollers, and give the segue an identifier if there is more than one segue in your project that you are using to pass data, then this is a must. In your first view controller's class you should create a prepareForSegue method by using the one built-in. In that prepareForSegue method, you write if the segue's identifier is equal to the one that you have set in your storyboard. In that if statement, you need to tell this viewcontroller what your segue's destination is. To do that write let destination = segue.destination as! nextViewControllerClass. To access variables and set them in your second viewcontroller, write destination.variableName = thisVariableName. Here is an example showing you what this looks like purely in code.
In First View Controller's class
class FirstViewController: UIViewController {
var thisString: String?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if(identifier == "secondViewController") {
let destination = segue.destination as! SecondViewController//SecondViewController is second view controller's class
destination.myString = thisString
}
}
}
}
Second View Controller's Class
class SecondViewController: UIViewController {
var myString: String?//this will equal to thisString in FirstViewController
}
I wrote an answer about this not too long ago :
One the simpler way to pass info from one VC to another is either through an initiliazer, or through a variable that you set before presenting the second VC.
The secone method would have you go through a delegate, mainly when passing data BACK to the initial VC. Either way, you'd need a setup similar to this:
class LoggedInVCViewController : UIViewController {
var info : String? {
didSet {
if let newInfo = self.info {
//do what ever you need to do here
}
}
}
override viewDidLoad() {
super.viewDidLoad()
}
}
func presentLoggedInScreen(yourInfo: String) {
let stroyboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loggedInVC:LoggedInVCViewController =
storyboard.instantiateViewController(withIdentifier: "loggedInVC") as!
LoggedInVCViewController
loggedInVC.info = yourInfo
self.present(loggedInVC, animated: true, completion: nil)
}
class LoggedInVCViewController : UIViewController {
var info : Any? {
get {
if let this = self.info {
return this
} else {
return nil
}
} set {
if let new = newValue {
//
}
}
}
init(info: Any?) {
//This first line is key, it also calls viewDidLoad() internally, so don't re-write viewDidLoad() here!!
super.init(nibName: nil, bundle: nil)
if let newInfo = info {
//here we check info for whatever you pass to it
self.info = newInfo
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Which is then used :
func presentLoggedInScreen(yourInfo: String) {
let loggedInVC = LoggedInVCViewController(info: yourInfo)
self.present(loggedInVC, animated: true, completion: nil)
}
Or if you're using the variable approach:
func presentLoggedInScreen(yourInfo: String) {
let loggedInVC = LoggedInVCViewController()
loggedInVC.info = yourInfo
self.present(loggedInVC, animated: true, completion: nil)
}
I also go over, and link to other post which talk about the caveats of using Storyboards, and custom initializers to pass on data. I'd read over them as well!
The best way to do this is by using a segue. Connect a segue between the controllers and in the prepareForSegue you add a variable that represents the controller you are segueing to like so: let viewController = segue.destination as! viewController. Now you can access and change variables inside viewController using viewController.variable.

Swift Delegate setting a label from a custom popUp textfield

I have a custom popup view that has a UIDatePicker. This, when changed, changes the date of the save time. I also want the label on the in the CustomCell to be updated if the date has changed. I have used a delegate protocol to update the table but I cannot get this protocol to transfer the information on save. Can you help? I think I have hooked up all the correct code in the viewController class. I have tried this answer but I cannot set the delegate in the target class and there isn't a segue A Swift example of Custom Views for Data Input (custom in-app keyboard)
protocol DatePopUpViewDelegate: class {
func pastDate(date: String) // date that is chosen in picker
func isPastDateSet(isSet: Bool) // has chosen new date
}
#IBDesignable class DatePopUpView: UIView {
var delegate: DatePopUpViewDelegate?
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "DatePopUp", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
delegate?.isPastDateSet(false)
return view
}
// close popup
#IBAction func closeButtonDatePopUp(sender: AnyObject) {
if dateToSave != openTime {
if let dateToSave = dateToSave {
SaveData.changedSaveTime = dateToSave
delegate?.pastDate(dateToSave)
delegate?.isPastDateSet(true)
}
} else {
SaveData.changedSaveTime = ""
delegate?.isPastDateSet(false)
}
}
class SaveTableViewCell: UITableViewCell, DatePopUpViewDelegate {
var changeDateLabel: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
changeDateLabel = false
datePopUpViewControllert.delegate = self
}
// delegate functions
func pastDate(date: String) {
self.labelDate = date
print("del date \(date)")
}
func isPastDateSet(isSet: Bool) {
self.changeDateLabel = isSet
print("is set by delegate \(isSet)")
}