Removing Data on Maps with different view controllers - swift

I am really struggling on an issue that I think is rather interesting and quite difficult. My application lets the user create annotation locations within a Mapview. They also have the option to edit and delete these locations in another modal view controller.
The issue I am facing is that when the user presses delete, which removes the location from firebase, the annotation is still displayed upon the map. I cannot reload my annotation data within the view did appear as this does not suit my application. I cant have my annotations being reloaded every time I bring up the Mapview.
I need to figure out a way to implement an annotation reload when the delete button is pressed. However, as this happens within my delete view controller (which does not contain the mapView) I cannot use the reload function. Is there a way to connect view controllers so that I can apply the reload function when delete is pressed?
Updated Code **
This is my map view controller:
class ViewController: UIViewController, SideBarDelegate, MGLMapViewDelegate, DeleteVCDelegate {
let EditSaveSpotController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "EditVC") as! EditSaveSpotViewController
override func viewDidLoad() {
super.viewDidLoad()
EditSaveSpotController.delegate = self
}
func wholeRefresh() {
let uid = FIRAuth.auth()!.currentUser!.uid
let userLocationsRef = FIRDatabase.database().reference(withPath: "users/\(uid)/personalLocations")
userLocationsRef.observe(.value, with: { snapshot in
for item in snapshot.children {
guard let snapshot = item as? FIRDataSnapshot else { continue }
let newSkatepark = Skatepark(snapshot: snapshot)
self.skateparks.append(newSkatepark)
self.addAnnotation(park: newSkatepark)
}
})
if let annotations = mapView.annotations {
mapView.removeAnnotations(annotations)
}
for item in skateparks {
self.addAnnotation(park: item)
}
}
This is my delete view controller:
import UIKit
import Firebase
protocol DeleteVCDelegate {
func wholeRefresh()
}
class EditSaveSpotViewController: UIViewController {
var delegate: DeleteVCDelegate?
#IBAction func deleteSkateSpot(_ sender: Any) {
ref = FIRDatabase.database().reference(withPath: "users").child(Api.User.CURRENT_USER!.uid).child("personalLocations/\(parkId!)")
ref.observe(.value, with: { (snapshot) in
self.ref.setValue(nil)
self.dismiss(animated: true, completion: nil)
self.delegate?.wholeRefresh()
// self.delegate?.mainRefresh()
print("CheckWorking")
})
}
}

This is very high level and I did not have a chance to verify but it should be enough to get you going:
Modal Delete View
protocol DeleteVCDelegate {
func mainRefresh()
}
class DeleteVC: UIViewController {
var delegate: DeleteVCDelegate?
//your delete code
#IBAction func deleteSkateSpot(_ sender: Any) {
ref = FIRDatabase.database().reference(withPath: "users").child(Api.User.CURRENT_USER!.uid).child("personalLocations/\(parkId!)")
ref.observe(.value, with: { (snapshot) in
self.ref.setValue(nil)
//call to delegate
self.delegate?.mainRefresh()
})
}
}
MapView Class (implement DeleteVCDelegate)
class mapVC: MKMapViewDelegate, DeleteVCDelegate{
//when you present your DeleteVC set its delegate to the map view
let vc=(self.storyboard?.instantiateViewController(withIdentifier: "deleteVC"))! as! DeleteVC
//set the delegate
vc.delegate=self
//present deleteVC
self.present(vc, animated: true, completion:nil)
//implement delegate method of DeleteVC
func mainRefresh(){
//dismiss modal
self.dismiss(animated: true) {
//update view
self.loadLocations()
self.annotationRefresh()
}
}
}

Related

Swift Call Particular function from another class for Present ViewController

My Scenario, I am trying to call a ViewController class file particular function from another one class file. Here, I am getting below warning and ViewController not presenting.
My Code Below ViewControllerA
func previewview(){ // Inside ViewControllerA
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let fileViewController = storyboard.instantiateViewController(withIdentifier: "fileviewcontroller")
let navController = UINavigationController(rootViewController: fileViewController)
self.present(navController, animated: true, completion: nil)
} }
Below code In Another Class File
import UIKit
class FileController {
//MARK:- Call a file preview
ViewControllerA().self.previewview()
}
Warning: Attempt to present on
whose view is not in the window
hierarchy!
Try this
Viewcontroller().FuncName()
Example:
LoginViewController().checkLoginValidation()
Use swift's Delegate Protocol to call a function in a class from different class.
Here is a link for understanding how delegate protocol works.
https://medium.com/#nimjea/delegation-pattern-in-swift-4-2-f6aca61f4bf5
Here is how you can proceed. This is just an example. You can take the idea and implement in you code as per your requirement.
Instead of creating a separate ViewController, create a ViewModel that handles the implementation of saving the file, i.e.
class SaveOptionsViewModel {
func save(file: String, handler: (()->())?) { //add parameters to save a file as per requirement
//save the file here...
handler?()
}
}
Now, in the controller that contains multiple save options, create a property of type SaveOptionsViewModel.
And present PreviewVC from SaveOptionsVC in the handler once the file is saved using the SaveOptionsViewModel after tapping the saveButton.
class SaveOptionsVC: UIViewController {
let viewModel = SaveOptionsViewModel()
#IBAction func onTapSaveButton(_ sender: UIButton) {
self.viewModel.save(file: "") {
if let previewVC = self.storyboard?.instantiateViewController(withIdentifier: "PreviewVC") {
self.present(previewVC, animated: true, completion: nil)
}
}
}
}
Add the custom implementation of PreviewVC as per your requirement.
class PreviewVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
//add the code...
}

Can not remove child coordinator because transitionCoordinator is nil in navigationController delegate method

Brief :
I have implemented Soroush's Coordinators architecture. Everything works fine except the removing part which is needed to remove previous(child) coordinators.
Scenario :
I have two ViewController named HomeViewController and MyGroupsViewController. Each has its own coordinator named HomeCoordinator and MyGroupsCoordinator respectively.
User taps a button on HomeViewController which triggers gotoMyGroupsTapped function and gets the user to MyGroupsViewController, Then the user taps on another button on MyGroupsViewController which get the user back to HomeViewController by triggering gotoHomePage().
Pretty simple! : HomeVC -> MyGroupsVC -> HomeVC
But the Problem is :
navigationController.transitionCoordinator? is nil in func navigationController(..., didShow viewController: UIViewController...) in both coordinators and I can not remove child coordinators in each transition.
Is it correct to set navigationController.delegate = self in start() func of both coordinators?
Should I use navigationController?.popViewController(animated: false ) in my backToHomePage() func? because Paul Hudson has only used pushViewController.
My Codes [Simplified Versions]:
HomeCoordinator.swift
import Foundation
import UIKit
class HomeCoordinator: NSObject,Coordinator,UINavigationControllerDelegate {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
weak var parentCoordinator : Coordinator?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// Transition here is nil
print(" Transition : ",navigationController.transitionCoordinator)
guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else {
print("Unknown fromViewController!")
return
}
// Removing a child coordinator
}
func gotoMyGroups (){
let groupsCoordinator = GroupsCoordinator(navigationController: navigationController)
childCoordinators.append(groupsCoordinator)
groupsCoordinator.parentCoordinator = self
groupsCoordinator.start()
}
func start() {
let vc = HomeViewController.instantiate()
vc.coordinator = self
navigationController.delegate = self
navigationController.pushViewController(vc, animated: false)
navigationController.setNavigationBarHidden(true, animated: false)
}
}
MyGroupsCoordinator.swift
import Foundation
import UIKit
class MyGroupsCoordinator: NSObject,Coordinator,UINavigationControllerDelegate {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
weak var parentCoordinator : Coordinator?
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// Transition here is nil
print(" Transition : ",navigationController.transitionCoordinator)
guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else {
print("Unknown fromViewController!")
return
}
// Removing a child coordinator
}
func start() {
let vc = MyGroupViewController.instantiate()
vc.coordinator = self
navigationController.delegate = self
navigationController.pushViewController(vc, animated: false)
navigationController.setNavigationBarHidden(true, animated: false)
}
}
MyGroupViewController.magik
class MyGroupViewController : UIViewControllerWithCoordinator,UITextFieldDelegate,Storyboarded{
#IBAction func gotoHomePage(_ sender: Any) {
if let coord = coordinator as? GroupsCoordinator {
coord.parentCoordinator?.start()
}
}
}
HomeViewController.swift
class HomeViewController: UIViewControllerWithCoordinator,Storyboarded {
#IBAction func gotoMyGroupsTapped(_ sender: Any) {
guard let acoordinator = coordinator as? HomeCoordinator else {
return
}
acoordinator.gotoMyGroups()
}
It looks to me there is a confusion around Coordinator pattern usage here.
From your expected flow HomeVC -> MyGroupsVC -> HomeVC, if you mean in the sense level1 -> level2 -> level3, then GroupsCoordinator should create a new HomeCoordinator instance with its own new HomeVC.
So instead of your previous code
class MyGroupViewController ... {
#IBAction func gotoHomePage(_ sender: Any) {
if let coord = coordinator as? GroupsCoordinator {
coord.parentCoordinator?.start()
}
}
}
I would change it to
class MyGroupViewController ... {
#IBAction func gotoHomePage(_ sender: Any) {
if let coord = coordinator as? GroupsCoordinator {
coord.goToHome()
}
}
}
class MyGroupsCoordinator ... {
func goToHome() {
let homeCoordinator = HomeCoordinator(navigationController: navigationController)
childCoordinators.append(homeCoordinator)
groupsCoordinator.parentCoordinator = self
groupsCoordinator.start()
}
}
This will allow you to create a brand new page as you describe there HomeVC -> MyGroupsVC -> HomeVC.
However, if you meant in this approach level1 -> level2 -> (back) level1, then you'll need to terminate MyGroupsCoordinator and remove from the parent while navigating back.
As you noticed, to do so, you'll need to use UINavigationControllerDelegate to be able to be notified when the user navigate back (either pop in code, or with classic back button).
One solution I found is to use a Router to handle all this navigation when a UIViewController is removed from it to also notify via closures the right coordinator to be removed. You can read more about it here.
Hope it helps

Swift: pass data to first child of NavigationController instantiate with storyboardID

I want to pass data to the first viewController which is embeded in navigationController.
To access this navigation controller it has a storyBoardID, I arrive at instantiate navigationController but I can not pass him data,
Here is my code:
extension UINavigationController {
func dismissAndPresentNavigationController(from storyboard: UIStoryboard?, identifier: String) {
guard let navigationController = storyboard?.instantiateViewController(withIdentifier: identifier) as? UINavigationController else { return }
print("OK")
if let nav = navigationController.navigationController?.viewControllers.first as? ChatBotViewController{
print("OK2")
}
self.dismiss(animated: false, completion: nil)
self.present(navigationController, animated: true, completion: nil)
}
}
The identifier that I put in parameter is the storyBoardID of the navigation controller.
How to transmit data to the first controller of navigationcontroller?
SOLUTION:
extension UINavigationController {
func dismissAndPresentNavigationController(from storyboard: UIStoryboard?, identifier: String, with fittoBottle: FittoBottle) {
guard let navigationController = storyboard?.instantiateViewController(withIdentifier: identifier) as? UINavigationController else { return }
if let nav = navigationController.viewControllers.first as? ChatBotViewController{
nav.fittoBottle = fittoBottle
}
self.dismiss(animated: false, completion: nil)
self.present(navigationController, animated: true, completion: nil)
}
After instantiating the navigation controller from the storyboard, you will be able to access the root view controller via navigationController.viewControllers.first.
guard let navigationController = storyboard?.instantiateViewController(withIdentifier: identifier) as? UINavigationController else { return }
if let chatBotViewController = navigationController.viewControllers.first as? ChatBotViewController {
chatBotViewController.fittoBottle = fittoBottle
}
self.dismiss(animated: false, completion: nil)
self.present(navigationController, animated: true, completion: nil)
To communicate between view controllers in an iOS app, the best way is using protocol (delegate) or Notification. In you case, extending an UINavigationController doesn't sound a good idea, because you shouldn't relay on extension methods to instance view controller and then passing any data to it, and as an extension method, it's not the responsibility for an UINavigationController to take care about ChatBotViewController or any other instanced controllers.
For my suggestion, in anywhere you want to show the ChatBotViewController, in your storyboard, create a presenting modal segue to the ChatBotViewController (which is embedded in an UINavigationController), and use performSegue(withIdentifier:sender:) to initiate the navigation controller, and override prepare(for:sender:) to set the data you want to pass in your ChatBotViewController.
Here's some codes for explaining:
import UIKit
struct FittoBottle {
}
class ChatBotViewController: UIViewController {
var fittoBottle = FittoBottle()
}
class ViewController: UIViewController {
func showChatController() {
/*
If there is any controller is presented by this view controller
or one of its ancestors in the view controller hierarchy,
we will dismiss it first.
*/
if presentedViewController != nil {
dismiss(animated: true) {
self.showChatController()
}
return
}
// Data that will be sent to the new controller
let fittoBottle = FittoBottle()
// ChatBotViewControllerSegue is the segue identifier set in your storyboard.
performSegue(withIdentifier: "ChatBotViewControllerSegue", sender: fittoBottle)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let navigationController = segue.destination as? UINavigationController else {
return
}
guard let chatBotController = navigationController.viewControllers.first as? ChatBotViewController else {
return
}
// Get the data from sender, if not found, create it there.
// Or if you don't pass it through sender, you can specify it here.
let fittoBottle = sender as? FittoBottle ?? FittoBottle()
chatBotController.fittoBottle = fittoBottle
}
}

Why delegate event is not received swift?

I would like to pass data from EditPostViewController to NewsfeedTableViewController using delegates, but func remove(mediaItem:_) is never called in the adopting class NewsfeedTableViewController. What am I doing wrong?
NewsfeedTableViewController: UITableViewController, EditPostViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//set ourselves as the delegate
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
}
//remove the row so that we can load a new one with the updated data
func remove(mediaItem: Media) {
print("media is received heeeee")
// it does't print anything
}
}
extension NewsfeedTableViewController {
//when edit button is touched, send the corresponding Media to EditPostViewController
func editPost(cell: MediaTableViewCell) {
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as? EditPostViewController
guard let indexPath = tableView.indexPath(for: cell) else {
print("indexpath was not received")
return}
editPostVC?.currentUser = currentUser
editPostVC?.mediaReceived = cell.mediaObject
self.navigationController?.pushViewController(editPostVC!, animated: true)
}
protocol EditPostViewControllerDelegate: class {
func remove(mediaItem: Media)
}
class EditPostViewController: UITableViewController {
weak var delegate: EditPostViewControllerDelegate?
#IBAction func uploadDidTap(_ sender: Any) {
let mediaReceived = Media()
delegate?.remove(mediaItem: mediaReceived)
}
}
The objects instantiating in viewDidLoad(:) and on edit button click event are not the same objects. Make a variable
var editPostVC: EditPostViewController?
instantiate in in viewDidLoad(:) with delegate
editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
and then present it on click event
navigationController?.pushViewController(editPostVC, animated: true)
or
present(editPostVC, animated: true, completion: nil)
you can pass data from presenter to presented VC before or after presenting the VC.
editPostVC.data = self.data
I suggest having a property in NewsfeedTableViewController
var editPostViewController: EditPostViewController?
and then assigning to that when you instantiate the EditPostViewController.
The idea is that it stops the class being autoreleased when NewsfeedTableViewController.viewDidLoad returns.

Return control to function when modal viewcontroller dismissed

I need to present a modal VC that sets a property in my presenting VC, and then I need to do something with that value back in the presenting VC. I have to be able to pass pointers to different properties to this function, so that it's reusable. I have the code below (KeyPickerTableViewController is the modal VC).
It should work, except not, because the line after present(picker... gets executed immediately after the picker is presented.
How do I get my presenting VC to "wait" until the modal VC is dismissed?
#objc func fromKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &sourceKey, presentingFrom: button)
}
#objc func toKeyTapped(_ button: UIBarButtonItem) {
print("from tapped")
setKey(for: &destKey, presentingFrom: button)
}
fileprivate func setKey(for key: inout Key!, presentingFrom buttonItem: UIBarButtonItem) {
let picker = KeyPickerTableViewController()
picker.delegate = self
picker.modalPresentationStyle = .popover
picker.popoverPresentationController?.barButtonItem = buttonItem
present(picker, animated: true, completion: nil)
if let delKey = delegatedKey {
key = delKey
}
}
You could use delegate pattern or closure.
I would do the following
1. I would not use inout pattern, I would first call the popover and then separately update what is needed to be updated
2. In KeyPickerTableViewController define property var actionOnDismiss: (()->())? and setting this action to what we need after initialisation of KeyPickerTableViewController
I could show it in code, but the abstract you've shown is not clear enough to come up with specific amendments. Please refer the illustration below.
import UIKit
class FirstVC: UIViewController {
var key = 0
#IBAction func buttonPressed(_ sender: Any) {
let vc = SecondVC()
vc.action = {
print(self.key)
self.key += 1
print(self.key)
}
present(vc, animated: true, completion: nil)
}
}
class SecondVC: UIViewController {
var action: (()->())?
override func viewDidLoad() {
onDismiss()
}
func onDismiss() {
action?()
}
}
While presenting VC, add dismissing modal VC action in its completion handler, so that Viewcontroller will be presented after dismissal is completed
present(picker, animated: true, completion: { (action) in
//dismissal action
if let delKey = delegatedKey {
key = delKey
}
})