Refresh UITableViewController data - ios

I have 2 controllers, DashboardController and LocateVehicleController. LocateVehicleController has UITableViewController.
In DashboardController, On button press I am doing API call and getting data. And sending array to LocateVehicleController.
let locateVehicleStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let locateVehicleController = locateVehicleStoryboard.instantiateViewController(withIdentifier: "tempID") as? LocateVehicle
self.present(locateVehicleController!, animated: true, completion: nil)
locateVehicleController?.dataArray = self.locateVehicleDataArr
locateVehicleController?.tableView.reloadData()
In LocateVehicleController I have refresh button, If I press refresh button I need to update the tableview controller data which I have used API call data from DashboardController.
As per my understanding when I press refresh button, same API call will invoke. Please help to solve this. Thanks in advance.

I would move the logic of calling of this API to another class. Inject this class into DashboardController and LocateVehicleController and use it to fetch you data. Something like this:
struct Vehicle {
}
protocol VehicleDataFetcherProtocol {
func getVehicleData(completionHandler:([Vehicle])->()) // assuming the data is in form of an Array of a class/struct Vehicle
}
class VehicleDataFetcher: VehicleDataFetcherProtocol {
// Hit API, get data and call the completion hanlder
func getVehicleData(completionHandler:([Vehicle])->()) {
}
}
class DashboardController:UIViewController {
private var vehicleDataFetcher: VehicleDataFetcher!
func injectVehicleDataFetcher(dataFetcher:VehicleDataFetcher) {
self.vehicleDataFetcher = dataFetcher
}
func presentLocateVehicle() {
let locateVehicleStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let locateVehicleController = locateVehicleStoryboard.instantiateViewController(withIdentifier: "tempID") as? LocateVehicleController
self.present(locateVehicleController!, animated: true, completion: nil)
locateVehicleController?.injectVehicleDataFetcher(dataFetcher: self.vehicleDataFetcher)
locateVehicleController?.dataArray = self.locateVehicleDataArr
locateVehicleController?.tableView.reloadData()
}
}
class LocateVehicleController:UIViewController {
private var vehicleDataFetcher: VehicleDataFetcher!
func injectVehicleDataFetcher(dataFetcher:VehicleDataFetcher) {
self.vehicleDataFetcher = dataFetcher
}
}

Related

passing data from 2 view controllers without segue

I have a mainviewcontroller and a popup view controller which opens without a segue.
the popup viewcontroller recive data from Firebase, and then i need to append this data to an array in the mainviewcontroller.
How can i do that?
(i tried to create a property of the popupviewcontroller in the mainviewcontroller, but in crashes the app)
this is the code that opens the popup:
#IBAction func showPopUp(_ sender: Any) {
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "sbPopUp") as! PopUpViewController
self.addChild(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParent: self)
You need to connect the classes so that the popup class knows what to do with the data once it has been received. Here's a sample structure that works in a playground that you should be able to apply to your real classes.
class MainClass {
func showPopUp() {
let popOverVC = PopUpClass()
popOverVC.update = addToArray
popOverVC.receivedData()
}
func addToArray() {
print("Adding")
}
}
class PopUpClass {
var update: (()->())?
func receivedData() {
if let updateFunction = update {
updateFunction()
}
}
}
let main = MainClass()
main.showPopUp()
Or you could create a global variable so it can be accessed anywhere. ... It is better to pass data but I just thought it would be easier in this instance, so the variable can be accessed anywhere in your entire project.
if it is just a notification, you can show it in an alert, but if you don't want to use an alert my offer to present another view controller is not like this , try my code :
//if you have navigation controller ->
let vc = self.storyboard?.instantiateViewController(
withIdentifier: "storyboadrID") as! yourViewController
self.navigationController?.pushViewController(vc, animated: true)
//if you don't use navigation controller ->
let VC1 = self.storyboard!.instantiateViewController(withIdentifier: "storyboadrID") as! yourViewController
self.present(VC1, animated:true, completion: nil)
also if you want to pass any data or parameter to destination view controller you can put it like this before presenting your view controller :
VC1.textView.text = "test"

How to push user to ViewController from non UIView class [duplicate]

This question already has answers here:
How to launch a ViewController from a Non ViewController class?
(7 answers)
Closed 5 years ago.
I would like to know how can I push user back to specific ViewController from regular swift class without being non UIView Class
Example
class nonUI {
function Push() {
//How to send user back to specific VC here?
}
}
This is a generic method you can use with in the class or outside the class for push if required else it will pop if the instance of view controller is in the stack:
func pushIfRequired(className:AnyClass) {
if (UIViewController.self != className) {
print("Your pushed class must be child of UIViewController")
return
}
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var isPopDone = false
let mainNavigation = UIApplication.shared.delegate?.window??.rootViewController as? UINavigationController
let viewControllers = mainNavigation!.viewControllers
for vc in viewControllers {
if (type(of: vc) == className) {
mainNavigation?.popToViewController(vc, animated: true)
isPopDone = true
break
}
}
if isPopDone == false{
let instanceSignUp = storyboard.instantiateViewController(withIdentifier: NSStringFromClass(className)) // Identifier must be same name as class
mainNavigation?.pushViewController(instanceSignUp, animated: true)
}
}
USES
pushIfRequired(className: SignupVC.self)
You could also utilise the NotificationCenter to achieve a loosely coupled way to "request a view controller"; if you will.
For example, create a custom UINavigationController that observes for the custom Notification and upon receiving one, looks for the requested UIViewController and pops back to it.
class MyNavigationController : UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(forName: NSNotification.Name("RequestViewController"), object: nil, queue: OperationQueue.main) { [unowned self] (note) in
guard let targetType = note.object as? UIViewController.Type else {
print("Please provide the type of the VC to display as an `object` for the notification")
return
}
// Find the first VC of the requested type
if let targetVC = self.viewControllers.first(where: { $0.isMember(of: targetType) }) {
self.popToViewController(targetVC, animated: true)
}
else {
// do what needs to be done? Maybe instantiate a new object and push it?
}
}
}
}
Then in the object you want to go back to a specific ViewController, post the notification.
#IBAction func showViewController(_ sender: Any) {
NotificationCenter.default.post(Notification(name: NSNotification.Name("RequestViewController"), object: ViewController2.self))
}
Now, it's also fairly easy to adopt this method for other presentation-styles.
Instead of using the NotificationCenter, you could also muster up a Mediator to achieve the loose coupling.
You can't. UIViewController and its subclass only can handle navigate between screen.
In your case, need pass link (variable) to navigation controller in custom class.
Like:
class nonUI {
var navigationController: UINavigationController?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
function Push() {
//How to send user back to specific VC here?
navigationController?.popViewController(animated: true)
}
}

Swift 3 pass data from one ViewController to another ViewController

I am new to IOS programming and don't really know what I'm asking but I will try and explain
I am using Firebase to auth and then I want to take the UID and pass it to a different VC the problem I am getting I cant get the var userID to print out side of the IBAction here is where I am so far, any pointer would get good cheers guys
#IBAction func creatAccountPressed(_ sender: Any) {
if let email = emailTextField.text,
let password = passwordTextField.text,
let name = nameTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion: { user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
}
let userID = user!.uid
let userEmail: String = self.emailTextField.text!
let fullName: String = self.nameTextField.text!
self.ref.child("users").child(userID).setValue(["Email": userEmail, "Name": fullName])
self.userID1 = user!.uid as! String
})
print(self.userID1)
presentLoggedInScreen()
}
}
func presentLoggedInScreen() {
let stroyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let loggedInVC: LoggedInVCViewController = stroyboard.instantiateViewController(withIdentifier: "loggedInVC") as! LoggedInVCViewController
self.present(loggedInVC, animated: true, completion: nil)
}
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.
Since you are new to this, try the variable approach for now, so if say, you're passing a string:
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)
}
Mind you, you can always use any other king of variable Class for info. You also would program your VC to execute some methods inside of the get bracket of the property, populate some fields based on the content of info, load a specific UI etc.
Another usefull way is to go the initialization route, but unfortunately you cannot use it with Storyboard nibs (sad i know, but this post goes over this nicely), but it still usefull whenever you will feel comfortable enough to initialize and design VC's programmatically (which I would learn ASAP if were you).
You pass a variable in a custom intializer method for your VC:
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")
}
}
So you would use is as:
func presentLoggedInScreen(yourInfo: String) {
let loggedInVC = LoggedInVCViewController(info: yourInfo)
self.present(loggedInVC, animated: true, completion: nil)
}
Obviously, as stated, this method cannot be used with Storyboards, but very usefull and, as I'm sure you can see, is much more compact.
I suggest you get familiar with Swift Properties of the docs, along with some blog tips such as this one. You can get pretty creative after a while.
For learning more programmatic approaches, i strongly recommend this YouTube channel: Let's Build That App, I personnaly haven't found a better reference point for programmatic approach Swift programming.
Don't hesitate to ask questions!
UPDATE
Your IBAction should look like this:
#IBAction func creatAccountPressed(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text, let name = nameTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion: { user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
}
let userID = user!.uid
let userEmail: String = self.emailTextField.text!
let fullName: String = self.nameTextField.text!
self.ref.child("users").child(userID).setValue(["Email": userEmail, "Name": fullName])
self.userID1 = user!.uid as! String
print(self.userID1)
presentLoggedInScreen(yourInfo: self.userID1)
})
}
}
Put
print(self.userID1)
presentLoggedInScreen()
inside the completion block. Currently those lines are almost certain to happen before the completion block is done as the block is executed after the required network calls return.
Be sure to wrap presentLoggedInScreen() in a block that dispatches it to the main thread as it touches the UI and all UI calls must be made from the main thread.
DispatchQueue.main.async {
presentLoggedInScreen()
}
This will make it so that presentLoggedInScreen() executes after the value as been assigned to userID1 allowing you to pass the value over to the incoming view controller (assuming, of course, that the incoming VC has an appropriate variable to pass userID1 into).
Something like:
loggedInVC.userID = self.userID1
before you present the VC.

IOS Swift how can I reload a tableView from a different controller

I have 2 controllers A and Controller B . Controller A has a TableView and Controller B is a subview that when clicked opens a form and on Submit it enters data into the database. My problem is that I attempt to reload my TableView from Controller B from the user hits submit and I get the following error
fatal error: unexpectedly found nil while unwrapping an Optional value from this line
self.TableSource.reloadData()
Now the data from Controller B is successfully inserted so after I restart my app the data I submit is there . This is my code (TableSource is the TableView outlet)
Controller A
func reloadTable(latmin: Float,latmax: Float,lonmin: Float, lonmax: Float) {
let url:URL = URL(string:ConnectionString+"MY-URL")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let parameter = "parameters"
request.httpBody = parameter.data(using: String.Encoding.utf8)
session.dataTask(with:request, completionHandler: {(data, response, error) in
if error != nil {
} else {
do {
let parsed = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let S = parsedData["myData"] as? [AnyObject] {
for A in Data {
// gets Json Data
}
DispatchQueue.main.async {
// This is what I named my TableView
self.TableSource.reloadData()
}
}
} catch let error as NSError {
print(error)
}
}
}).resume()
}
That is my HTTP-Request that gets data from the database, now in that same Controller A I have a button that when clicked opens the SubView to Controller B and this is the code
#IBAction func Post_Action(_ sender: Any) {
let Popup = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ControllerB") as! Controller B
self.addChildViewController(Popup)
Popup.view.frame = self.view.frame
self.view.addSubview(Popup.view)
Popup.didMove(toParentViewController: self)
}
This is the code in Controller B and this is how I try to reload the TableView in Controller A
#IBAction func Submit_Form(_ sender: Any) {
// Code that submits the form no issues here
latmin = 32.18
latmax = 32.50
lonmin = -81.12
lonmax = -81.90
let Homepage = ControllerA()
Homepage.reloadTable(latmin: latmin!,latmax: latmax!,lonmin: lonmin!,lonmax: lonmax!)
}
So as stated before Controller A loads the data from the Database, Controller B has a form and when submitted it enters new data into the database . That whole process works I just now want to update the TableView in Controller A from the form is submitted in Controller B
I would suggest using protocol:
protocol SomeActionDelegate {
func didSomeAction()
}
In ViewController B
var delegate: SomeActionDelegate?
In ViewController A when segue
viewControllerB.delegate = self
You should add this
extension ViewControllerA: SomeActionDelegate {
func didSomeAction() {
self.tableView.reloadData()
}
}
And in ViewController B
func didChangeSomething() {
self.delegate?.didSomeAction()
}
It works like when ViewController B didChangeSomething() it sends message to ViewController A that it should didSomeAction()
You can do it with NSNotification
in swift 3.0
Think you have two viwe controllers called viewcontrollerA and viewControllerB
viewcontrollerA has the tableview.
you need to reload it from viewcontrolerB
implementaion of viewcontrollerA
create a function to relod your tableview in viewcontrollerA and call it in viewDidLoad
override func viewDidLoad() {
let notificationNme = NSNotification.Name("NotificationIdf")
NotificationCenter.default.addObserver(self, selector: #selector(YourControllername.reloadTableview), name: notificationNme, object: nil)
}
func relodaTableview() {
self.TableSource.reloadData()
}
implementation in viewcontrollerB (where you want to reload tableview)
post the notification in button click or anywhere you want like below
let notificationNme = NSNotification.Name("NotificationIdf")
NotificationCenter.default.post(name: notificationNme, object: nil)
hope this will help to you.

How would I have one view controller access a variable from another view controller?

Everything I've seen on stack is passing the data from an input, onto another view controller on a button press. Let's say I have var banana that is an array of dictionaries, but once my function in ViewA.swift is done loading up banana, I want another viewController, say ViewB.swift to manipulate that data as it sees fit. I do NOT have a segue going from one view controller to the other.
EDIT: It's actually two TableViewControllers****
I've looked into NSNotificationCenter, but that doesn't seem to work with my variable type, which is an array of dictionaries
Use NSNotificationCenter for accessing data.
Try Below code
//Sent notification
let dictionary = ["key":"value"]
NSNotificationCenter.defaultCenter().postNotificationName("passData", object: nil, userInfo: dictionary)
//Receive notification
NSNotificationCenter.defaultCenter().addObserver(self,
selector:"myMethod:", name: "passData", object: nil)
//Receive notification method
func myMethod(notification: NSNotification){
print("data: \(notification.userInfo!["key"])")
Without using segue, you can instantiate the View controller, and set the public parameteres.
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("someViewController") as! ViewB
/* Here you have the reference to the view, so you can set the parameters*/
vc.parameterInViewB = banana
/* At this point you can present the view to the user.*/
self.presentViewController(vc, animated: true, completion: nil)
Make sure that you have given all ViewControllers an identifier, then instantiate them with:
guard let viewControllerB = storyboard?.instantiateViewControllerWithIdentifier("ViewControllerB") as? ViewControllerB else {
fatalError(); return
}
// then access the variable/property of ViewControllerB
viewControllerB.banana = whatEver
Added for clarification
This one works for me.
Just make sure that you have given the TableViewController an identifier otherwise you will not be able to instantiate it. Also make sure that you cast the result of instantiateViewControllerWithIdentifier to your TableViewController class otherwise you won't be able to access it's variables (I've seen that you were struggling with this; if you get an error that UIViewController doesn't have a member "myArray" then you probably have forgotten to cast the result)
class TableViewController: UITableViewController {
var myArray = [String]()
}
class ViewController: UIViewController {
func someEventWillTriggerThisFunction() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let tableViewController = storyboard.instantiateViewControllerWithIdentifier("TableViewController") as? TableViewController else {
fatalError(); return
}
tableViewController.myArray = ["Value1", "Value2", "Value3"]
/* if you want to present the ViewController use this: */
self.presentViewController(tableViewController, animated: true, completion: nil)
}
}

Resources