web-dev-qa-db-fra.com

CNContactViewController forUnknownContact inutilisable, détruit l'interface

[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:

  1. Appuyez sur le bouton (Personne inconnue).

  2. Accorder l'accès si demandé.

  3. Le contact partiel s’affiche dans notre interface de navigation (remarquez le bouton Précédent en haut).

  4. Appuyez sur Ajouter au contact existant. Le sélecteur de contact apparaît.

  5. 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.

  6. 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:

 enter image description here

Appuyez sur Ajouter au contact existant pour voir ceci:

 enter image description here

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:

 enter image description here

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.

29
matt

Évidemment, il s’agit d’un bogue, puisque Apple a finalement répondu à mon rapport de bogue en le déclarant en double.

7
matt

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.

8
Giammy

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

4
Roudger

Ê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.

3
Leo Natan

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
    }
}

 enter image description here

0
iAugus

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.

0
Itergator