web-dev-qa-db-fra.com

Swift: Obtenir l'erreur 'Capture instantanée d'une vue qui n'a pas été rendue ..' lors de l'ouverture d'une URL dans un safari depuis mon application

L'une des spécifications de mon application est que, en tapotant une cellule tableView, l'utilisateur sera redirigé vers le site Web associé à la cellule. Voici le code: 

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if let url = NSURL(string: appdelegate.studentInfo[indexPath.row].url) {
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
        UIApplication.sharedApplication().openURL(url)
    }
    else {
        let alert = UIAlertController(title: "Invalid URL", message: "Cannot open URL because it is invalid.", preferredStyle: UIAlertControllerStyle.Alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
    } 
}

Lors de mon premier contact, l'URL s'ouvre comme il se doit. Toutefois, si vous revenez dans l’application depuis Safari et que vous touchez une autre cellule, le message d'erreur suivant apparaît, même si l'application fonctionne toujours comme il est supposé:

La capture instantanée d’une vue qui n’a pas été rendue donne un. Vide instantané. Assurez-vous que votre vue a été rendue au moins une fois avant instantané ou instantané après les mises à jour de l'écran.

Y a-t-il un moyen d'éviter cette erreur? Ou est-ce un bug?

J'ai expérimenté des blocs dispatch_async mais cela n'a pas résolu le problème.

23
mayankk2308

Ce n'est peut-être pas le même problème que moi, mais je viens de résoudre le même avertissement dans mes journaux.

Je présente une UIAlertController sous la forme d'une popover d'actionSheet sur un iPad et j'ai eu exactement le même avertissement 8 fois de suite à chaque fois que j'ai essayé d'afficher le contrôleur d'alerte.

Pour que l'avertissement disparaisse, tout ce que j'avais à faire était de disposer la vue du contrôleur d'alertes comme dans le code suivant:

let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)

    ...            

alertController.view.layoutIfNeeded() //avoid Snapshotting error
self.presentViewController(alertController, animated: true, completion: nil)

J'espère que cela vous aide ou aide toute autre personne à avoir le même avertissement.

39
Saliom

Également en utilisant Objective-C, l’utilisation du [modeAlert.view layoutIfNeeded] suggéré a réduit le nombre d’erreurs à un, comme ci-dessus. L’erreur finale a été supprimée en remplaçant le dernier addAction de UIAlertActionStyleCancel par UIAlertActionStyleDefault comme ci-dessous. Pas une bonne solution de rechange à ce qui semble être un bug.

[modeAlert addAction:[UIAlertAction
                  actionWithTitle:NSLocalizedString(@"Cancel", @"")
                  style:UIAlertActionStyleDefault
                  handler:nil ]];
3
Patrick

Je pense que le dernier avertissement est venu du bouton Annuler.

Sur iOS8, le bouton d'annulation n'apparaît qu'en cas de besoin. Si vous exécutez l'application sur iPhone, elle est visible. Si vous exécutez l'application sur iPad, le bouton d'annulation n'est pas affiché et le gestionnaire de l'action d'annulation (style: UIAlertActionStyleCancel) est appelé lorsque l'utilisateur appuie en dehors du menu contextuel.

la réponse vient de: la réponse d'amalicka

2
Jerome

Je déclenche une UIAlertControllerStyleActionSheetUIAlertController après que l'utilisateur a cliqué sur une UITableViewRowAction et le même message d'erreur a été répété 8 fois après avoir présenté la UIAlertController.

Sur l'iPad Pro Simulator pour iOS 9.3, j'ai essayé la réponse de Saliom , et je suis passé de huit messages de journal à un.

Comme suggéré par anorskdev , je mets mon appel à -[UIView layoutIfNeeded] après mon appel -[UIViewController presentViewController:animated:completion:] et tous les avertissements disparaissent:

- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewRowAction *moreAction =
  [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault
                                     title:@"More"
                                   handler:^(UITableViewRowAction * _Nonnull action,
                                             NSIndexPath * _Nonnull moreIndexPath)
  {
    UIAlertController *alertController = 
    [UIAlertController alertControllerWithTitle:name 
                                        message:nil
                                 preferredStyle:UIAlertControllerStyleActionSheet];
    UIAlertAction *deleteAlertAction = 
    [UIAlertAction actionWithTitle:@"Delete"
                             style:UIAlertActionStyleDestructive
                           handler:deleteAction];
    [alertController addAction:deleteAlertAction];

    UIAlertAction *cancelAlertAction = 
    [UIAlertAction actionWithTitle:@"Cancel"
                             // totally ok to use the proper
                             // UIAlertActionStyleCancel
                             style:UIAlertActionStyleCancel
                           handler:cancelAction];
    [alertController addAction:cancelAlertAction];

    CGRect sourceRect = [self.tableView rectForRowAtIndexPath:moreIndexPath];

    // You must specify a sourceRect and sourceView
    // or a barButtonItem or presenting a
    // UIAlertControllerStyleActionSheet UIAlertController
    // will crash on iPad.
    alertController.popoverPresentationController.sourceRect = sourceRect;
    alertController.popoverPresentationController.sourceView = self.tableView;
    // first, present the alertController
    [self presentViewController:alertController
                       animated:YES
                     completion:nil];
    // then -layoutIfNeeded
    [alertController.view layoutIfNeeded];
  }
  return @[
          moreAction,
          ];
}

Notez qu'il n'était pas nécessaire d'utiliser la solution de Patrick d'utiliser UIAlertActionStyleDefault pour cancelAlertAction ou la solution de nurider de l'éliminer complètement sur les iPads.

0
Heath Borders

Je recevais un avertissement de débogage similaire lorsque j'essayais de présenter QLPreviewController sous forme modale. J'ai lu sur d'autres articles qu'il s'agissait d'un bogue Xcode. 

Pour moi, l'erreur affichée lorsque j'ai exécuté mon application sur le simulateur mais pas sur un appareil réel. Doit être un problème de Xcode/Simulator. J'espère que cela pourra aider.

0
RyanP

Je faisais face au même problème (même avertissement XD).

L'instantané d'une vue qui n'a pas été rendue donne un instantané vide. Assurez-vous que votre vue a été rendue au moins une fois avant la prise de vue ou après la mise à jour de l'écran.


Ma version de Xcode est 9.4.1 , et tout était parfait jusqu'à ce que j'ajoute resignFirstResponder() dans textFieldShouldReturn(_ textField: UITextField).


Voici ma situation:

Lorsque j'ai touché UITextField avec text dans la UITableViewCell, cela montrera l'avertissement.

J'ai donc changé la manière de déclencher UITextField ( Edit version ). Il ne montre plus l'avertissement.

En conclusion, je ne sais toujours pas pourquoi cela est arrivé, mais ce que je peux faire, c'est éviter l'avertissement, espérons que cela aidera quelqu'un: D



Code édité:

class TableViewCell: UITableViewCell {
    // add this in TableViewCell
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        answerTextField.isEnabled = selected
        if selected {
            answerTextField.becomeFirstResponder()
        } else {
            answerTextField.resignFirstResponder()
        }
    }
}


Code original:

class ViewController: UIViewController {

    private let tableView: UITableView = {
        let t = UITableView()
        t.separatorStyle = .none
        t.backgroundColor = .clear
        return t
    }()

    private let cellId = "Cell"

    private let data = [
        Model(title: "A", answer: "a"),
        Model(title: "B", answer: "b"),
        Model(title: "C", answer: nil),
        Model(title: "D", answer: nil)
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(TableViewCell.self, forCellReuseIdentifier: cellId)
        view.addSubview(tableView)
        tableView.frame = view.frame
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {

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

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

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

        if let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? TableViewCell {
            cell.contentView.backgroundColor = indexPath.section % 2 == 0 ? .gray : .white
            cell.setData(data[indexPath.row])
            return cell
        }
        return UITableViewCell()
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 100
    }
}

class TableViewCell: UITableViewCell {

    private let titleLabel = UILabel()
    private let answerTextField = UITextField()

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupViews()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupViews()
    }

    func setupViews() {
        setupTitleLabels()
    }

    func setData(_ data: Model) {
        titleLabel.text = data.title
        answerTextField.text = data.answer
    }

    private func setupTitleLabels() {
        answerTextField.delegate = self

        let sv = UIStackView(arrangedSubviews: [titleLabel, answerTextField])
        sv.axis = .vertical
        sv.spacing = 0
        sv.distribution = .fill

        contentView.addSubview(sv)
        sv.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        sv.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
        sv.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        sv.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
    }
}

extension TableViewCell: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

struct Model {
    let title: String
    var answer: String?
    init(title: String, answer: String? = nil) {
        self.title = title
        self.answer = answer
    }
}
0
alex02rt

Pour éviter le copier/coller de la réponse de Saliom vous pouvez créer une telle sous-classe et l'utiliser au lieu de UIAlertController:

class AlertController: UIAlertController {

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        self.view.layoutIfNeeded()
    }

}
0
Gleb Tarasov