web-dev-qa-db-ja.com

すでに表示されている(null)View ControllerにUIAlertControllerを表示しようとしました[Swift]

写真ビューに表示しようとしているアラートビューがあります。

写真はリストに表示され、全画面表示にプッシュできます。

写真ビューはプログラムで表示されています。アラートビューが既に表示されている(写真)ビューの上に別のビューを表示しようとしているため、それが問題の原因だと思います。

アラートビューを表示しようとしていますが、次のエラーが発生しています:

Warning: Attempt to present <UIAlertController: 0x147d2c6b0>  on <LiveDeadApp.ListViewController: 0x147d614c0> which is already presenting (null)

問題となる可能性のある行は次のとおりです。

 self.present(textPrompt, animated: true, completion: nil)

これがメインリストビューですThis is the main list view

これは、スクリーンショットを撮ったときのメインリストビューです This is the main list view when a screenshot is taken

これがメインの写真ビューですThis is the main photo view

これはメインの写真ビューのポップオーバーです([i]ボタンからアクセスします)This is the popover in the main photo view (accessed via the "i" button)

メインの写真ビューでスクリーンショットを撮ると、アラートビューは発生しません。ただし、デバイスの向きが変更されると、写真ビューがリストに戻り、アラートが表示されます。When a screenshot is taken on the main photo view, no alert view occurs. However, when the device's orientation is changed, the photo view goes back to the list and shows the alert.

これは私がやろうとしていることです:enter image description here

IOS 10のSwift 3

ありがとうございました!

リストビューと写真ビューのコードは次のとおりです。

import UIKit
import Kingfisher
import SKPhotoBrowser

class ListViewCell: UITableViewCell {

@IBOutlet weak var Cellimage: UIImageView!

@IBOutlet weak var cellVenue: UILabel!

@IBOutlet weak var cellLocation: UILabel!

@IBOutlet weak var cellDate: UILabel!
@IBOutlet weak var aiView: UIActivityIndicatorView!
}

class ListViewController: UITableViewController {

var subcategory:Subcategory!

var objects:[[String:String]] = [[String:String]]()

var images = [SKPhotoProtocol]()



override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)


}

override func viewDidLoad() {
    super.viewDidLoad()


    self.tableView.separatorStyle = .none

    self.view.backgroundColor = UIColor.black

    self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]

    navigationController!.navigationBar.barTintColor = UIColor.black

    let requireTextInput = "require text input"
    // add observer for screen shot
    NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationUserDidTakeScreenshot, object: nil, queue: OperationQueue.main, using:
        { notification in

            self.definesPresentationContext = true

            var inputTextField = UITextField()

            let textPrompt = UIAlertController(title: "Test!", message: "Testing!", preferredStyle: .alert)

            textPrompt.addAction(UIAlertAction(title: "Continue", style: .default, handler: {
                (action) -> Void in
                // if the input match the required text

                let str = inputTextField.text
                if str == requireTextInput {
                    print("right")
                } else {
                    print("wrong")
                }

            }))

            textPrompt.addTextField(configurationHandler: {(textField: UITextField!) in
                textField.placeholder = ""
                inputTextField = textField

            })

            self.present(textPrompt, animated: true, completion: nil)

    })

    if subcategory != nil {
        self.title = subcategory.title
        self.objects = subcategory.photos

        createLocalPhotos()

        self.tableView.reloadData()
    }


}

func createLocalPhotos() {

    for item in objects {
        let photo = SKPhoto.photoWithImageURL(item["url"]!)
        photo.shouldCachePhotoURLImage = true
        images.append(photo)
    }

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return objects.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell: ListViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ListViewCell

    let item = objects[indexPath.row]

    let title = item["title"]
    let location = item["location"]
    let date = item["date"]
    let urlSrt = item["url"]


    cell.cellVenue.text = title
    cell.cellLocation.text = location
    cell.cellDate.text = date

    if let url = URL(string: urlSrt!) {
        cell.aiView.startAnimating()
        cell.Cellimage.kf.setImage(with: url, placeholder: nil, options: nil, progressBlock: nil, completionHandler: { (image, error, cacheType, url) in
            cell.aiView.stopAnimating()
        })
    }

    return cell

}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath) as! ListViewCell

    if(cell.Cellimage.image != nil ) {
        SKPhotoBrowserOptions.displayToolbar = false
        SKPhotoBrowserOptions.displayCounterLabel = false
        SKPhotoBrowserOptions.displayBackAndForwardButton = false
        SKPhotoBrowserOptions.displayAction = false
        SKPhotoBrowserOptions.displayDeleteButton = true
        SKPhotoBrowserOptions.displayHorizontalScrollIndicator = false
        SKPhotoBrowserOptions.displayVerticalScrollIndicator = false
        SKPhotoBrowserOptions.displayStatusbar = false
        SKPhotoBrowserOptions.disableVerticalSwipe = true
        SKPhotoBrowserOptions.bounceAnimation = false
        let browser = ExtendedSKPhotoBrowser(originImage: cell.Cellimage.image!, photos: images, animatedFromView: cell)

        let btnSize = 80//24 * UIScreen.main.scale

        browser.updateCloseButton(UIImage(named: "ic_close_white")!, size: CGSize(width: btnSize, height: btnSize))
        browser.updateDeleteButton(UIImage(named: "ic_info_white")!, size: CGSize(width: btnSize, height: btnSize))
        browser.initializePageIndex(indexPath.row)
        browser.delegate = self
        present(browser, animated: true, completion: {})
        browser.toggleControls()
    }
}

override var prefersStatusBarHidden: Bool {
    get {
        return true
    }
}


var popOverVC:PopUpViewController!
}

extension ListViewController: SKPhotoBrowserDelegate {
func didShowPhotoAtIndex(_ index: Int) {






}

func willDismissAtPageIndex(_ index: Int) {

}

private func willShowActionSheet(photoIndex: Int) {
    // do some handle if you need
}

func didDismissAtPageIndex(_ index: Int) {
}

func didDismissActionSheetWithButtonIndex(_ buttonIndex: Int, photoIndex: Int) {
    // handle dismissing custom actions
}

func removePhoto(_ browser: SKPhotoBrowser, index: Int, reload: (() -> Void)) {
    popOverVC = self.storyboard?.instantiateViewController(withIdentifier: "sbPopUpID") as! PopUpViewController
    popOverVC.photoData = objects[index]

}

func viewForPhoto(_ browser: SKPhotoBrowser, index: Int) -> UIView? {
    return tableView.cellForRow(at: IndexPath(row: index, section: 0))
}
}


open class ExtendedSKPhotoBrowser: SKPhotoBrowser {

open override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent // white statusbar, .default is black
}

open override var prefersStatusBarHidden: Bool {
    return true
}
}
8
Miles

問題は本当に簡単です。現在表示されているUIAlertControllerに別のUIAlertControllerを表示しようとしています。

では、このようなケースを解決するにはどうすればよいですか

  1. 現在のビューコントローラで使用しているすべてのUIAlertControllerのリストを取得する必要があります。

  2. 現在のビューコントローラー(または非同期リクエストを実行している場合は他のビューコントローラー)でアラートを表示するためのロジックを確認する必要があります。

  3. あるアラートを別のアラートの上に表示したい場合、コードは次のようにする必要があります。

loadingAlertが現在画面に表示されていると仮定します。

self.loadingAlert.dismiss(animated: true, completion: {
     let anotherAlert = UIAlertController(title: "New One", message: "The Previous one is dismissed", preferredStyle: .alert)
     let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
     anotherAlert.addAction(okAction)
     self.present(anotherAlert, animated: true, completion: nil)
})

次のものを表示する前に、最初のものを閉じる必要があります。私はそれをボタンなしでアラートを却下してより効率的にするためにこの答えを作りました。

では、アクションボタンのあるアラートはどうですか?

作成したUIAlertControllerのアクションボタンの1つをクリックすると、自動的に閉じます。

ただし、UIAlertControllersを含む2つのUIButtonsを同時に表示している場合でも、問題は発生します。それぞれのロジックを再確認する必要があります。または、各アクションのハンドラーでロジックを処理できます。

self.connectionErrorAlert.dismiss(animated: true, completion: {
     let anotherAlert = UIAlertController(title: "New One", message: "The Previous one is dismissed", preferredStyle: .alert)
     let okAction = UIAlertAction(title: "OK", style: .default, handler: {action in
            let nextAlert = UIAlertController(title: "New One", message: "The Previous one is dismissed", preferredStyle: .alert)
            self.present(nextAlert, animated: true, completion: nil)
     })
     anotherAlert.addAction(okAction)
     self.present(anotherAlert, animated: true, completion: nil)
})

マイクへの回答の場合

DispatchQueue.main.async(execute: {

      if self.presentedViewController == nil {
           print("Alert comes up with the intended ViewController")
           var inputTextField = UITextField()

           let textPrompt = UIAlertController(title: "Test", message: "Testing", preferredStyle: .alert)

           textPrompt.addAction(UIAlertAction(title: "Continue", style: .default, handler: {
               (action) -> Void in
               // if the input matches the required text

               let str = inputTextField.text
               if str == requireTextInput {
                    print("right")
               } else {
                    print("wrong")
               }

           }))

           textPrompt.addTextField(configurationHandler: {(textField: UITextField!) in
                textField.placeholder = ""
                inputTextField = textField

            })
            weakSelf?.present(textPrompt, animated: true, completion: nil)
      } 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 _ : UIAlertController = thePresentedVC as? UIAlertController {
                      print("Alert not necessary, already on the screen !")

              } else {

                      print("Alert comes up via another presented VC, e.g. a PopOver")
              }
            }
       }
})

@Luke Answerに感謝: https://stackoverflow.com/a/30741496/3378606

8
Thiha Aung

ここに私の答えから複製

私の状況では、私はクラスをオーバーライドすることができませんでした。だから、ここに私が得たものがあります:

let viewController = self // I had viewController passed in as a function,
                          // but otherwise you can do this


// Present the view controller
let currentViewController = UIApplication.shared.keyWindow?.rootViewController
currentViewController?.dismiss(animated: true, completion: nil)

if viewController.presentedViewController == nil {
    currentViewController?.present(alert, animated: true, completion: nil)
} else {
    viewController.present(alert, animated: true, completion: nil)
}
0
George_E

私はこの問題に苦しんでおり、これを突き止めました。これは、2つのボタンが付いたシンプルなアプリです。最初のボタンをタップすると、「2019-03-05 16:58:04.094541-0500 ReadJason [41100:1610082]警告:すでに存在しているものを表示しようとすると、エラーが発生します。

この問題は、ボタン2をコピーしてボタン1を作成することで発生しました。各ボタンはアクション(btn1およびbtn2)に関連付けられています。 btn2をコピーしてbtn1を作成したとき、btn1のコードにbtn2への結合が含まれていました。次にtieをbtn1に追加したところ、2つの送信イベントがbtn1に結び付けられました。これがエラーの原因です。

ボタン1のイベントを調べると、2つのアクションがわかります。 2つのアクションを示すスクリーンショット 。不要なアクションを削除すると、エラーがクリアされます。

main.storyboardのスクリーンショット

<pre><code>    
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *btn1;
@property (weak, nonatomic) IBOutlet UIButton *btn2;
@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
}

- (IBAction)btn1:(id)sender {
   UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Btn1"
                   message:@"This is Btn1." preferredStyle:UIAlertControllerStyleAlert];

   UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

   [alert addAction:defaultAction];
   [self presentViewController:alert animated:YES completion:nil];
}

- (IBAction)btn2:(id)sender {
   UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Btn2"
                     message:@"This is Btn2." preferredStyle:UIAlertControllerStyleAlert];

   UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}];

   [alert addAction:defaultAction];
   [self presentViewController:alert animated:YES completion:nil];
}
</code></pre>
0
esym

viewDidLoadで、
weak var weakSelf = selfのようにweak変数を作成します

NotificationCenter
現在textPropmtのような

weak var weakSelf = self
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationUserDidTakeScreenshot, object: nil, queue: OperationQueue.main, using:
        { notification in
 DispatchQueue.main.async(execute: {
  //create textPrompt here in Main Thread
  weakSelf.present(textPrompt, animated: true, completion: nil)
 })
})
0
Vatsal Shukla