Swift: попытайтесь представить UIAlertController, чье представление не находится в иерархии окон!

Я пытаюсь отобразить UIAlertController при нажатии кнопки (нажатие кнопки запускает определенный код, и в зависимости от указанного результата появляется предупреждение). Первоначальный ViewController используется по умолчанию, и я создал второй (ConsoleViewController). Пользователь входит в систему и после успешного входа переходит к следующему представлению (ConsoleViewController), которое отображает данные (которые находятся в разделе viewDidLoad() в ConsoleViewController). Как только пользователь нажимает «Отметиться», приложение фиксирует местоположение устройства по GPS, текущую дату/время и открывает камеру, чтобы сделать снимок (селфи). При выборе «Использовать фото» в камере (эта функция еще не закодирована) она отправляет все 3 параметра обработчику API.

Вторая кнопка открывает средство выбора даты, и пользователь выбирает дату и время. После нажатия кнопки «Отправить» label.text обновляется с выбранной датой (из средства выбора даты), и должно появиться предупреждение о том, что дата была успешно сохранена на основе returnString из обработчика API.

Проблема, с которой я сталкиваюсь, заключается в том, что я хочу, чтобы во всплывающем окне с предупреждением отображалось сообщение «Успех» или «Ошибка» в зависимости от того, были ли данные успешно отправлены или нет (на основе returnString обработчика API). Я продолжаю получать ошибку Warning: Attempt to present <UIAlertController: 0x7fd03961b0a0> on <appName.ViewController: 0x7fd039538640> whose view is not in the window hierarchy!. Я попытался добавить представление предупреждений в основной поток, я попытался изменить способ представления перехода (push, модальный и т. д.) и почти все остальное, что я мог найти здесь, в StackOverFlow (а также поиск в Google), и никакое решение, кажется, не работает для меня. Я создал оповещения на основе неправильных учетных данных для входа (в ViewController), и это всплывающее окно работает правильно.

Ниже мой код... Не обращайте внимания на мои случайные print строки... это помогает мне отслеживать, где я нахожусь, лол.

Примечание. Я добавил соответствующие элементы info.plist, чтобы отображать правильные всплывающие окна из iOS. Они тоже, скажите мне, что они не находятся в иерархии представлений

    func consoleAlertPopup(title: String, message: String) {

    let alertController = UIAlertController(title: title, message: message,     preferredStyle: UIAlertControllerStyle.alert)
        UIApplication.shared.keyWindow?.rootViewController?.present(alertController,     animated: true, completion: nil)
    alertController.addAction(UIAlertAction(title: "Try Again", style:     UIAlertActionStyle.default, handler:  nil))
}

Консольвиевконтроллер:

import UIKit
import CoreLocation
import MobileCoreServices

class ConsoleViewController: UIViewController, CLLocationManagerDelegate {

var alertView: UIAlertController?

// IB Outlets \\
@IBOutlet var DisplayUserName: UILabel!
@IBOutlet var LastCheckInLabel: UILabel!
@IBOutlet var NextCourtDateLabel: UILabel!
@IBOutlet weak var CourtDateButton: UIButton!

@IBOutlet weak var courtDatePicker: UIDatePicker!

//Global Variables & UI Elements
var checkInImg: UIImage!
var userNameString: String!
var newDisplayDate: String?
var updatedCourtLabel: String?
let formatter = DateFormatter()
let displayFormatter = DateFormatter()
var locationManager: CLLocationManager!

@IBAction func clickCheckIn(_ sender: UIButton) {

    sendPicture()
    //Camera Pop Up

}

@IBAction func clickCourtDate() {

    courtPickerAction(Any.self)

}

@IBAction func courtPickerAction(_ sender: Any) {


   DatePickerDialog().show("Select Next Court Date", doneButtonTitle: "Submit", cancelButtonTitle: "Cancel", datePickerMode: .dateAndTime) {
        (courtDateTime) -> Void in

    if courtDateTime == nil {
        //Do nothing
    } else {
        self.formatter.dateFormat = "yyyy-MM-dd HH:mm"
        self.newDisplayDate = self.formatter.string(from: (courtDateTime)!)


    //print("Date after format: \(courtDateTime)")
    print("Date and time: \(self.newDisplayDate) after sendDefendantData func")


    // Submit Button - Date Picker \\
    if (DatePickerDialog().doneButton != nil) {

        self.sendDefendantData()


        print("Send Defendant Data from Submit")
        print("After sendDefData: \(self.newDisplayDate)")

        self.displayFormatter.dateStyle = DateFormatter.Style.full
        self.displayFormatter.timeStyle = DateFormatter.Style.short
        self.NextCourtDateLabel.text = self.displayFormatter.string(from: courtDateTime!)

            }

        }
    }
}


override func viewDidLoad() {
    super.viewDidLoad()


    print("Console View Did Load")
    self.hideKeyboardWhenTappedAround()

    DisplayUserName.text! = userNameString
    // For location allowance from user
    // I've placed this code here (instead of in a function) so the alert
    // pop up will show and allows accessing location. "not in hierarchy"
    // elsewise.
    self.locationManager = CLLocationManager()
    self.locationManager.delegate = self
    self.locationManager.requestWhenInUseAuthorization()

    // Format Display Date & Times
    self.displayFormatter.dateStyle = DateFormatter.Style.full
    self.displayFormatter.timeStyle = DateFormatter.Style.long


    // Retrieve Defendant Data From API Handler
    getDefendantData()





    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}




func presentAlert(_ message: String) {

    self.alertView = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
    alertView?.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in })

    ViewController().present(alertView!, animated: true, completion: nil)

}


func consoleAlertPopup(title: String, message: String) {

    let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
    UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
    alertController.addAction(UIAlertAction(title: "Try Again", style: UIAlertActionStyle.default, handler:  nil))
}

func getDefendantData() {...}
func sendDefendantData() {...}
func sendPicture() {....}

Вьюконтроллер:

    import UIKit


// Hide Keyboard \\
extension UIViewController {
    func hideKeyboardWhenTappedAround() {
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        view.addGestureRecognizer(tap)
    }

    func dismissKeyboard() {
        view.endEditing(true)
    }
}



class ViewController: UIViewController {

    // Send User Login to Console Screen \\
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if (segue.identifier == "toConsoleScreen") {

            let secondViewController = segue.destination as! ConsoleViewController

            secondViewController.userNameString = UserNameField.text!

            print("PrepareSegue")
        }

    }

    @IBAction func UserNameEditBegan() {
        UserNameField.text = nil
    }
    @IBAction func PasswordEditBegan() {
        PasswordField.text = nil
    }

    @IBOutlet weak var UserNameField: UITextField!
    @IBOutlet weak var PasswordField: UITextField!

    func successfulLogin(Username: String) {


        print("Inside Function")

        print(Username)
        print("Inside Successful Login")

        // Show next view - Add to Main Queue\\
        OperationQueue.main.addOperation{

            //print("Before dismissal")
            // self.dismiss(animated: true, completion: nil)
            //print("After dismissal")
            self.performSegue(withIdentifier: "toConsoleScreen", sender: self)
            print("After segue")

        }
    }



    override func viewDidLoad() {
        super.viewDidLoad()

        self.hideKeyboardWhenTappedAround()



        // Do any additional setup after loading the view, typically from a nib.


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func loginButton() {

        login(Username: UserNameField.text!, Password: PasswordField.text!) { username in
            self.successfulLogin(Username: username)
    }



}



    }

person szady    schedule 06.03.2017    source источник
comment
Вы внимательно просмотрели эти результаты поиска?   -  person rmaddy    schedule 07.03.2017
comment
Убедитесь, что вы установили modalPresentationStyle = .overCurrentContext в свой UIAlertViewController.   -  person onnoweb    schedule 07.03.2017
comment
Можете ли вы очистить свой код, 1) удалив ненужные пробелы 2) прокомментировав строку, которая показывает ошибку 3) сделайте вопрос как можно короче? Чтобы люди успели его прочитать?   -  person Bright Future    schedule 07.03.2017
comment
Почему вам нужно представить оповещение в ViewController, а не просто self?   -  person chengsam    schedule 07.03.2017


Ответы (2)


Эта строка:

ViewController().present(alertView!, animated: true, completion: nil)

создает новый экземпляр ViewController и вызывает для него метод present. Это не сработает. Вам нужно вызвать его из контроллера представления, который сам представлен. Похоже, этот код находится внутри ConsoleViewController, может быть, вы можете просто использовать self там.

person Dave Weston    schedule 07.03.2017
comment
Спасибо! На самом деле я очень злюсь на себя за то, что не уловил этого... Пожалуйста, не обращайте внимания на мое невежество... - person szady; 08.03.2017
comment
Пожалуйста! И не беспокойтесь об этом. Мы все были там. - person Dave Weston; 08.03.2017
comment
И последнее... Уведомление iOS о разрешении использования GPS/камеры, которое я хотел бы отображать, когда пользователь нажимает кнопку. Я поставил self.locationManager.requestWhenInUseAuthorization() в действие IB для нажатия кнопки, но там написано "Warning: Attempt to present <app name: 0x100f4c410> on <app name.ConsoleViewController: 0x100f43e40> while a presentation is in progress! Есть идеи? - person szady; 08.03.2017
comment
Похоже, что одновременно представлены два разных контроллера представления. Возможно, вы захотите переместить этот вызов requestWhenInUseAuthorization в более поздний метод жизненного цикла, например viewDidAppear. - person Dave Weston; 08.03.2017

попробуйте представить свой UIAlertController в DispatchQueue

DispatchQueue.main.async {

    let alert = UIAlertController(title: "Alert!", message: nil, preferredStyle: .alert)

    let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)

    alert.addAction(cancelAction)
    self.present(alert, animated: true, completion: nil)   
}
person HIEPING    schedule 16.07.2017