UIAlertController 已经呈现(空)

UIAlertController Already presenting (null)

我有一个应用程序,首先会显示一张地图,其中显示所有靠近我位置的公司。主屏幕有一个搜索按钮,我可以点击它并根据我的特定需要过滤结果

Mapa View Controller 和 Filtrar View Controller 之间的连接是基于自定义协议构建的(根据 https://medium.com/@jamesrochabrun/implementing-delegates-in-swift-step-by-step-d3211cbac3ef)。

所有沟通工作正常,我能够过滤并展示符合条件的公司。我的问题是过滤器 returns 什么都没有(没有符合条件的公司)。当发生这种情况时,我想向最终用户提供 UIAltert 作为建议。

警报已触发,但我收到一条错误消息 "Attempt to present on which is already presenting (null)"。

我尝试了

中的其中一项建议
   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")
          }
      }
    }

我看到 "Alert comes up via another presented VC, e.g. a PopOver" 打印出来了,但我不知道如何使用 thePresentedVC!.presentViewController(...)。

我正在使用 XCode 11.2.1 和 IOS 11.

我的详细代码是

警报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
    }
}

马帕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
        }
        // itterative 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({ [=12=] != ""})

        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 视图控制器

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.
     }
     */

}

谢谢 编辑

Link 到项目 https://github.com/afernandes0001/UIAlertController。 您只需点击登录(无需验证),转到主屏幕、地图、点击搜索和应用 - 无需添加任何数据,因为我已将数据设为静态。

尝试将 VC 传递到您的警报 class 并从那里呈现。

class Alert {
    class func show(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)

        //if more than one VC is presenting, or the same one is presenting twice, this might at least tell you which one (assuming it has a title) or when (if the same one is presenting twice)
        print("\(vc.title) is presenting alert")

        vc.present(alerta, animated: true)
    }
}

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        Alert.show(titulo: "title", mensagem: "this is an alert", vc: self)
    }
}

Alert.show(titulo: "", mensagem: "", vc: self)

通话

如果 Alert.show(arguments) 不起作用,您可能没有在函数中使用 class 关键字。如果是这样,您需要先创建一个实例 let alert = Alert() 并使用 alert.show(arguments)

或者您可以修改函数以使用 class 属性并创建 class 的实例,然后调用 instance.show(vc: self) 因为您已经拥有 titulomensagem

像这样让它成为 UIViewControiller 的扩展:

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)

        //if more than one VC is presenting, or the same one is presenting twice, this might at least tell you which one (assuming it has a title) or when (if the same one is presenting twice)
        print("\(vc.title) is presenting alert")

        vc.present(alerta, animated: true)
}}

在你想要的任何地方调用之后:

showAlert(titulo: "YourTitle", mensagem: "YourMessage", vc: self)
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)
 }
}

有效:)