Swift using delegate tableview to refresh content from another class - ios

I need to update the content of a TableView from another class that execute an async call to an external API.
I implemented a protocol to refresh the table after the API call but seem that this code is never executed.
I can't understand what is wrong with my code.. probably I forgot something, this is my first application and I just start studying protocols implementation
Below I wrote an example of my code where I remove all unnecessary code (for this question).
Protocol definition:
protocol ProductUpdateDelegate: class {
func refreshTableView()
}
Tabelview Implementation:
class TestTableViewController: UITableViewController,
UITextFieldDelegate, ProductUpdateDelegate {
var productList = [productInfo]()
// Delegate function implementation
func refreshTableView() {
self.tableView.reloadData()
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 50;
self.tableView.dataSource = self
self.tableView.delegate = self
more code ...
}
#IBAction private func buttonTappedAction(_ sender: Any) {
code that get input from user and did some check...
if(isValidCode){
self.storeProduct(productCode: code)
}
}
func storeProduct(productCode: String){
if(isNewCode){
self.productList.append(productInfo(code: productCode,
quantity: 1))
}
more code ...
}
Class that need to update some information on tableView
import Foundation
class Product {
weak var delegate: ProductUpdateDelegate?
some code here...
func getProductInfoFromAPI(){
API CAll...
if(result != "no_data")
self.delegate?.refreshTableView()
}
more code ...
}
class used to store informations about products and used to update products description using remote API
import Foundation
class productInfo: NSObject, NSCoding, Codable {
var code = ""
var quantity = 1
public var product_name = "Wait for information..."
init(code: String, quantity : Int,
product_name : String = "Wait for information...") {
self.code = code
self.quantity = quantity
self.product_name = product_name
super.init()
self.getProductInfoFromAPI()
}
required convenience init(coder aDecoder: NSCoder) {
let code = aDecoder.decodeObject(forKey: "code") as! String
let quantity = aDecoder.decodeInteger(forKey: "quantity")
self.init(code:code, quantity:quantity)
self.getProductInfoFromAPI()
}
func getProductInfoFromAPI(){
let productInfo = Product()
productInfo.getInfoByCode(productCode: code, refObject: self)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.code, forKey: "code")
aCoder.encode(self.quantity, forKey: "quantity")
}
}
UPDATE1: The product class is called from a some other classes (4 in total) that I not included here for sake of simplicity
The application workflow is:
Application start
show the TestTableViewController
Then there are some different options for the user to update the TableView content each option is controlled by a separate class, so the product class is called from some other classes and is the only one that interact directly with the TestTableViewController
UPDATE2:
Add more code to show the complete process.
After view initialization, the user can click on a button and can insert a product code.
The inserted code is saved in a array of productInfo using append. This action activate the init method of the productInfo class and a call to a remote service start to retrieve information about the product itself.
The problem is that the article information are update correctly but are not show on the tableview because the view is not updated.
Because is an async process, i need a way to update the view content when the retrieving process has been completed.
So i think that the best solution was the protocols. But as already stated, the function refreshTableView seem ignored.

var refreshControl = UIRefreshControl()
#IBOutlet weak var tblView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl.attributedTitle = NSAttributedString(string: "")
refreshControl.addTarget(self, action: #selector(refresh(sender:)), for: UIControl.Event.valueChanged)
tblView.addSubview(refreshControl)
}
#objc func refresh(sender:AnyObject) {
refreshControl.endRefreshing()
apiFunction()
}
func apiFunction(){
// call your api
}

Based in your code, you're not setting any value to your weak variable named delegate in Product class. You should set it to the class who should be using it.
One way is to make your Product class as singleton in a way that it will only initialize once.
e.g.
class Product {
static let shared = Product()
weak var delegate: ProductUpdateDelegate?
private init() { }
func getProductInfoFromAPI(){
if(result != "no_data")
self.delegate?.refreshTableView()
}
}
And in you tableview controller instead of let productInfo = Product() you can use let productInfo = Product.shared then set your delegate.
productInfo.delegate = self
That's it!

Related

Access data / functions from several view controllers

I am trying to build an app with several screens. Every screen should have different buttons which all call one function.
My problem is that I do not understand how to call one function from different view controllers with input parameters.
Also I want to have another variable defined accessible and changeable from every view controller.
This is what I kind of want my code to be:
import UIKit
var address = "address"
public func makeRequest(Command: String){
let url = URL(address + Command)
print(url)
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let command = "command"
makeRequest(Command: command)
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("address")
address = "address2"
}
}
If all classes are view controllers put the method in an extension of UIViewController and the address constant in a struct
struct Constant {
static let address = "address"
}
extension UIViewController {
public func makeRequest(command: String) -> URL? {
guard let url = URL(string: Constant.address + command) else { return nil }
print(url)
return url
}
}
extension UIViewController{
func getName(name: String)-> String{
print("hai \(name)")
return name
}
}
you can write a extension for Viewcontroller and try to call the method like this. if you write the extension for viewcontroller you can directly call them with its reference
self.getName(name:"Raghav")

Swift get value data from protocol

i need help with my code for swift 5,
so i make a struct and protocol to store list from uitextfield and now i wanna show that data in a UiTextView in another view controller
struct PatientNote {
var note : String
init(note :String) {
self.note = note
}
}
protocol AddNotesDelegate {
func AddNotes(controller : UIViewController, notes: PatientNote)
}
class AddNotesController: UIViewController {
var delegate : AddNotesDelegate!
#IBOutlet weak var Notes: UITextView!
#IBAction func addNotes(_ sender: Any) {
if let notes = self.Notes.text {
let patientNote = PatientNote(note: notes)
self.delegate.AddNotes(controller: self, notes: patientNote)
print(patientNote.note)
}
}
}
and now i wanna show in my view controller but i get this error of "Cannot convert value of type 'PatientNote' to expected argument type 'String'" in this viewController
class NotePatientController: UIViewController, AddNotesDelegate{
func AddNotes(controller: UIViewController, notes: PatientNote) {
let NotesPatient = PatientNote(note: notes) *this is where i get the error
}
var delegate : AddNotesDelegate!
var pasien : PatientNote!
override func viewDidLoad() {
super.viewDidLoad()
PatientTextView.text = pasien.note
}
#IBOutlet weak var PatientTextView: UITextView!
//in this ibaction i edit the notes that i get from the first Vc which is AddNotesController
#IBAction func Save(_ sender: UIButton) {
if let notes = self.PatientTextView.text {
let pasienNotes = PatientNote(note: notes)
self.delegate.AddNotes(controller: self, notes: pasienNotes)
}
}
}
i try to show the note from the AddNotesController to the NotePatientController, and in the NotePatientController i can edit and save the notes in UiTextView.
so i know i must be using the protocol in a wrong way, can someone help me how should i use it? im still kinda new in swift so could probably use any help i can get, Cheer!
Change let notesPatient = PatientNote(note: notes) to let notesPatient = PatientNote(note: notes.note)
It appears PatientNote takes a String as an argument but you passed an already created PatientNote to it instead. The below syntax, using notes.note would be a cleaner solution without involving initialising a new PatientNote.
func AddNotes(controller: UIViewController, notes: PatientNote) {
print(notes.note) // access the note String like this
}

Use function from a structure in class

I am new to Swift and I have trouble using classes and structures.
I have a Structure called Workspace:
struct Workspace: Decodable {
var guid: String
var name: String
func getUserWorkspace(base: String, completed: #escaping () -> ()){
//some code
}
}
Here is my class User:
public class User {
var Wor = [Workspace]()
var WorData:Workspace? = nil
//+some other var & functions
}
So what I'm doing in my view controller is this:
class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var listView: UITableView!
var co = User()
override func viewDidLoad() {
super.viewDidLoad()
co.WorData?.getUserWorkspace(base: co.Base) {
print("success")
self.listView.reloadData()
self.updateVertically()
}
listView.delegate = self
listView.dataSource = self
}
The problem is that the code never goes inside the function co.WorData?.getUserWorkspace(base: co.Base)
Before I put it in the structure it was directly in the class but since I changed it it doesn't work anymore so I think I might be calling it the wrong way ?
WorData is nil.
Conditional unwrapping (co.WorData?.getUserWorkspace(base: co.Base) will check WorData has a value before trying to call the method. If it was nil and Swift didn't do this, it would crash.
You either need to set it as new all the time
var worData = Workspace()
Set it after class init
var user = User()
user.worData = Workspace() // or pass a specific one in
or require your User object to be initialised with a Workspace
class User: NSObject {
var wor = [Workspace]()
var workspace: Workspace // use lower camel case for var names
required init(workspace: Workspace) {
self.workspace = workspace
}
}

passing data between view controllers without changing views

I want to pass data between two view controllers, but don't want the view to change when the users presses my save data button.
The users needs to fill in multiple data fields, and when finish can press another button to go to the second view controller.
I found many tutorials how to pass data using segue, but they all change view as soon as the 'save button is pressed'.
Any one can explain to me how to alter the code?
#Phillip Mills: here is how I used your code. (what am I doing wrong?)
code:
//////// declaring classes on FirstViewController (trying it first on only one ViewController)
class FakeVC1 {
func userInput() {
DataModel.shared.username = outbj14u.text
}
class FakeVC2 {
func viewAppears() {
if let name = DataModel.shared.username {
outbj14p.text = name
print("I have nothing to say")
}
}
}
class DataModel {
static let shared = DataModel()
var username: String?
}
////till here
//// here is where i call the functions
override func viewDidAppear(_ animated: Bool) {
FakeVC1().userInput()
FakeVC2().viewAppears()
if let xbj14p = UserDefaults.standard.object(forKey: "outbj14p") as? String
{
outbj14p.text = xbj14p
}
if let xbj14u = UserDefaults.standard.object(forKey: "outbj14u") as? String
{
outbj14u.text = xbj14u
}
////
#Phillip Mills: Below is what I have know. I think I got the code on the FirstViewController right, but the code on the Second View controller must be wrong. I don't get any errors, but the text field on the SecondViewController remains unchanged after putting input on in the FirstViewController
//// Code on the FirstViewController
class DataModel {
static let shared = DataModel()
var username: String?
}
#IBAction func savebj14p(_ sender: Any) {
outbj14p.text = inbj14p.text
DataModel.shared.username = outbj14p.text
UserDefaults.standard.set(inbj14p.text, forKey: "namebj14p")
}
//and on the SecondViewController
#IBOutlet weak var bj14u: UILabel! // connected to a label
//and
class DataModel {
static let shared = DataModel()
var username: String?
}
override func viewDidAppear(_ animated: Bool) {
if let name = DataModel.shared.username {
bj14u.text = name
}
}
In your case, don't pass data.
Create a shared object to act as your data model. When users fill in the fields, update the data model.
When the user moves to the second controller/view, that controller uses the data model object to show what it needs to.
class FakeVC1 {
func userInput() {
DataModel.shared.username = "Me"
}
}
class FakeVC2 {
func viewAppears() {
if let name = DataModel.shared.username {
print(name)
} else {
print("I have nothing to say")
}
}
}
class DataModel {
static let shared = DataModel()
var username: String?
}
FakeVC1().userInput()
FakeVC2().viewAppears()
If you need to pass value to another viewcontroller without changing the view , you can user NSNotificationCenter class
Refer this link for more details
NSNotificationCenter addObserver in Swift
what i will recommend is to use a global variable or array, you will have the info in all view controllers and you will be able to call it in your new view controller.

Swift 3 : Back to last ViewController with sending data [duplicate]

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 5 years ago.
I'm trying to go back to my las viewController with sending data, but it doesn't work.
When I just use popViewController, I can go back to the page, but I can't move my datas from B to A.
Here is my code :
func goToLastViewController() {
let vc = self.navigationController?.viewControllers[4] as! OnaylarimTableViewController
vc.onayCode.userId = taskInfo.userId
vc.onayCode.systemCode = taskInfo.systemCode
self.navigationController?.popToViewController(vc, animated: true)
}
To pass data from Child to parent Controller, you have to pass data using Delegate pattern.
Steps to implement delegation pattern, Suppose A is Parent viewController and B is Child viewController.
Create protocol, and create delegate variable in B
Extend protocol in A
pass reference to B of A when Push or Present viewcontroller
Define delegate Method in A, receive action.
After that, According to your condition you can call delegate method from B.
You should do it using delegate protocol
class MyClass: NSUserNotificationCenterDelegate
The implementation will be like following:
func userDidSomeAction() {
//implementation
}
And ofcourse you have to implement delegete in your parent class like
childView.delegate = self
Check this for more information
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
You have to send back to last ViewController with 2 options.
1. Unwind segue. (With use of storyboard)
You can refer this link.
2. Use of delegate/protocol.
You can refer this link.
Also this link will be useful for you.
You can use Coordinator Pattern
For example, I have 2 screens. The first displays information about the user, and from there, he goes to the screen for selecting his city. Information about the changed city should be displayed on the first screen.
final class CitiesViewController: UITableViewController {
// MARK: - Output -
var onCitySelected: ((City) -> Void)?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
onCitySelected?(cities[indexPath.row])
}
...
}
UserEditViewController:
final class UserEditViewController: UIViewController, UpdateableWithUser {
// MARK: - Input -
var user: User? { didSet { updateView() } }
#IBOutlet private weak var userLabel: UILabel?
private func updateView() {
userLabel?.text = "User: \(user?.name ?? ""), \n"
+ "City: \(user?.city?.name ?? "")"
}
}
And Coordinator:
protocol UpdateableWithUser: class {
var user: User? { get set }
}
final class UserEditCoordinator {
// MARK: - Properties
private var user: User { didSet { updateInterfaces() } }
private weak var navigationController: UINavigationController?
// MARK: - Init
init(user: User, navigationController: UINavigationController) {
self.user = user
self.navigationController = navigationController
}
func start() {
showUserEditScreen()
}
// MARK: - Private implementation
private func showUserEditScreen() {
let controller = UIStoryboard.makeUserEditController()
controller.user = user
controller.onSelectCity = { [weak self] in
self?.showCitiesScreen()
}
navigationController?.pushViewController(controller, animated: false)
}
private func showCitiesScreen() {
let controller = UIStoryboard.makeCitiesController()
controller.onCitySelected = { [weak self] city in
self?.user.city = city
_ = self?.navigationController?.popViewController(animated: true)
}
navigationController?.pushViewController(controller, animated: true)
}
private func updateInterfaces() {
navigationController?.viewControllers.forEach {
($0 as? UpdateableWithUser)?.user = user
}
}
}
Then we just need to start coordinator:
coordinator = UserEditCoordinator(user: user, navigationController: navigationController)
coordinator.start()

Resources