ios9 - CNContactViewController forUnknownContact inutilizable, destruye la interfaz
(7)
¿Estás interesado en una solución de API muy privada?
@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
Esta es una solución "mágica" que revierte el uso de Apple de los controladores con errores XPC.
Resuelve muchos problemas tanto en los controladores
CN
modernos como en los controladores
AB
heredados, que usan los
CN
internamente.
[Parece estar arreglado en iOS 10!] Entonces, lo que sigue se aplica solo a iOS 9 ...
He estado experimentando con el nuevo marco de contactos de Apple, y he encontrado un gran error en una de las tres formas de CNContactViewController. Destruye la interfaz circundante para que su aplicación se vuelva inútil; El usuario está atascado.
Para que este error sea fácil de ver, publiqué un proyecto de ejemplo en https://github.com/mattneub/CNContactViewControllerBug .
Para experimentar, ejecute el proyecto y siga los siguientes pasos:
-
Toque el botón (Persona desconocida).
-
Conceder acceso si se solicita.
-
Se le muestra el contacto parcial, en nuestra interfaz de navegación (observe el botón Atrás en la parte superior).
-
Toque Agregar a contacto existente. Aparece el selector de contactos.
-
Toca Cancelar. En realidad no importa lo que hagas desde aquí, pero tocar Cancelar es más simple y es la forma más rápida de llegar al error.
-
Ahora estamos de vuelta en el contacto parcial, pero la interfaz de navegación se ha ido. El usuario no tiene forma de escapar de esta interfaz. La aplicación está manguera.
Solo para aclarar, aquí hay capturas de pantalla de los pasos que debe seguir:
Toque Agregar al contacto existente para ver esto:
Toque Cancelar para ver esto; observe que es lo mismo que la primera captura de pantalla, pero la barra de navegación desapareció :
He intentado muchas formas de evitar este error, pero parece que no hay manera. Por lo que puedo decir, esta ventana está siendo presentada por el marco "fuera de proceso" y no es parte de su aplicación. No puedes deshacerte de él.
Entonces, ¿cuál es la pregunta? Supongo que es esto: ¿alguien puede mostrarme una manera de hacer que este controlador de vista (en esta forma) sea utilizable? ¿Hay alguna solución que no haya encontrado?
EDITAR Este error apareció en iOS 9.0 y todavía está presente en iOS 9.1. En un comentario, @SergeySkopus informa que cambiar al marco obsoleto de la libreta de direcciones no ayuda; El error está en la estructura subyacente en alguna parte.
Bueno, encontré tres formas de resolver el problema TEMPORALMENTE.
Versión Swift 2.2:
Opción 1: Agite el dispositivo para mostrar la barra de navegación o descartar directamente
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)
}
}
Opción 2: configure un temporizador para forzar que se muestre la barra de navegación.
Pero ... también crea un nuevo problema, no puedes editar o compartir el avatar de contacto.
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)
}
}
Opción 3: crear un botón de descartar en la vista superior.
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
}
}
Este es uno de esos problemas que me alegró ver que no estaba solo.
Tengo el mismo problema al mostrar un contacto usando CNContactViewController (contacto :).
Cuando se tocaba la imagen o "compartir contacto", la barra de navegación en la raíz CNContactViewController desaparecería haciendo que el usuario se atascara. Esto no se ha solucionado a partir de iOS 9.3.3.
La solución para mí en este momento es usar la barra de herramientas. El problema es que esto aparece en la parte inferior todo el tiempo, incluso con los datos de imagen para el contacto en pantalla completa.
// 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)
Después de esto, aparece una barra de navegación en blanco cuando presenta por primera vez el cncontactviewcontroller para eliminar este subclases uinavigationcontroller, y en viewWillAppear (animado :) Llamo a la función setnavigationbar (oculto: animado :) para ocultar la barra de navegación.
Espero que Apple solucione esto pronto ya que esta es una solución menos que ideal.
Este problema puede resolverse fácilmente. La subclase CNContactViewController y en el método viewDidAppear primero llaman a la superclase y luego inmediatamente configuran leftBarButtonItem con un método de acción que llama a despedir ViewController. También asegúrese de incrustar ese viewController en un controlador de navegación.
Evidentemente, este es un error, ya que Apple finalmente respondió a mi informe de error declarándolo un duplicado.
He ocultado el método UINavigationController para mostrar u ocultar la barra de navegación usando categorías:
@interface UINavigationController (contacts)
@end
@implementation UINavigationController (contacts)
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
NSLog(@"Hide: %d", hidden);
}
@end
De esta manera, CNContactViewController no puede hacer que la barra de navegación desaparezca.
Al establecer un punto de interrupción en NSLog descubrí que este método es llamado por el privado
[CNContactViewController isPresentingFullscreen:]
.
Al verificar si el
self.topViewController
del controlador de navegación es de clase
CNContactViewController
, puede decidir si oculta o no la barra de navegación.
La única forma que encontré para hacer "CNContactViewController forUnknownContact" utilizable es abandonar la barra de navegación y usar una barra de herramientas para salir de la vista modal como esta (en el Objetivo 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];
esperando que pueda ayudar