5

When I push a CNContactViewController on to the stack of UITableViewController subclass which is within a UINavigationController, the top navigation bar is almost entirely hidden. But with the brightness all the way up, you make out the back arrow followed by the word "Detail", and the system status bar. When I tap that corner of the screen, the CNContactViewController is indeed dismissed.

enter image description here

Of course this is not good, since a user would probably not even see the text of the navigation bar and now to press any buttons to dismiss.

Is there any way to make the navigation bar tint of the CNContactViewController be the same as the view controller that showed it (the rest of my app)?

CNContactViewController *controller = [CNContactViewController viewControllerForUnknownContact:person];

controller.contactStore = [[CNContactStore alloc] init];
controller.delegate = self;
controller.allowsActions = NO;

[self.navigationController pushViewController:controller animated:YES];

I should note that I'm only experiencing this problem on iOS 10, and not versions below 10. I also do get the properly tinted navigation bar when I tap "Add to Existing Contact", but it breaks again when that view controller is dismissed.

enter image description here

So again, my question is: Is there any way to make the navigation bar tint of the CNContactViewController be the same as the view controller that showed it (the rest of my app)?

matt
  • 515,959
  • 87
  • 875
  • 1,141

2 Answers2

2

Your second screen shot shows the reason for this problem: you have set the tint color for your bar (or bar button items in general) to be white. Hence, they are white in front of the transparent navigation bar and white background in the contact view controller.

You can't do anything directly about the bar tint color, but you can solve this in either of two ways:

  • One is to make your navigation bar nontranslucent. In that case, the contact view controller's navigation bar will be black, and your white bar button items will be visible.

  • Another approach is to change your navigation bar's tint color (not the bar tint color, but the tint color that it communicates down to its bar button items) as the contact view controller pushes, and change it back when it pops.

EDIT Okay, I see that there's a further problem because the New Contact view controller is a further view controller presented in front of yours. If you refuse to give up your white bar button item setting, you will have to use the appearance proxy to set the UIBarButtonItem tint color to something else when your push the contact view controller, and then reset it back to your white when your navigation controller delegate tells you that the user is popping back to your view controller.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    Thanks for your suggestions. I subclassed `CNContactViewController` and overrode the navigation bar colors in `viewWillAppear` and `viewDidAppear` and pushed to an instance of this class. I can see the text now. But now I'm having a similar problem when I tap "Create New Contact". I can't see the text of the buttons in this presumed navigation bar, but "Cancel" and "Save" behaviors are observed when I tap the left and right corners of the screen. Strangely, I don't have any problems when "Add to Existing Contact" is tapped, as you can see in my original question. Any ideas? –  Nov 15 '16 at 17:20
  • I think my original idea was pretty good. — Pls note that the Unknown Contact view controller has always been buggy; in iOS 9 you couldn't use it at all (it just destroyed the interface), as I discuss here: http://stackoverflow.com/questions/32973254/cncontactviewcontroller-forunknowncontact-unusable-destroys-interface – matt Nov 15 '16 at 17:28
  • Thanks for sharing that link. What was your original idea? When I try your first bulleted suggestion, I do see the navigation bar become black with white text against it, but that doesn't happen for the next screen when I tap **Create New Contact**. When I go with my own solution, which is in the same vein as your second bulleted suggestion (I think?), I still can't see the text for the **Create New Contact** screen. I don't know what class of view controller is appearing when tapping said button, so I don't know what class to override in the same way I did for `CNContactViewController`. –  Nov 15 '16 at 17:57
  • 1
    Yes, I was right. You can use the appearance proxy to change the tint color of UIBarButtonItem to some dark color like blue as you push the contact view controller, and change it back your white when the view controller is popped back to your view controller. – matt Nov 15 '16 at 18:05
  • Thank you for the edit, for future viewers, @matt . That is what I figured out with some trial and error a few minutes before your edit. –  Dec 06 '16 at 16:49
  • 2
    @dtorreci4ejg Please note that I am not saying any of this is good. It isn't. This entire notion of "injecting" these view controllers as out-of-process interface on top of your interface is atrocious. You should really be filing a bug report with Apple (bugreport.apple.com). It should not be necessary for you dance all around the moon just to get control of this interface. It's _your_ app, after all, not Apple's! – matt Dec 06 '16 at 17:19
2

I spent hours wrestling with CNContactViewController trying to force it to fit into my UINavigation appearance settings, but it just won't. It has it's own look and feel. I had a look at iOS apps like Mail and Notes to see how they show the CNContactViewController and it seems to be shown as a popover, so I went that way too.

Even that isn't trivial. The CNContactViewController has to be wrapped in a UINavigationView so that the Create New Contact and other views can push. And if you have overridden the UINavigationBar appearance defaults, you need to set them back to standard before and after. Here's how it looks in the end:

@property (strong, nonatomic) CNContactViewController *contactViewController;
@property (assign, nonatomic) UIBarStyle saveAppearanceBarStyle;
@property (strong, nonatomic) UIColor *saveAppearanceBarTintColor;
@property (strong, nonatomic) UIColor *saveAppearanceTintColor;
@property (strong, nonatomic) UIColor *saveAppearanceBackgroundColor;
@property (strong, nonatomic) NSDictionary<NSAttributedStringKey, id> * saveAppearanceTitleTextAttributes;
@property (assign, nonatomic) BOOL saveAppearanceTranslucent;


- (IBAction)onAddToContactsButtonTapped:(id)sender

    self.contactViewController = [CNContactViewController viewControllerForUnknownContact: ... ]; // as before

    [self suspendNavBarAppearanceSettings];

    UINavigationController *popNav = [[UINavigationController alloc] initWithRootViewController:self.contactViewController];
    popNav.modalPresentationStyle = UIModalPresentationPopover;

    UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(onAddToContactsDoneTapped:)];
    self.contactViewController.navigationItem.leftBarButtonItem = doneButton;

    [self presentViewController:popNav animated:YES completion:nil];

    UIPopoverPresentationController *popoverController = newNav.popoverPresentationController;
    popoverController.permittedArrowDirections = UIPopoverArrowDirectionAny;
    popoverController.sourceRect = ...;  // where you want it to point
    popoverController.sourceView = ...;  // where you want it to point
    popoverController.delegate = self;
}

- (void) suspendNavBarAppearanceSettings
{
    self.saveAppearanceBarStyle = [UINavigationBar appearance].barStyle;
    self.saveAppearanceBarTintColor = [UINavigationBar appearance].barTintColor;
    self.saveAppearanceTintColor = [UINavigationBar appearance].tintColor;
    self.saveAppearanceBackgroundColor = [UINavigationBar appearance].backgroundColor;
    self.saveAppearanceTitleTextAttributes = [UINavigationBar appearance].titleTextAttributes;
    self.saveAppearanceTranslucent = [UINavigationBar appearance].translucent;

    [UINavigationBar appearance].barStyle = UIBarStyleDefault;
    [UINavigationBar appearance].barTintColor = nil;
    [UINavigationBar appearance].tintColor = nil;
    [UINavigationBar appearance].backgroundColor = nil;
    [UINavigationBar appearance].titleTextAttributes = nil;
    [UINavigationBar appearance].translucent = YES;
}

- (void) restoreNavBarAppearanceSettings
{
    [UINavigationBar appearance].barStyle = self.saveAppearanceBarStyle;
    [UINavigationBar appearance].barTintColor = self.saveAppearanceBarTintColor;
    [UINavigationBar appearance].tintColor = self.saveAppearanceTintColor;
    [UINavigationBar appearance].backgroundColor = self.saveAppearanceBackgroundColor;
    [UINavigationBar appearance].titleTextAttributes = self.saveAppearanceTitleTextAttributes;
    [UINavigationBar appearance].translucent = self.saveAppearanceTranslucent;
}

- (void)onAddToContactsDoneTapped:(id)sender
{
    [self restoreNavBarAppearanceSettings];

    [[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
}

 #pragma mark - CNContactViewControllerDelegate

 - (void)contactViewController:(CNContactViewController *)viewController
        didCompleteWithContact:(nullable CNContact *)contact
 {
    [self restoreNavBarAppearanceSettings];

    [[self presentedViewController] dismissViewControllerAnimated:YES completion:nil];
 }

#pragma mark - UIPopoverPresentationControllerDelegate

- (void)prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController
{
    // This method is only called if we are presented in a popover, i.e. on iPad
    // as opposed to full screen like on a phone.

    // on iPad you just tap outside to finish, so remove the Done button
    self.contactViewController.navigationItem.leftBarButtonItem = nil;
}

- (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController
{
    [self restoreNavBarAppearanceSettings];
}
Ben
  • 1,881
  • 17
  • 20