Swift:尝试从我的应用程序中打开 Safari 浏览器中的 URL 时出现“快照尚未呈现的视图..”错误

Posted

技术标签:

【中文标题】Swift:尝试从我的应用程序中打开 Safari 浏览器中的 URL 时出现“快照尚未呈现的视图..”错误【英文标题】:Swift: Getting 'Snapshotting a view that has not been rendered..' error when trying to open a URL in safari from my app 【发布时间】:2015-06-06 17:00:22 【问题描述】:

我的应用程序的一个规范是,在点击 tableView 单元格时,用户将被重定向到与该单元格关联的网站。这是代码:

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)
     

在我第一次点击时,URL 会按预期打开。但是,从 Safari 返回应用并触摸另一个单元格会导致以下错误,尽管应用仍然可以正常运行:

对尚未渲染的视图进行快照会导致空白 快照。确保您的视图之前至少渲染过一次 屏幕更新后的快照或快照。

有什么办法可以避免这个错误吗?或者这是一个错误?

我已经尝试了 dispatch_async 块,但它并没有解决问题。

【问题讨论】:

【参考方案1】:

这可能与我的问题不同,但我只是在日志中解决了相同的警告。

我在 iPad 上将 UIAlertController 显示为 actionSheet 弹出框,每次尝试显示警报控制器时,我都会连续 8 次收到完全相同的警告。

要使警告消失,我所要做的就是按照以下代码布局警报控制器视图:

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

    ...            

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

我希望这对您或任何其他有相同警告的人有所帮助。

【讨论】:

我正在使用 Objective-C,并且在呈现 UIAlertController 时看到了这些错误。如果我执行 [alert.view layoutIfNeeded] 所有警告都会消失,但只有一个。换句话说,这个警告的重复结束了,但我仍然在日志中看到一个。 在 Swift 中我现在没有任何警告了,我会在空闲时间找一些时间在 Objective-C 中测试它,我会让你知道我的找到 这个绝妙的答案解决了我的问题并删除了所有警告。所以我猜哈内曼最后一次警告还有另一个原因。 刚刚发现最后一个警告是由alert控制器中的title引起的 胡奇怪但很高兴知道,我还没有自己在objc中尝试过,还没有时间。可能是标题在另一个视图中,在执行 layoutIfNeeded 时未布局,这表明它在另一个层次结构中【参考方案2】:

同样使用 Objective-C,使用建议的 [modeAlert.view layoutIfNeeded] 将错误减少到一个如上所述。通过将最后一个 addAction 从 UIAlertActionStyleCancel 更改为 UIAlertActionStyleDefault 抑制了最终错误,如下所示。对于似乎是错误的问题,这不是一个很好的解决方法。

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

【讨论】:

我发现取消 iPad 工作表的“取消”选项(无论如何都不会为 iPad 显示)也可以消除最终错误。我仍然为不得不调用 layoutIfNeeded 而烦恼,但我的日志现在很干净。 添加“layoutIfNeeded()”为我消除了错误消息。斯威夫特:alert.view.layoutIfNeeded() 还发现我需要在 presentViewController() 之后而不是之前执行 layoutIfNeeded() - 之前似乎适用于 iPad,但不适用于 iPhone。把它放在对两种类型的设备都适用之后。 @anorskdev 在这里相同 - 之后似乎是要走的路。 ios9 -- 我将样式保留为取消,但按照上面的建议做了 -- 将 layoutIfNeeded 放在 presentVC 之后,它清除了所有消息。【参考方案3】:

为避免从Saliom's answer 复制/粘贴,您可以创建这样的子类并使用它来代替UIAlertController

class AlertController: UIAlertController 

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


【讨论】:

【参考方案4】:

我认为最后一个警告来自取消按钮。

在 iOS8 上,取消按钮仅在需要时显示。如果您在 iPhone 上运行该应用程序,它是可见的。如果您在 iPad 上运行应用程序,则不会显示取消按钮,并且当用户在弹出窗口外点击时会调用取消操作 (style:UIAlertActionStyleCancel) 的处理程序。

答案来自:amalicka's answer

【讨论】:

【参考方案5】:

我遇到了类似的问题(同样的警告 XD)。

对尚未渲染的视图进行快照会导致快照为空。确保您的视图在快照之前或屏幕更新后的快照之前至少渲染过一次。

我的 Xcode 版本是 9.4.1,在我将 resignFirstResponder() 添加到 textFieldShouldReturn(_ textField: UITextField) 之前,一切都很完美。


这是我的情况:

当我用UITableViewCell中的文本触摸UITextField时,它会显示警告。

所以我改变了触发UITextField(编辑版本)的方式。它不再显示警告。

总之,我仍然不知道为什么会发生这种情况,但我可以做的是避免警告,希望它会帮助某人:D


编辑代码:

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()
        
    

原文代码:

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
    

【讨论】:

【参考方案6】:

在用户单击UITableViewRowAction 后,我触发了UIAlertControllerStyleActionSheet UIAlertController,并且在呈现UIAlertController 后,我收到了相同的错误消息重复8 次。

在适用于 iOS 9.3 的 iPad Pro 模拟器上,我尝试了Saliom's answer,我的日志消息从 8 条变为了 1 条。

作为anorskdev suggested,我在-[UIViewController presentViewController:animated:completion:] 通话后拨打了-[UIView layoutIfNeeded],所有警告都消失了:

- (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,
          ];

请注意,在 iPad 上没有必要使用 Patrick's solution 或 cancelAlertAction 或 nurider's solution 使用 Patrick's solution 来完全消除它。

【讨论】:

【参考方案7】:

在尝试以模态方式呈现 QLPreviewController 时,我收到了类似的调试警告。我在其他帖子上读到这是一个 Xcode 错误。

对我来说,当我在模拟器上而不是在实际设备上运行我的应用程序时显示的错误。必须是 Xcode/Simulator 问题。希望对您有所帮助。

【讨论】:

已经接受的答案对我来说似乎更好。您的没有提供真正的解决方案 - 避免设备和模拟器上的错误。【参考方案8】:

我遇到了同样的问题,并找到了一个有意义的简单解决方案。

如果是 iPad ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad),那么NOT 将带有样式UIAlertActionStyleCancelUIAlertAction 添加到UIAlertController

我做了这个改变,我的错误消失了。此解决方案很有意义,因为您不需要在 iPad 上对样式为 UIAlertControllerStyleActionSheet 的警报执行取消操作。

【讨论】:

你是对的。这并不总能解决问题。有时有效,有时无效。我认为@Saliom 的回答是最好的。

以上是关于Swift:尝试从我的应用程序中打开 Safari 浏览器中的 URL 时出现“快照尚未呈现的视图..”错误的主要内容,如果未能解决你的问题,请参考以下文章

在我的应用程序中从我的 Today Extension(小部件)打开 Safari

如何从我的应用程序关闭 Safari

XCODE swift - webview 无法打开 ics 链接 - 在 safari 中有效

如何让链接在 iOS 中使用 Xcode/Swift 自动打开 safari 应用程序(不是本地)?

从app中的Safari链接打开safari

如何从 Web 视图打开 Safari 视图控制器(swift)