I have an app where at first a map is shown with all companies close to my position. Main screen has a search button where I can click and filter results according to my specific needs [![enter image description here][1]][1]
The connection between Mapa View Controller and Filtrar View Controller was built based on a custom protocol (according to https://medium.com/@jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef).
All the communication works fine and I'm able to filter and present companies that meet the criteria. My issue is when the filter returns nothing (there are no companies that meet the criteria). When that happen I wanted to present a UIAltert as an advice to the end user.
Alert is triggered but I get an error message "Attempt to present <UIAlertController: 0x7f9314a47a00> on <xxx.MapaViewController: 0x7f9313e0f9e0> which is already presenting (null)".
I tried one of the suggestions from What is the best way to check if a UIAlertController is already presenting?
if self.presentedViewController == nil {
// do your presentation of the UIAlertController
// ...
} else {
// either the Alert is already presented, or any other view controller
// is active (e.g. a PopOver)
// ...
let thePresentedVC : UIViewController? = self.presentedViewController as UIViewController?
if thePresentedVC != nil {
if let thePresentedVCAsAlertController : UIAlertController = thePresentedVC as? UIAlertController {
// nothing to do , AlertController already active
// ...
print("Alert not necessary, already on the screen !")
} else {
// there is another ViewController presented
// but it is not an UIAlertController, so do
// your UIAlertController-Presentation with
// this (presented) ViewController
// ...
thePresentedVC!.presentViewController(...)
print("Alert comes up via another presented VC, e.g. a PopOver")
}
}
}
I see "Alert comes up via another presented VC, e.g. a PopOver" printed but I don't know how to use thePresentedVC!.presentViewController(...).
I'm using XCode 11.2.1 and IOS 11.
My detailed code is
Alert Class
import UIKit
class Alerta {
var titulo : String
var mensagem : String
init(titulo: String, mensagem: String) {
self.titulo = titulo
self.mensagem = mensagem
}
func getAlerta() -> UIAlertController {
let alerta = UIAlertController(title: titulo, message: mensagem, preferredStyle: .alert)
let acaoCancelar = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alerta.addAction(acaoCancelar)
return alerta
}
}
Mapa Class
import UIKit
import MapKit
import ProgressHUD
class MapaViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, FiltroVCDelegate {
@IBOutlet weak var mapa: MKMapView!
var gerenciadorLocalizacao = CLLocationManager()
var anotacaoArray = [MinhaAnotacao]()
// var annotationSearch: [MinhaAnotacao] = [] // Filtered annotation
var anotacao = MinhaAnotacao()
var searchArr: [String] = []
var searching = false
var todasAnotacoes: [(objLat: CLLocationDegrees, objLong: CLLocationDegrees, objName: String, objDesc: String, objId: String, objTel1: String, objTel2: String, objEsp1: String, objEsp2: String, objEst: String, objCid: String)] = []
var clinicasR: [(objLat: CLLocationDegrees, objLong: CLLocationDegrees, objName: String, objDesc: String, objId: String, objTel1: String, objTel2: String, objEsp1: String, objEsp2: String, objEst: String, objCid: String)] = []
@IBOutlet weak var adicionarOutlet: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
mapa.register(MyAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
// Retrieving Logger user data and hidding "add" button if applicable
ProgressHUD.show("Carregando...")
var searching = false
// Map - User location
self.mapa.delegate = self
self.gerenciadorLocalizacao.delegate = self
self.gerenciadorLocalizacao.desiredAccuracy = kCLLocationAccuracyBest
self.gerenciadorLocalizacao.requestWhenInUseAuthorization()
self.gerenciadorLocalizacao.startUpdatingLocation()
ProgressHUD.dismiss()
}
// add annotations to the map
func addAnnotationsToMap() {
anotacaoArray = []
self.mapa.removeAnnotations(mapa.annotations)
if searching {
for oneObject in self.clinicasR {
let umaAnotacao = MinhaAnotacao()
let oneObjLoc: CLLocationCoordinate2D = CLLocationCoordinate2DMake(oneObject.objLat, oneObject.objLong)
umaAnotacao.coordinate = oneObjLoc
umaAnotacao.title = oneObject.objName
umaAnotacao.subtitle = oneObject.objDesc
umaAnotacao.identicadorMapa = oneObject.objId
umaAnotacao.telefone = oneObject.objTel1
umaAnotacao.telefone2 = oneObject.objTel2
umaAnotacao.especialidade1 = oneObject.objEsp1
umaAnotacao.especialidade2 = oneObject.objEsp2
umaAnotacao.estado = oneObject.objEst
umaAnotacao.cidade = oneObject.objCid
umaAnotacao.endereco = oneObject.objDesc
umaAnotacao.latitude = String(oneObject.objLat)
umaAnotacao.longitude = String(oneObject.objLong)
self.anotacaoArray.append(umaAnotacao)
}
} else {
for oneObject in self.todasAnotacoes {
let umaAnotacao = MinhaAnotacao()
let oneObjLoc: CLLocationCoordinate2D = CLLocationCoordinate2DMake(oneObject.objLat, oneObject.objLong)
umaAnotacao.coordinate = oneObjLoc
umaAnotacao.title = oneObject.objName
umaAnotacao.subtitle = oneObject.objDesc
umaAnotacao.identicadorMapa = oneObject.objId
umaAnotacao.telefone = oneObject.objTel1
umaAnotacao.telefone2 = oneObject.objTel2
umaAnotacao.especialidade1 = oneObject.objEsp1
umaAnotacao.especialidade2 = oneObject.objEsp2
umaAnotacao.estado = oneObject.objEst
umaAnotacao.cidade = oneObject.objCid
umaAnotacao.endereco = oneObject.objDesc
umaAnotacao.latitude = String(oneObject.objLat)
umaAnotacao.longitude = String(oneObject.objLong)
self.anotacaoArray.append(umaAnotacao)
}
}
self.mapa.addAnnotations(self.anotacaoArray)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "clinicaDetailsSegue" {
let clinicasDetailsViewController = segue.destination as! ClinicasDetailsViewController
clinicasDetailsViewController.identificador = self.anotacao.identicadorMapa
} else if segue.identifier == "searchSegue" {
if let nav = segue.destination as? UINavigationController, let filterVC = nav.topViewController as? FiltrarViewController {
filterVC.delegate = self
}
}
}
func search(searchAnnotation: [MinhaAnotacao], searchArr: [String]) -> [MinhaAnotacao] {
var searchArr = searchArr
// base case - no more searches - return clinics found
if searchArr.count == 0 {
return searchAnnotation
}
// iterative case - search clinic with next search term and pass results to next search
let foundAnnotation = searchAnnotation.filter { item in
(item.title!.lowercased() as AnyObject).contains(searchArr[0]) ||
item.especialidade1.lowercased().contains(searchArr[0]) ||
item.especialidade2.lowercased().contains(searchArr[0]) ||
item.cidade.lowercased().contains(searchArr[0]) ||
item.estado.lowercased().contains(searchArr[0]) ||
item.latitude.contains(searchArr[0]) || item.longitude.contains(searchArr[0]) || item.endereco.contains(searchArr[0])
}
// remove completed search and call next search
searchArr.remove(at: 0)
return search(searchAnnotation: foundAnnotation, searchArr: searchArr)
}
// From Custom Protocol
func dadosEscolhidos(nomeClinicaFiltro: String, estadoClinicaFiltro: String, cidadeClinicaFiltro: String, especialidade1ClinicaFiltro: String, especialidade2ClinicaFiltro: String) {
searchArr = []
clinicasR = []
searchArr = ["\(nomeClinicaFiltro.lowercased())","\(estadoClinicaFiltro.lowercased())", "\(cidadeClinicaFiltro.lowercased())", "\(especialidade1ClinicaFiltro.lowercased())", "\(especialidade2ClinicaFiltro.lowercased())"]
searchArr = searchArr.filter({ $0 != ""})
let annotationSearch = search(searchAnnotation: anotacaoArray, searchArr: searchArr) // Filtered Clinicas
if annotationSearch.count > 0 {
for i in 0...annotationSearch.count - 1 {
self.clinicasR.append((objLat: Double(annotationSearch[i].latitude)!, objLong: Double(annotationSearch[i].longitude)!, objName: annotationSearch[i].title!, objDesc: annotationSearch[i].endereco, objId: annotationSearch[i].identicadorMapa, objTel1: annotationSearch[i].telefone, objTel2: annotationSearch[i].telefone2, objEsp1: annotationSearch[i].especialidade1, objEsp2: annotationSearch[i].especialidade2, objEst: annotationSearch[i].estado, objCid: annotationSearch[i].cidade))
}
searching = true
addAnnotationsToMap()
} else {
if self.presentedViewController == nil {
let alerta = Alerta(titulo: "Erro", mensagem: "Nenhuma clínica atende ao filtro definido")
self.present(alerta.getAlerta(), animated: true, completion: nil)
print( "e Aqui, chegou? \(annotationSearch.count)")
} else {
// either the Alert is already presented, or any other view controller
// is active (e.g. a PopOver)
// ...
let thePresentedVC : UIViewController? = self.presentedViewController as UIViewController?
if thePresentedVC != nil {
if let thePresentedVCAsAlertController : UIAlertController = thePresentedVC as? UIAlertController {
// nothing to do , AlertController already active
// ...
print("Alert not necessary, already on the screen !")
} else {
// there is another ViewController presented
// but it is not an UIAlertController, so do
// your UIAlertController-Presentation with
// this (presented) ViewController
// ...
//let alerta = Alerta(titulo: "Erro", mensagem: "Nenhuma clínica atende ao filtro definido")
//thePresentedVC!.presentedViewController(alerta)
print("Alert comes up via another presented VC, e.g. a PopOver")
}
}
}
}
}
}
Filtrar View Controller
import UIKit
import ProgressHUD
protocol FiltroVCDelegate: class {
func dadosEscolhidos(nomeClinicaFiltro: String, estadoClinicaFiltro: String, cidadeClinicaFiltro: String, especialidade1ClinicaFiltro: String, especialidade2ClinicaFiltro: String)
}
class FiltrarViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
weak var delegate: FiltroVCDelegate?
var nomeSelecionado = ""
var estadosJSON = [Estado]()
var cidades = [Cidade]()
var estado : Estado? // Selected State identifier
var cidade : Cidade? // Selected City identifier
var estadoSelecionado = "" // Selected State
var cidadeSelecionada = "" // Selected City
var especialidadesJSON = [Especialidade]()
var especialidades2 = [Especialidade2]()
var especialidade1 : Especialidade? // Selected Specialty1 identifier
var especialidade2 : Especialidade2? // Selected Specialty2 identifier
var especialidade1Selecionada = ""
var especialidade2Selecionada = ""
let fontName = "HelveticaNeue"
var searchArr = [String]()
@IBOutlet weak var nomeClinica: UITextField!
@IBOutlet weak var especialidadeLabel: UILabel!
@IBOutlet weak var estadoClinicaPicker: UIPickerView!
@IBOutlet weak var especialidade1Picker: UIPickerView!
@IBOutlet weak var especialidade2Picker: UIPickerView!
override func viewDidLoad() {
ProgressHUD.show("Carregando...")
readJsonEstados()
readJsonEspecialidades()
super.viewDidLoad()
nomeClinica.text = ""
especialidadeLabel.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
ProgressHUD.dismiss()
}
@IBAction func aplicarFiltro(_ sender: Any) {
if nomeClinica.text == nil {
nomeClinica.text = ""
}
delegate?.dadosEscolhidos(nomeClinicaFiltro: nomeClinica.text!, estadoClinicaFiltro: estadoSelecionado, cidadeClinicaFiltro: cidadeSelecionada, especialidade1ClinicaFiltro: especialidade1Selecionada, especialidade2ClinicaFiltro: especialidade2Selecionada)
navigationController?.dismiss(animated: true)
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
especialidade1Picker.reloadComponent(0)
especialidade2Picker.reloadComponent(0)
estadoClinicaPicker.reloadAllComponents()
if pickerView == estadoClinicaPicker {
if component == 0 {
self.estado = self.estadosJSON[row]
self.cidades = self.estadosJSON[row].cidades
estadoClinicaPicker.reloadComponent(1)
estadoClinicaPicker.selectRow(0, inComponent: 1, animated: true)
} else {
self.cidade = self.cidades[row]
estadoClinicaPicker.reloadAllComponents()
}
} else if pickerView == especialidade1Picker {
self.especialidade1 = self.especialidadesJSON[row]
self.especialidades2 = self.especialidadesJSON[row].especialidade2
especialidade1Picker.reloadComponent(0)
especialidade2Picker.reloadComponent(0)
especialidade2Picker.selectRow(0, inComponent: 0, animated: true)
} else if pickerView == especialidade2Picker {
self.especialidade2 = self.especialidades2[row]
especialidade1Picker.reloadComponent(0)
especialidade2Picker.reloadComponent(0)
}
let estadoIndiceSelecionado = estadoClinicaPicker.selectedRow(inComponent: 0)
let cidadeIndiceSelecionada = estadoClinicaPicker.selectedRow(inComponent: 1)
let especialidade1IndiceSelecionado = especialidade1Picker.selectedRow(inComponent: 0)
let especialidade2IndiceSelecionado = especialidade2Picker.selectedRow(inComponent: 0)
if estadoIndiceSelecionado >= 0 {
if cidadeIndiceSelecionada >= 0 {
estadoSelecionado = estadosJSON[estadoIndiceSelecionado].nome
cidadeSelecionada = cidades[cidadeIndiceSelecionada].nome
}
}
if especialidade1IndiceSelecionado >= 0 {
if especialidade2IndiceSelecionado >= 0 {
especialidade1Selecionada = especialidadesJSON[especialidade1IndiceSelecionado].nome
especialidade2Selecionada = especialidadesJSON[especialidade1IndiceSelecionado].especialidade2[especialidade2IndiceSelecionado].nome
}
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
if pickerView == estadoClinicaPicker {
return 2
} else if pickerView == especialidade1Picker {
return 1
} else if pickerView == especialidade2Picker {
return 1
}
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if pickerView == estadoClinicaPicker {
if component == 0 {
return estadosJSON.count
} else {
return cidades.count
}
} else if pickerView == especialidade1Picker {
return self.especialidadesJSON.count
} else if pickerView == especialidade2Picker {
return especialidades2.count
}
return 1
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var rowTitle = ""
let pickerLabel = UILabel()
pickerLabel.textColor = UIColor.black
if pickerView == estadoClinicaPicker {
if component == 0 {
rowTitle = estadosJSON[row].nome
} else {
rowTitle = cidades[row].nome
}
} else if pickerView == especialidade1Picker {
rowTitle = especialidadesJSON[row].nome
} else if pickerView == especialidade2Picker {
rowTitle = especialidades2[row].nome
}
pickerLabel.text = rowTitle
pickerLabel.font = UIFont(name: fontName, size: 16.0)
pickerLabel.textAlignment = .center
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if pickerView == estadoClinicaPicker {
if component == 0 {
return 50
} else {
return 300
}
}
return 300
}
@IBAction func cancel(_ sender: Any) {
navigationController?.dismiss(animated: true)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
func readJsonEstados() {
let url = Bundle.main.url(forResource: "EstadosECidades", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(RootState.self, from: data)
//handles the array of countries on your json file.
self.estadosJSON = jsonResult.estado
self.cidades = self.estadosJSON.first!.cidades
} catch {
let alerta = Alerta(titulo: "Erro ao Carregar", mensagem: "Erro ao carregar Estados e Cidades. Por favor reinicie o app")
self.present(alerta.getAlerta(), animated: true, completion: nil)
}
}
func readJsonEspecialidades() {
let url = Bundle.main.url(forResource: "Especialidades", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(RootEsp.self, from: data)
//handles the array of specialties on your json file.
self.especialidadesJSON = jsonResult.especialidade
self.especialidades2 = self.especialidadesJSON.first!.especialidade2
} catch {
let alerta = Alerta(titulo: "Erro ao Carregar", mensagem: "Erro ao carregar Especialidades. Por favor reinicie o app")
self.present(alerta.getAlerta(), animated: true, completion: nil)
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
Thanks [1]: https://i.sstatic.net/e3KCt.png
EDIT
Link to the project https://github.com/afernandes0001/UIAlertController. You just need to click Log in (no validation is done), go to main screen, Map, Click Search and Apply - There is no need to add any data as I have made data static.
import UIKit
class ViewController: UIViewController {
let button = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
button.backgroundColor = .black
button.setTitle("Alert", for: .normal)
button.setTitleColor(.white, for: .normal)
button.addTarget(self, action: #selector(handleAlert), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.widthAnchor.constraint(equalToConstant: 100).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
// Do any additional setup after loading the view.
}
@objc fileprivate func handleAlert() {
OperationQueue.main.addOperation {
showAlert(titulo: "YourTilte", mensagem: "YourMessage", vc: self)
print("On main thread: \(Thread.current.isMainThread)")
}
}
extension UIViewController {
func showAlert(titulo: String, mensagem: String, vc: UIViewController) {
let alerta = UIAlertController(title: titulo, message: mensagem, preferredStyle: .alert)
let acaoCancelar = UIAlertAction(title: "Ok", style: .cancel, handler: nil)
alerta.addAction(acaoCancelar)
vc.present(alerta, animated: true)
}
}
it work :)