CNContactViewController forUnknownContact inutilisable, détruit l'interface

[Semble être résolu dans iOS 10!] donc ce qui suit s'applique à iOS 9 seulement...


J'ai expérimenté le nouveau cadre de Contacts D'Apple, et j'ai trouvé un gros bug dans l'une des trois formes de CNContactViewController. Il détruit l'interface environnante de sorte que votre application devient inutile; l'utilisateur est coincé.

Pour faire ce bug facile à voir, j'ai posté un exemple de projet à https://github.com/mattneub/CNContactViewControllerBug.

À expérimenter, à exécuter le projet et de faire les étapes suivantes:

  1. Appuyez sur le bouton (Personne Inconnue).

  2. accorder l'accès sur demande.

  3. vous êtes affiché le contact partiel, dans notre interface de navigation (notez le bouton de retour en haut).

  4. Tap ajouter au Contact existant. Le sélecteur de contacts apparaît.

  5. Appuyez Sur Annuler. Ce n'est pas vraiment important ce que vous faites d'ici, mais taper Annuler est le moyen le plus simple et le plus rapide pour atteindre le bug.

  6. nous sommes maintenant de retour au contact partiel, mais l'interface de navigation a disparu. L'utilisateur n'a aucun moyen de s'échapper de cette interface. L'application est modifié.

juste pour clarifier, voici des screenshots des étapes dont vous avez besoin pour prendre:

enter image description here

Tap ajouter au Contact existant pour voir ceci:

enter image description here

Appuyez sur Annuler pour voir ce; observer que c'est la même que la première capture d'écran, mais la barre de navigation a disparu!--38-->:

enter image description here

j'ai essayé de nombreuses façons de contourner ce bug, mais il semble y avoir aucun moyen. Aussi loin que je peux dire, cette fenêtre est présenté par le cadre "hors processus" et ne fait pas partie de votre application. Vous ne pouvez pas se débarrasser de lui.

alors quelle est la question? Je suppose que c'est ceci: quelqu'un peut-il me montrer un moyen de rendre ce contrôleur de vue (sous cette forme) utilisable? Est-il une solution que je n'ai pas trouvé?

EDIT ce bogue est apparu dans iOS 9.0 et est toujours présent dans iOS 9.1. Dans un commentaire, @SergeySkopus rapporte que le fait de passer au cadre du carnet d'adresses déprécié n'aide pas; le bug est dans la structure sous-jacente quelque part.

26
demandé sur matt 2015-10-06 18:00:30

7 réponses

j'ai caché la méthode UINavigationController pour afficher ou masquer la barre de navigation en utilisant les catégories:

@interface UINavigationController (contacts)
@end

@implementation UINavigationController (contacts)

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

de cette façon, le contrôleur CNContactViewController ne peut pas faire disparaître la barre de navigation. Définir un point de rupture sur NSLog j'ai découvert que cette méthode est appelée par le privé [CNContactViewController isPresentingFullscreen:].

En vérifiant si le self.topViewController du contrôleur de navigation est une sorte de classe CNContactViewController vous pouvez décider si vous cachez ou pas la barre de navigation.

8
répondu Giammy 2015-12-15 12:29:34

évidemment, C'est un bug, puisque Apple a finalement répondu à mon rapport de bug en le déclarant en double.

6
répondu matt 2015-11-17 16:31:43

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 sortir de la vue modale comme ceci (dans L'objectif 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 puisse aider

4
répondu Roudger 2016-01-17 22:41:07

Êtes-vous intéressé par une solution API très privée?

@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 inverse L'utilisation des contrôleurs XPC buggy par Apple. Résout beaucoup de problèmes, à la fois moderne CN contrôleurs, ainsi que l'héritage AB contrôleurs, qui utilisent l' CN intérieurement.

2
répondu Leo Natan 2017-04-03 22:27:38

Eh bien, j'ai trouvé trois façons de résoudre le problème temporairement.

Swift 2.2 Version:



Option 1: Secouer l'appareil pour afficher la barre de navigation ou de les rejeter 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éfinir une minuterie pour forcer la barre de navigation à s'afficher. Mais... il crée aussi un nouveau problème, vous ne pouvez pas éditer ou 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 suppression sur le dessus la plupart vue.

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
répondu iAugus 2016-05-22 15:01:43

C'est l'une de ces questions, j'ai été heureux de voir que je n'étais pas seule.

j'ai le même problème lors de l'affichage d'un contact à l'aide de CNContactViewController(contact:).

lorsque l'image ou 'share contact' est tapé la barre de navigation dans le CNContactViewController racine disparaîtrait faisant l'utilisateur coincé. Ceci n'a pas été corrigé à partir de iOS 9.3.3.

La solution pour moi à ce point dans le temps est d'utiliser la uitoolbar. Le problème est que cela apparaît à la bas tout le temps, même avec les données d'image pour le 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 Vierge apparaît lorsque vous présentez pour la première fois le cncontactviewcontroller alors pour supprimer ce sous-Clas sé uinavigationcontroller, et dans viewWillAppear(animé:) j'appelle la fonction setnavigationbar(caché: animé:) pour cacher la barre de navigation.

J'espère Qu'Apple corrigera cela dès que ce sera une solution loin d'être idéale.

0
répondu Itergator 2016-07-29 12:22:17

Ce problème peut facilement résolu. Sous-classe CNContactViewController et dans la méthode viewDidAppear appeler d'abord la classe super puis immédiatement après définir le leftBarButtonItem avec une méthode d'action qui appelle dismissViewController. Assurez-vous également que vous avez intégré ce viewController dans un contrôleur de navigation.

-1
répondu mcoding 2016-04-16 19:51:47