I have to call a api and when the api is call I have to move on next controller and show a table with list of data but my problem is when I move to next controller the api call is not finished yet so table view is become empty . Is there any way so that I can reload table view after a specific time
You should have a completion handler of some sort when you do the API call. Make use of it!
You can either 1) show the table view after the API call has finished, or 2) show the table view first, then reload it once the API call has finished.
Pseudocode:
// 1)
showLoadingIndicator()
performAPICall(completion: { data, error in
if error != nil {
performSegue(withIdentifier: "show table view", sender: data)
// remember to pass the data in prepareForSegue
hideLoadingIndicator()
}
})
// or
// 2)
performSegue(withIdentifier: "show table view", sender: nil)
// in the table view controller,
performAPICall(completion: { data, error in
if error != nil {
self.data = data
self.tableView.reloadData()
}
})
you can do this by using DispatchTime.
let time = DispatchTime.now() + 5
DispatchQueue.main.asyncAfter(deadline: time) {
// reload your table view here
}
but for a good programming first, let your API call complete and then move to your next controller for that use completion handler.
Related
I have an array "numbers" which I call in my table view file to make the table view cells. When one is clicked it goes to a view controller which shows details about that cell, and in within that view controller is a delete button. How would I delete the item from the array, and reload the data in the table view controller?
So I set it up that when the delete button is clicked it runs an exit code, and deletes the item from the array, and reloads the data. I tried testing it but it never seems to execute.
This is in the detailViewController where it runs the exit function and runs the protocol to delete the item from the array
func deleteNumber() {
self.delegate?.unwind()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // Change `2.0` to the desired number of seconds.
self.performSegue(withIdentifier: "unwindToNumbersList2WithSender", sender: self)
}
}
Then in the table view controller it runs this:
func unwind() {
numbers.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
saveNumbers()
}
I also tried using:
func unwind() {
numbers.remove(at: indexPath.row)
tableView.reloadData()
saveNumbers()
}
So I wanted it to run an either delete the row or delete the item from the array, and reload the data, but neither of those ran. Is there a way I can delete it from the detail view controlle?
You can use NotificationCenter for doing this,
First you need to add notification in UIViewController like this
let DELETE_DATA = "DELETE_DATA".notificationName()
NotificationCenter.default.addObserver(self, selector: #selector(self.delete_data), name: DELETE_DATA, object: nil)
Make a function named delete_data
#objc func delete_data(_ notification : NSNotification){
let userData = notification.userInfo as? [String:Any] ?? [String:Any]()
// here you can get your wanted index to be deleted.
}
From details view controller you need to POST this notification with index you want to delete
let obj = ["index":`your index to be deleted`]
NotificationCenter.default.post(name: DELETE_DATA, object: nil, userInfo: obj)
I would like to only execute a segue if I get a certain response from the server. In swift, how can I wait until I get a response to continue?
Bottom line, you don't "wait" for the response, but rather simply specify what you want to happen when the response comes in. For example, if you want to perform a segue when some network request is done, you should employ the completion handler pattern.
The issue here is that you're probably accustomed to just hooking your UI control to a segue in Interface Builder. In our case, we don't want to do that, but rather we want to perform the network request, and then have its completion handler invoke the segue programmatically. So, we have to create a segue that can be performed programmatically and then hook your button up to an #IBAction that performs the network request and, if appropriate, performs the segue programmatically. But, note, there should be no segue hooked up to the button directly. We'll do that programmatically.
For example:
Define the segue to be between the two view controllers by control-dragging from the view controller icon in the bar above the first scene to the second scene:
Give that segue a storyboard identifier by selecting the segue and going to the "Attributes Inspector" tab:
Hook up the button (or whatever is going to trigger this segue) to an #IBAction.
Write an #IBAction that performs network request and, upon completion, programmatically invokes that segue:
#IBAction func didTapButton(_ sender: Any) {
let request = URLRequest(...). // prepare request however your app requires
let waitingView = showWaitingView() // present something so that the user knows some network request is in progress
// perform network request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// regardless of how we exit this, now that request is done, let's
// make sure to remove visual indication that network request was underway
defer {
DispatchQueue.main.async {
waitingView.removeFromSuperview()
}
}
// make sure there wasn't an error; you'll undoubtedly have additional
// criteria to apply here, but this is a start
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
// parse and process the response however is appropriate in your case, e.g., if JSON:
//
// guard let responseObject = try? JSONSerialization.jsonObject(with data) else {
// // handle parsing error here
// return
// }
//
// // do whatever you want with the parsed JSON here
// do something with response
DispatchQueue.main.async {
performSegue(withIdentifier: "SegueToSceneTwo", sender: self)
}
}
task.resume()
}
/// Show some view so user knows network request is underway
///
/// You can do whatever you want here, but I'll blur the view and add `UIActivityIndicatorView`.
private func showWaitingView() -> UIView {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
effectView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(effectView)
NSLayoutConstraint.activateConstraints([
effectView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
effectView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
effectView.topAnchor.constraintEqualToAnchor(view.topAnchor),
effectView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
])
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
effectView.addSubview(spinner)
spinner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
spinner.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor),
spinner.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
])
spinner.startAnimating()
return effectView
}
I have tableview that I add cells to via an "add button" and a second view controllers. I currently have the following code setup for my tableview and it sorts the cells alphabetically whenever the app loads. My issue is that when I add a new recipe it does not re-sort automatically. What is the best way to do this? Should my reloadData() be somewhere else? Or perhaps put it in the Segue from view controller back to table view?
override func viewDidLoad() {
configureView()
if let savedRecipes = loadRecipes() {
recipes2 += savedRecipes
recipes2 = recipes2.sort({current, next in return current.name < next.name})
recipeList.reloadData()
}
}
You could add reloadData() to the viewWillAppear() or viewDidAppear() methods in your table view controller. That way, whenever you return to the view, it should be showing the most up to date data. For example:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
If the recipe is a class on its own you can do it like this:
if let savedRecipes = loadRecipes() {
recipes2 += savedRecipes
recipes2.sortUsingComparator {
let r1 = $0 as! Recipe
let r2 = $1 as! Recipe
if r1.title < r2.title {
return .OrderedAscending
}
if r1.title == r2.title {
return .OrderedSame
}
return .OrderedDescending
}
self.tableView.reloadData()
}
This will sort your recipes in ascending order.
You can use the ModelAssistant framework to show recipe objects in tableview. This is a library to mediate between tableView and model. you can set your sort method at the begin of viewcontroller then each new recipe which you add to your model, the model automatically will be sort and also your tableview will be updated too.
I am working on an app where it starts out at a tableViewController which loads and displays data stored in a Realm database. I have it so I can create a new entry in my Realm database in a separate scene and the save button unwind segues back to the initial tableView.
I current have it so the tableViewController will reload the tableView on pull down (something I Learned here, second answer down) but I would be better if the tableView would reload its self automatically upon unwind, displaying all the data in my database, including my new entry. Could someone please direct me to a tutorial that will teach me how this is done.
Additional info: My app is embedded in a navigation controller. The save button is located the bottom tool bar.
You can use NSNotification for that.
First of all in your tableViewController add this in your viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refreshTable:", name: "refresh", object: nil)
}
And this will call one method from your class:
func refreshTable(notification: NSNotification) {
println("Received Notification")
tableView.reloadData() //reload your tableview here
}
So add this method too.
Now in your next view controller where you add new data into data base add this in you unWindSegue function:
NSNotificationCenter.defaultCenter().postNotificationName("refresh", object: nil, userInfo: nil)
Hope it will help
Try reloading your tabledata in viewWillAppear in initial (tableview)controller.
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
Or call again the function through which you are loading your data from Realm like
override func viewWillAppear(animated: Bool) {
getMyData() //or whatever your function name is
}
If you are using storyboard unwind segue, try using
func unwindSegue(segue:UIStoryboardSegue) {
if segue.identifier == "identifier" {
getData()
self.tableView.reloaddata()
}
}
I am trying to pass the Parse Class "Conversation" objectId after it's created to another View Controller. When I check the data does not pass to the variable I point to.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object(s) to the new view controller.
let selectedIndex = self.tableView.indexPathForSelectedRow()?.row
let destinationVC = segue.destinationViewController as ConversationViewController
//Save Conversation Data
var convo = PFObject(className: "Conversation")
convo.saveInBackgroundWithBlock{
(success: Bool!, error: NSError!) -> Void in
if (success != nil) {
//Save Selected Person
participant["conversationId"] = convo.objectId as String!
participant.saveInBackground()
}
else{
NSLog("%#", error)
}
}
//Trying to Pass convo.objectId
destinationVC.newConversationId = convo.objectId as String!
}
The problem with the code is that convo.objectId is not set at the point where you use it. It gets set in the completion block of saveInBackgroundWithBlock:, but that block runs after the code that appears underneath it.
So what to do? If the next vc needs the convo object to be saved before it runs, then the correct pattern is to run the segue after the save. Find the point in your code where you are initiating the segue and replace it with the convo.saveInBackgroundWithBlock. Then do the performSegue from within that block.
Edit - Here's how doing this looks in objective-C. In either language, in order to do this, you must initiate the segue from code. Say you have the segue painted from a table view cell in IB (or storyboard) to the next view controller. Delete that segue, and control-drag a new one starting from the view controller which contains the table. (Select the view controller in IB and drag from there. Then, using the attributes inspector, give that segue and identifier, say, "ConvoSegue").
// since a table selection that starts the action, implement the selection delegate method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// we decide here that the convo object must be saved, and
// a segue should happen to another vc that needs the convo object:
var convo = PFObject(className: "Conversation")
convo saveInBackgroundWithBlock:^(BOOL success, NSError *error) {
if (!error) {
// now that convo is saved, we can start the segue
[self performSegueWithIdentifier:#"ConvoSegue" sender:convo];
} else {
// don't segue, stay here and deal with the error
}
}];
}
Notice in the above, we passed convo as the sender of the segue. This allows access to it in prepareForSegue. You can skip that if convo is a property of this view controller.
Now prepare for segue looks like yours, except without the asynch save...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// we don't need the selected row from the table view, because we have
// the convo object as the sender
// let selectedIndex = self.tableView.indexPathForSelectedRow()?.row
let destinationVC = segue.destinationViewController as ConversationViewController
// sorry, back to objective-c here:
PFObject *convo = (PFObject)AnyObject; // need a cast to use properly in objective-c
// deleted your save logic. just pass convo's id
//Trying to Pass convo.objectId
destinationVC.newConversationId = convo.objectId as String!
}