Folha de ação de sobreposição de teclado no iOS 13.1 no CNContactViewController


12

Isso parece ser específico para o iOS 13.1, pois funciona como esperado no iOS 13.0 e versões anteriores para adicionar um contato no CNContactViewController, se eu 'Cancelar', a folha de ação está sobreposta pelo teclado. Nenhuma ação é executada e o teclado não está descartando.

Respostas:


5

Parabéns ao @GxocT pela excelente solução! Ajudei imensamente meus usuários.
Mas eu queria compartilhar meu código com base na solução @GxocT, esperando que ajude outras pessoas nesse cenário.

Eu precisava que meu CNContactViewControllerDelegate contactViewController(_:didCompleteWith:)cancelamento fosse cancelado (além de concluído).

Além disso, meu código não estava em um UIViewControllermodo que não háself.navigationController

Eu também não gosto de usar força para desembrulhar quando posso ajudá-lo. Eu fui mordido no passado, então eu acorrentei if letna configuração

Aqui está o que eu fiz:

  1. Estenda CNContactViewControllere coloque a função swizzle
    lá.

  2. No meu caso na função swizzle basta ligar para o
    CNContactViewControllerDelegatedelegado
    contactViewController(_:didCompleteWith:)com selfe
    self.contactobjeto do controlador de contato

  3. No código de instalação, verifique se a chamada swizzleMethod class_getInstanceMethodespecifica a CNContactViewController classe em vez deself

E o código Swift:

class MyClass: CNContactViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.changeImplementation()
    }

    func changeCancelImplementation() {

        let originalSelector = Selector(("editCancel:"))
        let swizzledSelector = #selector(CNContactViewController.cancelHack)

        if let originalMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), originalSelector),
           let swizzledMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), swizzledSelector) {

            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

   func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
       // dismiss the contacts controller as usual
       viewController.dismiss(animated: true, completion: nil)
       // do other stuff when your contact is canceled or saved
       ...
    }
}

extension CNContactViewController {
    @objc func cancelHack()  {
        self.delegate?.contactViewController?(self, didCompleteWith: self.contact)
    }
}

O teclado ainda aparece momentaneamente, mas cai logo após o controlador de contatos ser descartado.
Permite que a Apple conserte isso


forçar o desembrulhamento é usado para tornar o código compacto, é claro que você deve evitá-lo se possível e se pode levar a uma falha. btw eu não tenho certeza se ele está correto para passar self.contact para delegado, porque ele provavelmente não é criado se você cancelar o fluxo ps: mudou a minha aplicação para desembrulha força evitar: D
GxocT

@GxocT - concordou com os esforços da força. E nem sempre são terríveis, mas outros não são como você e podem sempre usá-los sem perceber o risco;). Eu gosto se deixar em vez de! quando não tenho 100% de certeza de que não será nulo. Sobre o self.contact - é verdade Eu não sei o que o código lib da Apple normalmente passa para o delegado internamente, mas self.contact tinha os dados de contato e o documento CNContactViewController diz que é "o contato sendo exibido", por isso parecia bem usar. Meu código na verdade não usa o contato passado no delegado de conclusão para que eu pudesse passar nulo na extensão.
Barrett

O conteúdo (incluindo exibições de texto e teclados) no CNContactViewController deve estar em um processo separado. Se você pode usar 'View Hierarchy' no Xcode para este controlador de exibição, poderá achar que o conteúdo não pode ser visto. Como resultado, não podemos controlar o teclado nem a exibição de texto.
WildCat 9/10/19

5

Não consegui encontrar uma maneira de descartar o teclado. Mas pelo menos você pode exibir o ViewController usando meu método.

  1. Não sei por que, mas é impossível descartar o teclado no CNContactViewController. Tentei endEditing :, crie um novo UITextField firstResponder e assim por diante. Nada funcionou.
  2. Tentei alterar a ação do botão "Cancelar". Você pode encontrar esse botão na pilha NavigationController, mas sua ação é alterada toda vez que você digita alguma coisa.
  3. Finalmente, usei o método swizzling. Não consegui encontrar uma maneira de descartar o teclado como mencionei anteriormente, mas pelo menos você pode descartar o CNContactViewController quando o botão "Cancelar" for pressionado.
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        changeImplementation()
    }

    @IBAction func userPressedButton(_ sender: Any) {
        let controller = CNContactViewController(forNewContact: nil)
        controller.delegate = self
        navigationController?.pushViewController(controller, animated: true)
    }

    @objc func popController() {
        self.navigationController?.popViewController(animated: true)
    }

    func changeImplementation() {
        let originalSelector = Selector("editCancel:")
        let swizzledSelector = #selector(self.popController)

        if let originalMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), originalSelector),
            let swizzledMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), swizzledSelector) {

            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}

PS: Você pode encontrar informações adicionais sobre o tópico do reddit: https://www.reddit.com/r/swift/comments/dc9n3a/bug_with_cnviewcontroller_ios_131/


2

De fato, o usuário pode deslizar para baixo para descartar o teclado e depois tocar em Cancelar e ver a folha de ação. Portanto, esse problema é lamentável e definitivamente um bug (e eu arquivei um relatório de bug), mas não fatal (embora, com certeza, a solução alternativa não seja trivial para o usuário descobrir).

insira a descrição da imagem aqui


Você poderia criar um link para o relatório de bug que você registrou?
Paaske 10/12/19


1

Obrigado @Gxoct por seu excelente trabalho. Acho que essa é uma pergunta e um post muito útil para quem está trabalhando CNContactViewController. Eu também tive esse problema (até agora), mas no objetivo c. Interpreto o código Swift acima no objetivo c.

- (void)viewDidLoad {
    [super viewDidLoad];
    Class class = [CNContactViewController class];

    SEL originalSelector = @selector(editCancel:);
    SEL swizzledSelector = @selector(dismiss); // we will gonna access this method & redirect the delegate via this method

    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod =
        class_addMethod(class,
            originalSelector,
            method_getImplementation(swizzledMethod),
            method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
            swizzledSelector,
            method_getImplementation(originalMethod),
            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

Criando uma CNContactViewControllercategoria para acessar dispensar;

@implementation CNContactViewController (Test)

- (void) dismiss{
    [self.delegate contactViewController:self didCompleteWithContact:self.contact];
}

@end

Caras que não estão tão familiarizados com o Swizzling, você pode tentar este post por matt


0

Obrigado, @GxocT pela sua solução alternativa, no entanto, a solução postada aqui é diferente da que você postou no Reddit.

O do Reddit funciona para mim, este não, então eu quero republicá-lo aqui. A diferença está na linha com swizzledMethod, que deve ser:

   let swizzledMethod = class_getInstanceMethod(object_getClass(self), swizzledSelector) {

Todo o código atualizado é:

class MyClass: CNContactViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.changeImplementation()
    }

    func changeCancelImplementation() {

        let originalSelector = Selector(("editCancel:"))
        let swizzledSelector = #selector(CNContactViewController.cancelHack)

        if let originalMethod = class_getInstanceMethod(object_getClass(CNContactViewController()), originalSelector),
           let swizzledMethod = class_getInstanceMethod(object_getClass(self), swizzledSelector) {

            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

   func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
       // dismiss the contacts controller as usual
       viewController.dismiss(animated: true, completion: nil)
       // do other stuff when your contact is canceled or saved
       ...
    }
}

extension CNContactViewController {
    @objc func cancelHack()  {
        self.delegate?.contactViewController?(self, didCompleteWith: self.contact)
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.