【问题标题】:CNContactViewController forUnknownContact unusable, destroys interfaceCNContactViewController forUnknownContact 不可用,破坏接口
【发布时间】:2016-01-03 13:33:23
【问题描述】:

[似乎已在 iOS 10 中修复!] 那么以下内容仅适用于 iOS 9...


我一直在尝试使用 Apple 的新联系人框架,并且在 CNContactViewController 的三种形式之一中发现了一个巨大的错误。它破坏了周围的界面,使您的应用程序变得无用;用户卡住了。

为了让这个错误更容易被发现,我在https://github.com/mattneub/CNContactViewControllerBug 发布了一个示例项目。

要进行实验,请运行项目并执行以下步骤:

  1. 点击按钮(未知人物)。

  2. 根据要求授予访问权限。

  3. 在我们的导航界面中,您会看到部分联系人(请注意顶部的“返回”按钮)。

  4. 点击添加到现有联系人。出现联系人选择器。

  5. 点击取消。从这里开始做什么实际上并不重要,但点击取消是最简单的,也是解决问题的最快方法。

  6. 我们现在回到了部分联系,但导航界面不见了。用户无法逃离这个界面。 该应用程序已关闭。

澄清一下,这里是您需要采取的步骤的屏幕截图:

点击添加到现有联系人以查看:

点击取消查看此内容;观察它和第一个屏幕截图一样,但是导航栏不见了

我尝试了很多方法来解决这个错误,但似乎没有办法。据我所知,这个窗口是由“进程外”框架呈现的,而不是你的应用程序的一部分。你无法摆脱它。

那么问题是什么?我想是这样的:谁能告诉我一种使这个视图控制器(以这种形式)可用的方法?有没有我没有找到的解决方法?

编辑 这个错误出现在 iOS 9.0 中,并且仍然存在于 iOS 9.1 中。在评论中,@SergeySkopus 报告说切换到已弃用的通讯簿框架并没有帮助。错误在某处的底层结构中。

【问题讨论】:

  • 这些测试是使用 iOS 9 和(在设备上)iOS 9.0.2 执行的。以雷达 22984883 的形式向 Apple 报告。
  • 如果您找到解决方法,请更新此帖子。有类似的问题。在 iPad 上更糟糕的是,如果你使用 [CNContactViewController viewControllerForContact:] 然后尝试编辑生日,同样的事情会发生……导航栏完全消失,让用户卡在这个屏幕上。看起来这个问题在 iOS9.1 (beta3) 中也没有得到修复。
  • @matt: 如果你Create New Contact > Cancel,导航应该返回
  • @matt:确实是的——这很糟糕,因为导航完全离开了猫王的建筑物。不幸的是,这是一个严重的错误,除了 Apple 实际修复它之外,我无法看到任何解决方案。 可能值得尝试的一件事是在消失的 UINavigationBar 上方设置另一个 UINavigationBar 并且不允许视图使用整个屏幕,尽管它也可能会消失。
  • 谢谢@Andrew - 你可能想添加一个注释来提醒我现有的 bug Radar 22984883。

标签: ios9 cncontact


【解决方案1】:

我已经通过使用类别隐藏了 UINavigationController 方法来显示或隐藏导航栏:

@interface UINavigationController (contacts)
@end

@implementation UINavigationController (contacts)

- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
    NSLog(@"Hide: %d", hidden);
}
@end

这样 CNContactViewController 不能让导航栏消失。在NSLog上设置断点发现这个方法是被私有的[CNContactViewController isPresentingFullscreen:]调用的。

通过检查导航控制器的self.topViewController 是否属于CNContactViewController 类,您可以决定是否隐藏导航栏。

【讨论】:

  • 非常聪明!问题是现在当您召唤联系人列表时导航栏不会消失(例如,您点击添加到现有联系人)。那是错误的。但是您肯定已经指出问题出在哪里:联系人列表应该出现在 everything 的前面,但它只出现在导航控制器的子视图前面。这一定是他们隐藏导航栏的原因。
  • 不幸的是,这里发布的解决方案有副作用,例如在MFMailComposeViewController 中禁用发送按钮。我想method-swizzling可以用来调用原来的setNavigationBarHidden:animated:方法。
【解决方案2】:

显然这是一个错误,因为 Apple 终于通过声明重复来回应我的错误报告。

【讨论】:

  • @mattt 你能分享那个报告的错误的链接吗
  • 这是我在Open Radar 上找到的一个。我在 iPhone 上使用 UIPopoverPresentationController 时遇到了同样的问题(在 iPad 上非常完美)...
【解决方案3】:

我发现使“CNContactViewController forUnknownContact”可用的唯一方法是放弃导航栏并使用工具栏来退出这样的模式视图(在 Objective C 中):

CNContactViewController *picker = [CNContactViewController viewControllerForUnknownContact: newContact];
picker.delegate = self;

UINavigationController *newNavigationController = [[UINavigationController alloc] initWithRootViewController:picker];

UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStyleDone target:self action:@selector(YourDismissFunction)];
UIBarButtonItem *flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
[picker setToolbarItems:[[NSArray alloc] initWithObjects:flexibleSpace, doneButton, flexibleSpace, nil] animated:NO];

newNavigationController.toolbarHidden = NO;
picker.edgesForExtendedLayout = UIRectEdgeNone;

[self presentViewController:newNavigationController animated:YES completion:nil];

希望对你有帮助

【讨论】:

  • 非常聪明,它确实为用户提供了离开界面的方法——导航栏被破坏,但工具栏没有,所以完成按钮仍然存在并且可以使用。不幸的是,这也给了我们一个空的导航栏。即使您隐藏导航栏开始,如果您执行创建新联系人然后取消,它会重新出现。因此,我认为我更喜欢其他解决方法。
  • 非常有帮助,我只是用initWithBarButtonSystemItem:target:action:而不是initWithTitle:style:target:action:来避免翻译另一个词。
  • 这真的是唯一帮助我让它在 03.2020 工作的东西。愚蠢的。
【解决方案4】:

您对非常私密的 API 修复感兴趣吗?

@implementation CNContactViewController (Debug)

+ (void)load
{
    Method m1 = class_getInstanceMethod([CNContactViewController class], NSSelectorFromString(@"".underscore.s.h.o.u.l.d.B.e.O.u.t.O.f.P.r.o.c.e.s.s));
    Method m2 = class_getInstanceMethod([CNContactViewController class], @selector(checkStatus));

    method_exchangeImplementations(m1, m2);
}

- (BOOL)checkStatus
{
    //Leo: Fix bug where in-process contact view controller crashes if there is no access to local contacts.
    BOOL result;
    if([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts] == CNAuthorizationStatusAuthorized)
    {
        result = NO;
    }
    else {
        result = YES;
    }

    return result;
}

@end

这是一个“神奇”的解决方案,可以恢复 Apple 对有缺陷的 XPC 控制器的使用。解决了现代CN 控制器以及内部使用CN 控制器的传统AB 控制器中的许多问题。

【讨论】:

  • 方法 m1 使用什么选择器?
  • @AshutoshDave 从答案中阅读。
【解决方案5】:

这是我很高兴看到我并不孤单的问题之一。

我在使用 CNContactViewController(contact:) 显示联系人时遇到了同样的问题。

当点击图像或“共享联系人”时,根 CNContactViewController 中的导航栏会消失,从而使用户卡住。从 iOS 9.3.3 开始,此问题尚未得到修复。

此时我的解决方案是使用 uitoolbar。问题是它总是出现在底部,即使联系人的图像数据全屏显示也是如此。

// initialise new contact view controller to display with contact
                let contactVC = CNContactViewController(forContact: contact!)

                // set view controller delegate
                contactVC.delegate = self

                // set view controller contact store
                contactVC.contactStore = self.store

                // enable actions
                contactVC.allowsActions = true

                // disable editing
                contactVC.allowsEditing = false

                // add cancel button
                let cancelButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Cancel, target: self, action: #selector(dismissContactVC(_:)))

                // add flexible space
                let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)

                // add to toolbar
                contactVC.setToolbarItems([flexibleSpace, cancelButton, flexibleSpace], animated: false)

                // contact view controller must be embedded in navigation controller
                // initialise navigation controller with contact view controller as root
                let navigationVC = SubClassNavigationController(rootViewController: contactVC)

                // show toolbar
                navigationVC.setToolbarHidden(false, animated: false)

                // set navigation presentation style
                navigationVC.modalPresentationStyle = UIModalPresentationStyle.CurrentContext

                // present view controller
                self.presentViewController(navigationVC, animated: true, completion: nil)

之后,当您第一次展示 cncontactviewcontroller 时会出现一个空白导航栏,因此为了删除它,我将 uinavigationcontroller 子类化,并在 viewWillAppear(animated:) 中调用函数 setnavigationbar(hidden: animated:) 来隐藏导航栏。

我希望 Apple 尽快解决这个问题,因为这是一个不太理想的解决方案。

【讨论】:

    【解决方案6】:

    这个问题很容易解决。子类 CNContactViewController 并在 viewDidAppear 方法中首先调用超类,然后立即使用调用 dismissViewController 的操作方法设置 leftBarButtonItem。还要确保将 viewController 嵌入到导航控制器中。

    【讨论】:

      【解决方案7】:

      好吧,我暂时找到了三种解决问题的方法。

      Swift 2.2 版本:


      选项 1:摇动设备以显示导航栏或直接关闭

      class CustomContactViewController: CNContactViewController {
      
          override func viewDidLoad() {
              super.viewDidLoad()
              UIApplication.sharedApplication().applicationSupportsShakeToEdit = true
          }
      
          override func canBecomeFirstResponder() -> Bool {
              return true
          }        
      
          override func viewWillAppear(animated: Bool) {
              super.viewWillAppear(animated)            
              becomeFirstResponder()
          }
      
          override func viewWillDisappear(animated: Bool) {
              super.viewWillDisappear(animated)            
              resignFirstResponder()
              UIApplication.sharedApplication().applicationSupportsShakeToEdit = false
          }
      
          override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
              navigationController?.setNavigationBarHidden(false, animated: true)
      
              // or just dismiss
              // dismissViewControllerAnimated(true, completion: nil)
      
              // or pop
              // navigationController?.popViewControllerAnimated(true)
      
          }
      }
      


      选项 2:设置计时器以强制显示导航栏。但是……这也带来了一个新问题,你不能编辑或分享联系人头像。

      class CustomContactViewController: CNContactViewController {
      
          var timer: NSTimer?
      
          override func viewDidLoad() {
              super.viewDidLoad()
              timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(showNavigationBar), userInfo: nil, repeats: true)
          }
      
          override func viewDidAppear(animated: Bool) {
              super.viewDidAppear(animated)
              timer?.fire()
          }
      
          override func viewWillDisappear(animated: Bool) {
              super.viewWillDisappear(animated)
              timer?.invalidate()
          }
      
          @objc private func showNavigationBar() {
              navigationController?.setNavigationBarHidden(false, animated: true)
          }
      }
      


      选项 3:在最顶部的视图上创建一个关闭按钮。

      class CustomContactViewController: CNContactViewController {
      
          override func viewDidLoad() {
              super.viewDidLoad()
              configureDismissButton()
          }
      
          private func configureDismissButton() {
      
              guard let topView = UIApplication.topMostViewController?.view else { return }
      
              let button = UIButton()
              button.setImage(UIImage(named: "close"), forState: .Normal)
              button.addTarget(self, action: #selector(dismissViewController), forControlEvents: .TouchUpInside)
              topView.addSubview(button)
      
              // just use SnapKit to set AutoLayout
              button.snp_makeConstraints { (make) in
                  make.width.height.equalTo(36)
                  make.bottom.equalTo(8)
                  make.left.equalTo(-8)
              }
          }
      
          @objc private func dismissViewController() {
              dismissViewControllerAnimated(true, completion: nil)
          }
      
          var topMostViewController: UIViewController? {
              var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
              while topController?.presentedViewController != nil {
                  topController = topController?.presentedViewController
              }
              return topController
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-09-15
        • 1970-01-01
        • 2011-06-08
        • 1970-01-01
        • 2022-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多