I've Looked around SO, and read a good number of blogs to find out how to accomplish my goal. The most understandable post I came across was the one here Pass data back to previous viewcontroller. I'm sure my understanding is mixed up but what I am trying to accomplish is removing an annotation from the map when I swipe a cell in my second view.
Removing the annotation from the CoreData is not a problem, removing the pin when I click the rightCallOut is not an issue either. The problem comes when I want to remove the annotation from the map in VC1 from an action in VC2. Where am I misunderstanding this simple process and how do I accomplish it?
FirstViewController
import UIKit
class ViewController: UIViewController, PinRemoverDelegate {
func removePin() {
mapView.removeAnnotation(selectedAnnotation)
}
}
SecondViewController
import UIKit
protocol PinRemoverDelegate: class {
func removePin()
}
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
weak var delegate: PinRemoverDelegate? = nil
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let place = storedLocations[indexPath.row]
context.delete(place)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
// Attempt To remove The Pin
delegate?.removePin()
}
tableView.reloadData()
}
}
The misunderstanding is only how to remove the pin. Calling a removePin function is wrong. Simply reload the CoreData, since the pin has already been removed from the CoreData.
Related
I have a viewController with a tableView, and another viewController that contains data that I'm trying to pass to the tableView. To add entries to the tableView I used segues, but the problem with segues is that they don't update the tableView permanently. They merely create an instance of the ViewController and add the entry there, but the original object remains unchanged. Both ViewControllers are part of a tab bar controller. What I want is to update the table permanently. Meaning, I want to be able to navigate to the viewController where the table is defined and see that's an entry has been added. Here's the code for the viewController with the tableView:
import UIKit
class FavoritesViewController: UIViewController {
public var shops = [
"hello world",
"hello world",
"hello world"
]
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
table.reloadData()
}
}
extension FavoritesViewController: UITableViewDelegate, UITableViewDataSource{
func add(_ shopName: String) {
print(shopName)
shops.append(shopName)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shops.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath)
cell.textLabel?.text = shops[indexPath.row]
return cell
}
// define the action. In this case "delete"
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
// do the actual deleting
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.beginUpdates()
shops.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
}
And here's how I'm trying to update (in the case adding an entry) the tableView in the other viewController:
class MainViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate{
public var shopName:String?
// there are also many other vars, but they're irrelevant
public var favoritesDestinationVC = FavoritesViewController()
// prepares the data for the segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToFavs" {
favoritesDestinationVC = segue.destination as! FavoritesViewController
if let newShopName = shopName {
favoritesDestinationVC.shops.append(newShopName)
}
}
}
}
However, when I add the entry the to the table, the segue creates an instance of FavoritesViewController, adds that entry there, and then displays it in a popup window like this:
But when I dismiss this window the changes disappear (the tableView remains the same; 3 times "Hello World").
I want the changes to be saved in the tableView after dismissing this window. Any idea on how to do that? On how to make those changes permanent?
By your explanation, it looks like. Segue is defined as presenting FavoritesViewController modally. and this behavior is expected. By your implementation. Since you are not updating the view controller object which is part of the tab bar controller.
To make the changes in the controller in tab bar controller, Either you access that object using tabcontroller.viewcontrollers. Communicate using another way like a delegate or notification.
Edit:
It totally depends on where you want to access FavoritesViewController.
If you want to access from TabBarViewController (based on your view hierarchy it may change. be careful about index and typecasting):
let favVC = self.viewControllers?[1] as! FavoritesViewController
favVC.shops.append(newShopName)
If you want to access from a view controller which is part of same tab bar controller:
var favVC = self.tabBarController?.viewControllers?[1] as! FavoritesViewController
favVC.shops.append(newShopName)
Note: Viewcontrollers's index depends on viewcontroller order in your tab bar controller. And type casting depends upon your view hierarchy.
I am puzzled by the behavior of tableView if you leave their view and come back.
I have a screen with one tableView in it that works when I first enter the view. Adding, removing, and updating table cells work. However, when I press a button to segue into the next view and immediately come back, the tableView no longer works. The code that is supposed to execute ( tableView.reload() and all the associated methods) run as they should. However, the screen does not get updated even though internally the arrays get updated, and reload gets ran and executes the code that should update the screen( that is, tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell runs jus fine).
What am I missing? Does tableView require any special treatment if I leave the view and come back to ti?
Thanks.
Edit:
The code for the class where the tableView is something like:
class DebugViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var table: UITableView!
var array = [M]()
override func viewDidLoad() {
super.viewDidLoad()
self.searchbar.delegate = self
self.table.delegate = self
self.table.dataSource = self
search_view = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Search_view") as? SomeViewController
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cellManage") as? TableCellManage else { return UITableViewCell() }
let idx = indexPath.row
let value = array[idx]
cell.lbl_time.text = value.month
cell.lbl_background.text = value.color
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.array.count
}
#IBAction func tapped_addsongs(_ sender: Any) {
self.present( search_view, animated: true, completion: nil)
}
}
Where is the tableView.reload() part? The issue might be generated because you're trying to update the table in a background thread. All UI Updates must be done in main thread:
func someFunc() {
...
DispatchQueue.main.async {
tableView.reload()
}
...
}
After looking at pretty pictures of the life cycle of apps and googling I found the issue and the solution.
The problem was that I had listeners set up to update my table view in a troublesome way. Specifically, I was using the viewDidAppear/viewDidDisappear to bring up and down the listeners, and there was some conflict in the code that managed the state because of this.
Instead, I now bring up the listeners on viewDidLoad. They stay active, regardless of how many views are pushed (within reason, but I only push one), and update my tableview so that when I come back to that view everything is already updated. I don't even see the updates happening, they happen before I get to my view. As for detaching the listeners there is a handy-dandy function I did not know about until 5 minutes ago: deinit. This is the equivalent of destructor in Swift, so I detach my listener when my class object for this view is released from memory.
That solves my issue...and increases performance and I no longer have dangling connections for not managing the listeners well. So a win-win-win.
Thank you all for trying to help! I hope this helps other folks.
I have a Social Network Feed in form UItableView which has a cell. Now each cell has an image that animates when an even is triggered. Now, This event is in form of a string, will be triggered at every cell. the options for the event are defined in another class(of type NSObject).
My issue:
I constructed a protocol delegate method in table view, which will be called whenever the event is triggered for each cell. Then, I define this function in UITableViewCell Class, since my the image will be animating on that.
All is working well but I am unable to figure out how to assign the delegate of TableView class to cell class. What I mean is, how can I use UITableView.delegate = self in cellView class. I have tried using a static variable, but it doesn't work.
I have been playing around the protocols for a while now but really unable to figure out a solution to this.
I hope I am clear. If not, I will provide with an example in the comments. I am sorry, This is a confidential project and I cant reveal all details.
If I understand you correctly, you are trying to make each of your cells conform to a protocol that belongs to their UITableView? If this is the case then this cannot be done. The Delegation design pattern is a one to one relationship, i.e only one of your UITableViewCells would be able to conform to the UITableView's delegate.
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.
Quote from the Apple Docs
I would suggest that your UITableViewCell should call a block (Objective-C) or a closure (Swift) whenever your specified event is triggered to achieve what you are looking for. Set up this closure in your tableView:cellForRowAtIndexPath function.
EXAMPLE
TableViewController
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCellID", for: indexPath) as! MyTableViewCell
cell.eventClosure = {
//Do something once the event has been triggered.
}
return cell
}
TableViewCell
func eventTriggered()
{
//Call the closure now we have a triggered event.
eventClosure()
}
If I correctly understood your question, maybe this could help:
class ViewController: UIViewController, YourCustomTableDelegate {
#IBOutlet weak var tableView: YourCustomTableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.customTableDelegate = self
}
// table delegate method
func shouldAnimateCell(at indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.animate(...)
}
}
}
Try something like this:
Define your delegate protocol:
protocol CustomCellDelegate: class {
func animationStarted()
func animationFinished()
}
Define your CustomCell. Extremely important to define a weak delegate reference, so your classes won't retain each other.
class CustomCell: UITableViewCell {
// Don't unwrap in case the cell is enqueued!
weak var delegate: CustomCellDelegate?
/* Some initialization of the cell */
func performAnimation() {
delegate?.animationStarted()
UIView.animate(withDuration: 0.5, animations: {
/* Do some cool animation */
}) { finished in
self.delegate?.animationFinished()
}
}
}
Define your view controller. assign delegate inside tableView:cellForRowAt.
class ViewController: UITableViewDelegate, UITableViewDataSource {
/* Some view controller customization */
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: CustomCell.self)) as? CustomCell
cell.delegate = self
cell.performAnimation()
return cell
}
}
I have a class and extension Swift file. After adding a delegate that I declared in another file to the class, Xcode shows this error
Declaration is only valid at file scope
at the extension line. I don't know what the problem is.
Can anyone help me to fix it?
class ListViewController: UIViewController, AddItemViewControllerDelegate {...}
extension ListViewController: UITableViewDataSource{
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
performSegueWithIdentifier("ShowDetail", sender: indexPath)
}
}
The error is somewhere in your ... — that error means that your ListViewController class didn't get closed, so the extension is being interpreted as nested inside, like this:
class ListViewController {
...
extension ListViewController {
}
}
Find the missing closing brace and you should solve the problem.
The extension must be at the root level - don't embed them into a class or whatever.
Make sure that the extension is declared at the end of your main class and after the last curly braces "}"
class ListViewController: UIViewController, AddItemViewControllerDelegate {
//Make sure that everything is clean here!
}
extension ListViewController: UITableViewDataSource{
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
performSegueWithIdentifier("ShowDetail", sender: indexPath)
}
}
Make sure your class and extension are seperated.
class ViewController: UIViewController {}
extension name: type {}
I had my extension calls at the bottom of my file and put them at the top and that fixed it for me. At the bottom, they were outside the class scope so I was a little stumped and just tried this.
The extension should be out of the Class.
class ListViewController: UIViewController, AddItemViewControllerDelegate {...}
// Code...
}
extension ListViewController: UITableViewDataSource{
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
performSegueWithIdentifier("ShowDetail", sender: indexPath)
}
When I use the UITableViewController to create a tableView, you get a lot of override functions, but when you use a regular UIViewController you get an error when using these same override functions and you are forced to change them to regular functions. I believe this is why my core data won't load into my cells, and tried to use the viewDidLoad function to get my data to load.
I know my code should work since all I'm trying to do is transfer all my code from a UITableViewController to a UIViewController, and my code worked in my UITableViewController.
My effort so far:
override func viewDidLoad() {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let CellID:NSString = "CELL"
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
if let ip = indexPath as Optional {
var data:NSManagedObject = myList[ip.row] as NSManagedObject
cell.textLabel!.text = data.valueForKeyPath("username") as? String
}
return cell
}
}
Are the override functions the reason my cells are empty, or are there other aspects when using a regular UIViewController to show a tableView?
Any suggestions would be appreciated.
(1) You have to add UITableViewDelegate to the class in order to access the delegate methods, ex:
class ViewController: UIViewController, UITableViewDelegate {
After adding the UITableViewDelegate to the class, your UITableView delegate functions should auto-complete.
Also, make sure to set the UITableView's delegate to self in order to have the delegate methods populate the table.
(2) Right now, your cellForRowAtIndexPath method is within your viewDidLoad. Move it so it's not contained within any other method.