Delete UITableVIewCell from another view controller file in Xcode - swift

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)

Related

how to reload table view after a specific time

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.

Swift split view controller, issue with "repeat" Notification on first load

Full title: Swift 4.2 using split view controller, issue with "repeat" Notification on first load that disrupts ability to programmatically select a "new" object in the Master Table View.
Background
I am working with a split view controller with master and detail table views - master is a plain dynamic table view that displays a list of entities to the user and detail is a grouped dynamic table view that displays the attributes and relationship values for the user-selected entity in the master.
I have implemented Core Data.
The master table view shows a list of entities. The data source for the master table view is a fetched results controller.
The detail table view shows the attributes and associated relationship values of the currently selected row (entity) in the master table view. The data source for the detail table view is the entity relating to the currently selected row in the master table view, which it is passed using a "Show Detail" segue.
On devices with larger screens where splitViewController.isCollapsed == false, the master and detail table views are both active on screen, per the image below.
Fairly standard arrangement for a data driven app...?
Logic Flow
When a user updates an existing entity (in the case of the screenshot example, an existing "Event"), they click the the Save button in the navigation bar of the detail table view.
Because the entity already exists, in the Master Table View Controller's controller(_:didChange:at:for:newIndexPath) Fetched Results Controller Delegate method:
under case .update, implement tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.none), which triggers tableView(_willDisplay:forRowAt:) which in turn updates the formatting of the selected Table View Cell; AND
under case .insert, case .update and case .move, I set a temporary value for the current IndexPath indexPathForManagedObjectChanged
The Fetched Results Controller Delegate method:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
indexPathForManagedObjectChanged = newIndexPath
print("___controllerDidChangeObject: INSERTED OBJECT")
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
print("___controllerDidChangeObject: DELETED OBJECT")
case .update:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! PT_Events)
indexPathForManagedObjectChanged = indexPath
tableView.reloadRows(at: [indexPath!], with: UITableView.RowAnimation.none)
print("___controllerDidChangeObject: UPDATED OBJECT")
case .move:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! PT_Events)
indexPathForManagedObjectChanged = newIndexPath
tableView.moveRow(at: indexPath!, to: newIndexPath!)
print("___controllerDidChangeObject: MOVED OBJECT")
}
}
Then, in the Master Table View Controller's controllerDidChangeContent(_:) Fetched Results Controller Delegate method, I execute the following code...
guard let indexPathOfRowToSelect: IndexPath = indexPathForManagedObjectChanged else {
return
}
indexPathForManagedObjectChanged = nil
tableView.selectRow(at: indexPathOfRowToSelect, animated: false, scrollPosition: UITableViewScrollPosition.none)
This ensures that after inserts, updates and moves, the row that corresponds to the data in the Detail Table View is selected.
I use a Notification of type UserDefaults.didChangeNotification.
The observer is added in the Master Table View Controller's (EDIT was viewDidLoad, but now) viewWillAppear(_:) method. The observer is removed in the Master Table View Controller's viewWillDisappear(_:) method.
If the user changes one of the settings the Notification observer calls the following function...
#objc
func userDefaultSettingsDidChange(_ notification: Notification) {
if (notification.object as? UserDefaults) != nil {
NSFetchedResultsController<NSFetchRequestResult>.deleteCache(withName: classCacheName)
fetchedResultsController = nil
tableView.reloadData()
}
}
Problem
On first run of the Master Table View Controller there is a Notification that calls the userDefaultSettingsDidChange method, but it calls this AFTER the first run through of the Fetched Results Controller Delegate methods in response to the first user interaction. This despite no change to the user defaults.
The issue occurs on simulator and on device.
I've added a stack of print()s (removed for ease of reading code) to track what is happening in what order.
The console logs the userDefaultSettingsDidChange function as executing AFTER the Fetched Results Controller Delegate methods have completed - BUT only on first run.
Because of this, the call to reloadData wipes out my previous call to selectRow.
Solution
I can wrap my call to the selectRow(at:animated:scrollPosition:) instance method in the Master Table View Controller's controllerDidChangeContent(_:) Fetched Results Controller Delegate method to include a slight delay...
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
guard let indexPathOfRowToSelect: IndexPath = indexPathForManagedObjectChanged else {
return
}
indexPathForManagedObjectChanged = nil
let when = DispatchTime.now() + 0.20
DispatchQueue.main.asyncAfter(deadline: when) {
self.tableView.selectRow(at: indexPathOfRowToSelect, animated: false, scrollPosition: UITableView.ScrollPosition.none)
}
}
This works!
But - I'm not satisfied.
I've done a lot of reading but for some reason, cannot seem to understand what is happening in NotificationCenter that is causing this ghost update to user settings despite no update.
Could someone please explain why I'm seeing this delayed "update" to user settings that is wreaking havoc on only the first user interaction in my UI?

Re-sort TableView Alphabetically

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.

How do I display the data fetched from called view controller into a dynamic tableviewcell of the calling view controller while using unwind segue.?

I have dynamic tableview, wherein one of the cell (duration) when tapped opens another view controller which is a list of duration viz (30 min, 1 hour, 2 hours and so fort). One of the durations when selected should display the selected duration in the first view controller. I am able to pass the data back to first view controller using unwind segue but unable to display the passed value. DOn't know whats missing.
I am displaying the code below:
FIRST VIEW CONTROLLER (CALLING)
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
var cell = tableView.dequeueReusableCellWithIdentifier("durationCell") as! durationTableViewCell
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController,
selectedDuration = durationTableViewController.selectedDuration {
cell.meetingDurationCell.text = selectedDuration
duration = selectedDuration
}
SECOND VIEW CONTROLLER (CALLED)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SaveSelectedDuration" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
if let index = indexPath?.row {
selectedDuration = durationList[index]
}
}
}
}
tableView.dequeueReusableCellWithIdentifier should only be called within tableView:cellForRowAtIndexPath:. It has no use outside this context.
The easiest fix is to just reload the table once you have stored the selected duration:
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController {
selectedDuration = durationTableViewController.selectedDuration
tableView.reloadData()
}
}
Note that this assumes you only need one selectedDuration for your whole table, rather than one per row. If you need one per row, I assume you have them stored in an array somewhere, so it is that array that should be updated instead before the reloadData.

Automatically Reload TableViewController On Rewind

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