This seems to be specific to iOS 13.1, as it works as expected on iOS 13.0 and earlier versions to add a contact in CNContactViewController, if I 'Cancel', the action sheet is overlapping by keyboard. No actions getting performed and keyboard is not dismissing.
7 Answers
I couldn't find a way to dismiss keyboard. But at least you can pop ViewController using my method.
- Don't know why but it's impossible to dismiss keyboard in CNContactViewController. I tried endEditing:, make new UITextField firstResponder and so on. Nothing worked.
- I tried to alter action for "Cancel" button. You can find this button in NavigationController stack, But it's action is changed every time you type something.
- Finally I used method swizzling. I couldn't find a way to dismiss keyboard as I mentioned earlier, but at least you can dismiss CNContactViewController when "Cancel" button is pressed.
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: You can find additional info on reddit topic: https://www.reddit.com/r/swift/comments/dc9n3a/bug_with_cnviewcontroller_ios_131/

- 764
- 1
- 10
- 20
Kudos to @GxocT for the the great workaround! Helped my users immensely.
But I wanted to share my code based on @GxocT solution hoping it will help others in this scenario.
I needed my CNContactViewControllerDelegate
contactViewController(_:didCompleteWith:)
to be called on cancel (as well as done).
Also my code was not in a UIViewController
so there is no self.navigationController
I also dont like using force unwraps when I can help it. I have been bitten in the past so I chained if let
s in the setup
Here's what I did:
Extend
CNContactViewController
and place the swizzle function in
there.In my case in the swizzle function just call the
CNContactViewControllerDelegate
delegate
contactViewController(_:didCompleteWith:)
withself
and
self.contact
object from the contact controllerIn the setup code, make sure the swizzleMethod call to
class_getInstanceMethod
specifies theCNContactViewController
class instead ofself
And the Swift code:
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)
}
}
The keyboard still shows momentarily but drops just after the Contacts controller dismisses.
Lets hope apple fixes this

- 470
- 4
- 11
-
force unwraps are used to make code compact of course you should avoid them if possible and if it may lead to crash. btw i'm not sure if it's correct to pass self.contact to delegate, cause it's probably not created if you cancel flow ps: changed my implementation to avoid force unwraps :D – GxocT Oct 04 '19 at 06:15
-
@GxocT - agreed on the force urwraps. And they're not not always terrible but others are not like you and may always use them without realizing the risk ;) . I like if let instead of ! when i'm not 100% sure it wont be nil. About the self.contact - its true I dont know what Apple's lib code normally passes to the delegate internally but self.contact had the contact data and the CNContactViewController doc says it is "the contact being displayed" so it seemed ok to use. My code doesnt actually use the contact passed in the completion delegate so I could just pass nil in the extension. – Barrett Oct 04 '19 at 18:13
-
The contents (incl. text views and keyboards) in CNContactViewController should be in a separate process. If you can use 'View Hierarchy' in Xcode for this view controller, you may find the contents cannot be seen. As a result, we cannot control the keyboard nor the text view. – WildCat Oct 09 '19 at 05:51
NOTE: This bug is now fixed. This question and answer were applicable only to some particular versions of iOS (a limited range of iOS 13 versions).
The user can in fact swipe down to dismiss the keyboard and then tap Cancel and see the action sheet. So this issue is regrettable and definitely a bug (and I have filed a bug report) but not fatal (though, to be sure, the workaround is not trivial for the user to discover).

- 515,959
- 87
- 875
- 1,141
Thanks, @GxocT for your workaround, however, the solution posted here is different from the one you posted on Reddit.
The one on Reddit works for me, this one doesn't so I want to repost it here. The difference is on the line with swizzledMethod which should be:
let swizzledMethod = class_getInstanceMethod(object_getClass(self), swizzledSelector) {
The whole updated code is:
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)
}
}

- 31
- 3
Thanks @Gxoct for his excellent work around. I think this is very useful question & post for those who are working with CNContactViewController
. I also had this problem (till now) but in objective c. I interpret the above Swift code into objective 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);
}
}
Creating a CNContactViewController
category for accessing dismiss;
@implementation CNContactViewController (Test)
- (void) dismiss{
[self.delegate contactViewController:self didCompleteWithContact:self.contact];
}
@end
Guys who are not so familiar with Swizzling you may try this post by matt

- 813
- 1
- 9
- 19
One thing to always take into account is that swizzler method is executed only once. Make sure that you implement changeCancelImplementation() in dispatch_once queue so that it is executed only once.
Check this link for description
Also this bug is found only in iOS 13.1, 13.2 and 13.3

- 1,106
- 13
- 15