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

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.

Related

How do I pass my array data coming from api to another ViewController?

I'm new in iOS.
I'm trying to pass my array data to another view controller through prepareforsegue method.
And this api is called inside the #IBAction func ButtonTapped( ).
class FirstVc {
var location = [Any]()
self.clientrequest.request(url: "http://api.details.in/api/users/alllocation", method: .GET, completion: {
res , err in
let json = try! JSONSerialization.data(withJSONObject: res , options: .prettyPrinted)
let decode = try! JSONDecoder().decode(SelectLocation.self, from:json)
self.location = decode.states
//I'm getting all locations here
print(self.location)
})
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is SecondVc
{
let vc = segue.destination as? SecondVc
//here I'm assigning the variable and while I'm printing location here then also I'm getting the value
vc?.mineSpillere2 = self.location
}
}
class SecondVc{
#IBOutlet weak var selectState: UILabel!
var mineSpillere2 = [Any]()
override func viewDidLoad() {
//While I'm trying to print mineSpillere2 here I', getting []
selectState.text = mineSpillere2 as? String
}
}
One problem might be that you don't actually assign the variable (because vc may be nil). Also you may want to use segueIdentifiers, especially if you have more than one. To make sure that you do so, you may want to use something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondVc //, segue.identifier = "YOUR_SEGUE_ID"
{
vc.mineSpillere2 = self.location
} else { print("SecondVc couldn't be initialised!") }
}
First, the problem is that web service request is not executed asynchronously but synchronously. In other words, your segue is already performed before decode.states is assigned to the location variable.
The solution is to call performSegue function in the completion block:
self.clientrequest.request(url: "http://api.details.in/api/users/alllocation", method: .GET, completion: {
res , err in
let json = try! JSONSerialization.data(withJSONObject: res , options: .prettyPrinted)
let decode = try! JSONDecoder().decode(SelectLocation.self, from:json)
self.location = decode.states
self.performSegue(identifier: "your identifier",
sender: nil)
//I'm getting all locations here
print(self.location)
})
Second,
Make sure your segue is assigned from VC1 to VC2 not Button to VC2.
There are several way to pass this in another ViewController. Good way is make a model class and pass model object to another vc .
Or might be another short and easy solution is make Global variable. So you can access it from any ViewController class. like
var myGloblArray= [Any]()
//add your result array in this array , you can access it from second VC Directly.
class FirstVc {
...
}

Does anyone have good idea to handle the deeplink with the id which is supposed to show the single page?

Does anyone have good idea to handle the deeplink?
I want to push a single page view which needs id from the HomeViewcontroller(or anything is ok) to the single page with the id that I get from the deeplink.
My current situation is that I could get the deeplink and the id inside of that in AppDelegate file by the way like below.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
let linkHandled = DynamicLinks.dynamicLinks()!.handleUniversalLink(incomingURL) { [weak self](dynamiclink, error) in
guard let strongSelf = self else { return }
if let dynamiclink = dynamiclink, let _ = dynamiclink.url {
strongSelf.handleIncomingDynamicLink(dynamicLink: dynamiclink)
}
}
return linkHandled
}
return false
}
func handleIncomingDynamicLink(dynamicLink: DynamicLink) {
guard let pathComponents = dynamicLink.url?.pathComponents else {
return
}
if pathComponents.count > 1 {
for (i, value) in pathComponents.enumerated() {
if i == 1 {
// define whether the deeplink is for topic or post
UserDefaults.standard.set(value, forKey: "deepLinkType")
print(value)
} else if i == 2 {
UserDefaults.standard.set(value, forKey: "deepLinkId")
print(value)
}
}
}
}
And then viewDidAppear in the HomeViewController
if (self.isViewLoaded && (self.view.window != nil)) {
let us = UserDefaults.standard
if let deepLinkType = us.string(forKey: "deepLinkType"), let deepLinkId = us.string(forKey: "deepLinkId"){
us.removeObject(forKey: "deepLinkType")
us.removeObject(forKey: "deepLinkId")
if deepLinkType == "topic" {
let storyboard = UIStoryboard(name: "Topic", bundle: nil)
let nextVC = storyboard.instantiateViewController(withIdentifier: "SingleKukaiVC") as! TopicViewController
nextVC.topicKey = deepLinkId
self.navigationController?.pushViewController(nextVC, animated: true)
} else if deepLinkType == "post" {
}
}
}
this works fine when the app is neither in foreground nor background I mean if it's not instanced. However, while the app is instanced, this doesn't work because viewDidAppear is not going to be read. Or even the HomeViewController itself is not might be called if user had opened another view.
So my question is that what is the best way to handle the deeplink which has id for the single page? I appreciate some examples.
Thanks in advance.
Don't write your code to push Topic view controller in the HomeViewController.
Inside the App Delegate's handleIncomingDynamicLink method, get the top most view controller, then create the view controller (as in your code) and then push it from the top most view controller.
Your code to create Topic View Controller:
let storyboard = UIStoryboard(name: "Topic", bundle: nil)
let nextVC = storyboard.instantiateViewController(withIdentifier: "SingleKukaiVC") as! TopicViewController
nextVC.topicKey = deepLinkId
Check URL to see how to fetch top most view controller
Two ways you could handle it:
For the ViewController that you want presented upon handling of the deeplink set an initializer that takes info contained in the deeplink. And set whatever you need to set to handle that info(you seem to have done that). Now in your handleIncomingDynamicLink() instantiate the ViewController we mentioned and make it to be presented. How you are going to make it present itself depends on the navigation logic that you have set.
AppDelegate receives link->Handles it->Instatiates VC and presents it->Does things accordingly
In your handleIncomingDynamicLink() use NotificationCenter to post a notification containing the info. In your ViewController add an observer for that notification and define whatever you need to be done when the notification is received.
AppDelegate receives link->Handles it->Fires notification->VC listens notifications->Does things accordingly

Populate table view from different Swift file

I am having Search view Controller:
Refer This Image .
In this image you can see it has tableview, search_textfiled and advance search button. So if I put some text in search field and click on search icon it use to fire query and it is populating the result in the table. But when clicking on Advance search it use to open one popover, which is having some text_field.
Refer this image for Popover.
So after entering value in all fields I have to do search and fire query and populate table view present in Search view controller. So when clicking on search I am calling that full_text_search function which use to fire query and populate data in table view. So in popover controller creating instance of search controller and calling search function but it is showing thread exception.
func full_text_search(){
self.view.endEditing(true)
image_contain.removeAll()
datafiles.removeAll()
search_table.reloadData()
search_table.delegate = self
search_table.dataSource = self
let defaults = UserDefaults.standard
let username = defaults.string(forKey: "username")
let password = defaults.string(forKey: "password")
var check_icon_flag = 0
let loginData = String(format: "%#:%#", username!, password!).data(using: String.Encoding.utf8)!
let base64LoginData = loginData.base64EncodedString()
let serarchbar_text = search_text.text
print(serarchbar_text)
var new_String = "http://xx.xxxx.com:9090/dtm-rest/repositories/xxx/search?q='"
new_String.append(serarchbar_text!+"'&object-type=dm_document")
let again_new_String = new_String.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let url = URL(string: again_new_String!)
print(again_new_String)
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginData)", forHTTPHeaderField: "Authorization")
##After this use to fetch query result and at last i use refresh table data
So in above code it use to show thread exception in
search_table.reloadData
search_table.delegate = self
search_table.dataSource = self
If I call this function from search controller it is working fine. This function is defined in search controller only.But calling from popover controller it is giving exception.Below function shows calling full_text_search function from popover view controller.
#IBAction func Search(_ sender: Any) {
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
print("2sec")
let check_search = SearchViewController()
check_search.full_text_search()
}
removeAnimate()
}
What to do if I want to add some values to query using popover view and then do search.
How I am calling popover :
#IBAction func advance_search(_ sender: Any) {
let popOverVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "types_popover") as! TypesViewController
self.addChildViewController(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParentViewController: self)
}
instead of populating the tableView from another VC, use delegates to populate it in its own VC, read more about delegates
Here
so inside the popover VC, you pass the parameters you required for search and fire up a function inside the tableview VC, and perform the searching pretty simple
in your case the delegate protocol function should hold all the required search parameters

Refresh UITableViewController data

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
}
}

PrepareForSegue is too fast for my other functions, and variables aren't updating in time?

I have a FIRSTViewController that has a button. The button has an IBAction function, in which it calls CLGeocoder(). The button is also a segue to a SECONDViewController.
The CLGeocoder inside the IBAction updates two variables, and those variables are passed in the prepareForSegue function.
However, since CLGeocoder acts asynchronously, it is too slow to update the variables before the segue occurs, and thus, the incorrect variables are being passed to the SECONDViewController. How could I fix this?
Here's the code in my IBAction method:
geocoder.geocodeAddressString(userInputText!) { (placemarks, error) -> Void in
if let firstPlacemark = placemarks?[0] {
self.searchLatitude = "\(firstPlacemark.location!.coordinate.latitude)"
self.searchLongitude = "\(firstPlacemark.location!.coordinate.longitude)"
self.addressLoaded = true
} else {
//implement error pop up
print("error")
}
}
All the CLGeocoder instance methods accept a completionHandler parameter. Try to call the performSegue from it
EDIT: this is an example
#IBAction private func buttonPressed(sender: UIButton?) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(
"Your address string") { (let placeMarks, let error) in
if let error = error {
// handle the error
} else if let placemarks = placeMarks {
// Do whatever with the placemarks
self.performSegueWithIdentifier(
"Segue identifier",
sender: sender
)
}
}
}
Do not use segue. You can simply instantiate the SecondViewController manually like this:
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerIdentifier") as! SecondViewController
And then you can do:
geocoder.geocodeAddressString(userInputText!) { (placemarks, error) -> Void in
if let firstPlacemark = placemarks?[0] {
self.searchLatitude = "\(firstPlacemark.location!.coordinate.latitude)"
self.searchLongitude = "\(firstPlacemark.location!.coordinate.longitude)"
self.addressLoaded = true
//now present viewController
self.presentViewController(vc, animated: true, completion: nil)
} else {
//implement error pop up
print("error")
}
}
You must set the identifier of the viewController in order to instantiate it: in order to do this open your storyboard and select your viewController, then open the inspector and set the id.

Resources