[Semble être corrigé dans iOS 10!] Ce qui suit ne s'applique donc qu'à iOS 9 ...
J'ai expérimenté le nouveau framework de contacts d'Apple et j'ai découvert un énorme bug dans l'une des trois formes de CNContactViewController. Il détruit l'interface environnante pour que votre application devienne inutile. l'utilisateur est bloqué.
Pour rendre ce bogue facile à voir, j'ai posté un exemple de projet à https://github.com/mattneub/CNContactViewControllerBug .
Pour expérimenter, exécutez le projet et procédez comme suit:
Appuyez sur le bouton (Personne inconnue).
Accorder l'accès si demandé.
Le contact partiel s’affiche dans notre interface de navigation (remarquez le bouton Précédent en haut).
Appuyez sur Ajouter au contact existant. Le sélecteur de contact apparaît.
Appuyez sur Annuler. Peu importe ce que vous ferez à partir d’ici, mais appuyer sur Annuler est le plus simple et le moyen le plus rapide d’atteindre le bogue.
Nous sommes de retour au contact partiel, mais l'interface de navigation a disparu. L'utilisateur n'a aucun moyen d'échapper à cette interface. _ {L'application est arrosée.
Juste pour clarifier, voici des captures d'écran des étapes à suivre:
Appuyez sur Ajouter au contact existant pour voir ceci:
Appuyez sur Annuler pour voir ceci; observez que c'est la même chose que la première capture d'écran, mais la barre de navigation est partie:
J'ai essayé de nombreuses façons de contourner ce bogue, mais il semble n'y avoir aucun moyen. Autant que je sache, cette fenêtre est présentée par le cadre "hors processus" et ne fait pas partie de votre application. Vous ne pouvez pas vous en débarrasser.
Alors quelle est la question? Je suppose que c’est ceci: est-ce que quelqu'un peut me montrer un moyen de rendre ce contrôleur de vue (sous cette forme) utilisable? Existe-t-il une solution de contournement que je n'ai pas trouvée?
EDIT Ce bogue est apparu dans iOS 9.0 et est toujours présent dans iOS 9.1. Dans un commentaire, @SergeySkopus indique que le passage au framework de carnet d'adresses obsolète n'aide pas. le bogue est quelque part dans la structure sous-jacente.
Évidemment, il s’agit d’un bogue, puisque Apple a finalement répondu à mon rapport de bogue en le déclarant en double.
J'ai caché la méthode UINavigationController pour afficher ou masquer la barre de navigation à l'aide de catégories:
@interface UINavigationController (contacts)
@end
@implementation UINavigationController (contacts)
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
NSLog(@"Hide: %d", hidden);
}
@end
De cette manière, CNContactViewController ne peut pas faire disparaître la barre de navigation. Définition d'un point d'arrêt sur NSLog J'ai découvert que cette méthode est appelée par le [CNContactViewController isPresentingFullscreen:]
privé.
En vérifiant si le self.topViewController
du contrôleur de navigation est une sorte de classe CNContactViewController
, vous pourrez décider de masquer ou non la barre de navigation.
Le seul moyen que j'ai trouvé pour rendre "CNContactViewController forUnknownContact" utilisable est d'abandonner la barre de navigation et d'utiliser une barre d'outils pour quitter la vue modale comme ceci (dans 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];
en espérant que cela pourrait aider
Êtes-vous intéressé par un correctif API très privé?
@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
Il s'agit d'une solution "magique" qui rétablit l'utilisation par Apple des contrôleurs XPC bogués. Résout de nombreux problèmes dans les contrôleurs CN
modernes, ainsi que dans les contrôleurs AB
hérités, qui utilisent ceux CN
en interne.
Eh bien, j'ai trouvé trois façons de résoudre le problème temporairement.
Version Swift 2.2:
Option 1: secouez l'appareil pour afficher la barre de navigation ou rejetez-le directement
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)
}
}
Option 2: Définissez une minuterie pour forcer l'affichage de la barre de navigation. Mais ... cela crée également un nouveau problème, vous ne pouvez ni éditer ni partager l'avatar de contact.
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)
}
}
Option 3: Créer un bouton de rejet dans la vue la plus en haut.
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
}
}
C’est l’un de ces problèmes, j’ai été heureux de constater que je n’étais pas seul.
J'ai le même problème lors de l'affichage d'un contact à l'aide de CNContactViewController (contact :).
Lorsque l'image ou le 'contact partagé' était sélectionné, la barre de navigation dans la racine CNContactViewController disparaissait, ce qui rendait l'utilisateur bloqué. Cela n'a pas été corrigé depuis iOS 9.3.3.
La solution pour moi à ce stade est d'utiliser le uitoolbar. Le problème est que cela apparaît tout le temps en bas, même avec les données d'image du contact en plein écran.
// 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)
Après cela, une barre de navigation vide apparaît lorsque vous présentez cncontactviewcontroller afin de supprimer ce contrôleur sous-classé uinavigationcontroller et dans viewWillAppear (animated :), la fonction setnavigationbar (hidden: animated :) permet de masquer la barre de navigation.
J'espère que Apple corrigera cela rapidement car il s'agit d'une solution moins qu'idéale.