mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			410 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Matlab
		
	
		
		
			
		
	
	
			410 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Matlab
		
	
| 
											9 years ago
										 | // | ||
|  | //  Copyright (c) 2017 Open Whisper Systems. All rights reserved. | ||
|  | // | ||
|  | 
 | ||
|  | #import "ContactsViewHelper.h" | ||
|  | #import "Environment.h" | ||
| 
											8 years ago
										 | #import "NSString+OWS.h" | ||
| 
											8 years ago
										 | #import "UIUtil.h" | ||
| 
											8 years ago
										 | #import <SignalMessaging/OWSProfileManager.h> | ||
| 
											8 years ago
										 | #import <SignalMessaging/SignalMessaging-Swift.h> | ||
|  | #import <SignalServiceKit/AppContext.h> | ||
| 
											9 years ago
										 | #import <SignalServiceKit/Contact.h> | ||
|  | #import <SignalServiceKit/OWSBlockingManager.h> | ||
| 
											9 years ago
										 | #import <SignalServiceKit/PhoneNumber.h> | ||
| 
											9 years ago
										 | #import <SignalServiceKit/SignalAccount.h> | ||
| 
											9 years ago
										 | #import <SignalServiceKit/TSAccountManager.h> | ||
|  | 
 | ||
| 
											9 years ago
										 | @import ContactsUI; | ||
|  | 
 | ||
| 
											9 years ago
										 | NS_ASSUME_NONNULL_BEGIN | ||
|  | 
 | ||
|  | @interface ContactsViewHelper () | ||
|  | 
 | ||
| 
											9 years ago
										 | // This property is a cached value that is lazy-populated. | ||
|  | @property (nonatomic, nullable) NSArray<Contact *> *nonSignalContacts; | ||
|  | 
 | ||
| 
											9 years ago
										 | @property (nonatomic) NSDictionary<NSString *, SignalAccount *> *signalAccountMap; | ||
|  | @property (nonatomic) NSArray<SignalAccount *> *signalAccounts; | ||
| 
											9 years ago
										 | 
 | ||
| 
											9 years ago
										 | @property (nonatomic) NSArray<NSString *> *blockedPhoneNumbers; | ||
| 
											9 years ago
										 | 
 | ||
| 
											9 years ago
										 | @property (nonatomic) BOOL shouldNotifyDelegateOfUpdatedContacts; | ||
| 
											9 years ago
										 | @property (nonatomic) BOOL hasUpdatedContactsAtLeastOnce; | ||
| 
											8 years ago
										 | @property (nonatomic) OWSProfileManager *profileManager; | ||
| 
											8 years ago
										 | @property (nonatomic, readonly) ConversationSearcher *conversationSearcher; | ||
| 
											9 years ago
										 | 
 | ||
| 
											9 years ago
										 | @end | ||
|  | 
 | ||
|  | #pragma mark - | ||
|  | 
 | ||
|  | @implementation ContactsViewHelper | ||
|  | 
 | ||
| 
											9 years ago
										 | - (instancetype)initWithDelegate:(id<ContactsViewHelperDelegate>)delegate | ||
| 
											9 years ago
										 | { | ||
|  |     self = [super init]; | ||
|  |     if (!self) { | ||
|  |         return self; | ||
|  |     } | ||
|  | 
 | ||
| 
											9 years ago
										 |     OWSAssert(delegate); | ||
|  |     _delegate = delegate; | ||
|  | 
 | ||
| 
											9 years ago
										 |     _blockingManager = [OWSBlockingManager sharedManager]; | ||
| 
											9 years ago
										 |     _blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers]; | ||
| 
											8 years ago
										 |     _conversationSearcher = ConversationSearcher.shared; | ||
| 
											9 years ago
										 | 
 | ||
| 
											8 years ago
										 |     _contactsManager = [Environment current].contactsManager; | ||
| 
											8 years ago
										 |     _profileManager = [OWSProfileManager sharedManager]; | ||
| 
											9 years ago
										 | 
 | ||
| 
											9 years ago
										 |     // We don't want to notify the delegate in the `updateContacts`. | ||
|  |     self.shouldNotifyDelegateOfUpdatedContacts = YES; | ||
| 
											9 years ago
										 |     [self updateContacts]; | ||
| 
											9 years ago
										 |     self.shouldNotifyDelegateOfUpdatedContacts = NO; | ||
| 
											9 years ago
										 | 
 | ||
|  |     [self observeNotifications]; | ||
|  | 
 | ||
|  |     return self; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)observeNotifications | ||
|  | { | ||
|  |     [[NSNotificationCenter defaultCenter] addObserver:self | ||
| 
											9 years ago
										 |                                              selector:@selector(signalAccountsDidChange:) | ||
|  |                                                  name:OWSContactsManagerSignalAccountsDidChangeNotification | ||
| 
											9 years ago
										 |                                                object:nil]; | ||
|  |     [[NSNotificationCenter defaultCenter] addObserver:self | ||
|  |                                              selector:@selector(blockedPhoneNumbersDidChange:) | ||
|  |                                                  name:kNSNotificationName_BlockedPhoneNumbersDidChange | ||
|  |                                                object:nil]; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)dealloc | ||
|  | { | ||
|  |     [[NSNotificationCenter defaultCenter] removeObserver:self]; | ||
|  | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (void)signalAccountsDidChange:(NSNotification *)notification | ||
| 
											9 years ago
										 | { | ||
| 
											9 years ago
										 |     OWSAssert([NSThread isMainThread]); | ||
|  | 
 | ||
|  |     [self updateContacts]; | ||
| 
											9 years ago
										 | } | ||
|  | 
 | ||
|  | - (void)blockedPhoneNumbersDidChange:(id)notification | ||
|  | { | ||
| 
											9 years ago
										 |     OWSAssert([NSThread isMainThread]); | ||
|  | 
 | ||
|  |     self.blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers]; | ||
| 
											9 years ago
										 | 
 | ||
| 
											9 years ago
										 |     [self updateContacts]; | ||
| 
											9 years ago
										 | } | ||
|  | 
 | ||
|  | #pragma mark - Contacts | ||
|  | 
 | ||
| 
											9 years ago
										 | - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId | ||
| 
											9 years ago
										 | { | ||
|  |     OWSAssert([NSThread isMainThread]); | ||
|  |     OWSAssert(recipientId.length > 0); | ||
|  | 
 | ||
| 
											9 years ago
										 |     return self.signalAccountMap[recipientId]; | ||
| 
											9 years ago
										 | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (BOOL)isSignalAccountHidden:(SignalAccount *)signalAccount | ||
| 
											9 years ago
										 | { | ||
|  |     OWSAssert([NSThread isMainThread]); | ||
|  | 
 | ||
| 
											9 years ago
										 |     if ([self.delegate respondsToSelector:@selector(shouldHideLocalNumber)] && [self.delegate shouldHideLocalNumber] && | ||
|  |         [self isCurrentUser:signalAccount]) { | ||
|  | 
 | ||
| 
											9 years ago
										 |         return YES; | ||
|  |     } | ||
|  | 
 | ||
|  |     return NO; | ||
|  | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (BOOL)isCurrentUser:(SignalAccount *)signalAccount | ||
| 
											9 years ago
										 | { | ||
|  |     OWSAssert([NSThread isMainThread]); | ||
|  | 
 | ||
| 
											9 years ago
										 |     NSString *localNumber = [TSAccountManager localNumber]; | ||
|  |     if ([signalAccount.recipientId isEqualToString:localNumber]) { | ||
|  |         return YES; | ||
|  |     } | ||
|  | 
 | ||
|  |     for (PhoneNumber *phoneNumber in signalAccount.contact.parsedPhoneNumbers) { | ||
|  |         if ([[phoneNumber toE164] isEqualToString:localNumber]) { | ||
| 
											9 years ago
										 |             return YES; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return NO; | ||
|  | } | ||
|  | 
 | ||
|  | - (NSString *)localNumber | ||
|  | { | ||
|  |     return [TSAccountManager localNumber]; | ||
|  | } | ||
|  | 
 | ||
|  | - (BOOL)isRecipientIdBlocked:(NSString *)recipientId | ||
|  | { | ||
|  |     AssertIsOnMainThread(); | ||
|  | 
 | ||
|  |     return [_blockedPhoneNumbers containsObject:recipientId]; | ||
|  | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (void)updateContacts | ||
| 
											9 years ago
										 | { | ||
|  |     AssertIsOnMainThread(); | ||
|  | 
 | ||
| 
											9 years ago
										 |     NSMutableDictionary<NSString *, SignalAccount *> *signalAccountMap = [NSMutableDictionary new]; | ||
|  |     NSMutableArray<SignalAccount *> *signalAccounts = [NSMutableArray new]; | ||
|  |     for (SignalAccount *signalAccount in self.contactsManager.signalAccounts) { | ||
|  |         if (![self isSignalAccountHidden:signalAccount]) { | ||
|  |             signalAccountMap[signalAccount.recipientId] = signalAccount; | ||
|  |             [signalAccounts addObject:signalAccount]; | ||
| 
											9 years ago
										 |         } | ||
|  |     } | ||
| 
											9 years ago
										 |     self.signalAccountMap = [signalAccountMap copy]; | ||
|  |     self.signalAccounts = [signalAccounts copy]; | ||
| 
											9 years ago
										 |     self.nonSignalContacts = nil; | ||
| 
											9 years ago
										 | 
 | ||
| 
											9 years ago
										 |     // Don't fire delegate "change" events during initialization. | ||
| 
											9 years ago
										 |     if (!self.shouldNotifyDelegateOfUpdatedContacts) { | ||
| 
											9 years ago
										 |         [self.delegate contactsViewHelperDidUpdateContacts]; | ||
| 
											9 years ago
										 |         self.hasUpdatedContactsAtLeastOnce = YES; | ||
| 
											9 years ago
										 |     } | ||
| 
											9 years ago
										 | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (NSArray<NSString *> *)searchTermsForSearchString:(NSString *)searchText | ||
|  | { | ||
| 
											8 years ago
										 |     return [[[searchText ows_stripped] | ||
| 
											9 years ago
										 |         componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] | ||
|  |         filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable searchTerm, | ||
|  |                                         NSDictionary<NSString *, id> *_Nullable bindings) { | ||
|  |             return searchTerm.length > 0; | ||
|  |         }]]; | ||
|  | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (NSArray<SignalAccount *> *)signalAccountsMatchingSearchString:(NSString *)searchText | ||
| 
											9 years ago
										 | { | ||
| 
											8 years ago
										 |     return [self.conversationSearcher filterSignalAccounts:self.signalAccounts withSearchText:searchText]; | ||
| 
											9 years ago
										 | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (BOOL)doesContact:(Contact *)contact matchSearchTerm:(NSString *)searchTerm | ||
|  | { | ||
|  |     OWSAssert(contact); | ||
|  |     OWSAssert(searchTerm.length > 0); | ||
|  | 
 | ||
|  |     if ([contact.fullName.lowercaseString containsString:searchTerm.lowercaseString]) { | ||
|  |         return YES; | ||
|  |     } | ||
|  | 
 | ||
|  |     NSString *asPhoneNumber = [PhoneNumber removeFormattingCharacters:searchTerm]; | ||
|  |     if (asPhoneNumber.length > 0) { | ||
|  |         for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { | ||
|  |             if ([phoneNumber.toE164 containsString:asPhoneNumber]) { | ||
|  |                 return YES; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return NO; | ||
|  | } | ||
|  | 
 | ||
|  | - (BOOL)doesContact:(Contact *)contact matchSearchTerms:(NSArray<NSString *> *)searchTerms | ||
|  | { | ||
|  |     OWSAssert(contact); | ||
|  |     OWSAssert(searchTerms.count > 0); | ||
|  | 
 | ||
|  |     for (NSString *searchTerm in searchTerms) { | ||
|  |         if (![self doesContact:contact matchSearchTerm:searchTerm]) { | ||
|  |             return NO; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     return YES; | ||
|  | } | ||
|  | 
 | ||
|  | - (NSArray<Contact *> *)nonSignalContactsMatchingSearchString:(NSString *)searchText | ||
|  | { | ||
|  |     NSArray<NSString *> *searchTerms = [self searchTermsForSearchString:searchText]; | ||
|  | 
 | ||
|  |     if (searchTerms.count < 1) { | ||
|  |         return [NSArray new]; | ||
|  |     } | ||
|  | 
 | ||
|  |     return [self.nonSignalContacts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Contact *contact, | ||
|  |                                                                    NSDictionary<NSString *, id> *_Nullable bindings) { | ||
|  |         return [self doesContact:contact matchSearchTerms:searchTerms]; | ||
|  |     }]]; | ||
|  | } | ||
|  | 
 | ||
|  | - (nullable NSArray<Contact *> *)nonSignalContacts | ||
|  | { | ||
|  |     if (!_nonSignalContacts) { | ||
|  |         NSMutableSet<Contact *> *nonSignalContacts = [NSMutableSet new]; | ||
| 
											8 years ago
										 |         [[TSStorageManager sharedManager].dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { | ||
| 
											9 years ago
										 |             for (Contact *contact in self.contactsManager.allContactsMap.allValues) { | ||
|  |                 NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction]; | ||
|  |                 if (signalRecipients.count < 1) { | ||
|  |                     [nonSignalContacts addObject:contact]; | ||
|  |                 } | ||
|  |             } | ||
|  |         }]; | ||
|  |         _nonSignalContacts = [nonSignalContacts.allObjects | ||
|  |             sortedArrayUsingComparator:^NSComparisonResult(Contact *_Nonnull left, Contact *_Nonnull right) { | ||
|  |                 return [left.fullName compare:right.fullName]; | ||
|  |             }]; | ||
|  |     } | ||
|  | 
 | ||
|  |     return _nonSignalContacts; | ||
|  | } | ||
|  | 
 | ||
| 
											9 years ago
										 | #pragma mark - Editing | ||
|  | 
 | ||
| 
											8 years ago
										 | - (void)presentMissingContactAccessAlertControllerFromViewController:(UIViewController *)viewController | ||
| 
											8 years ago
										 | { | ||
|  |     UIAlertController *alertController = [UIAlertController | ||
|  |         alertControllerWithTitle:NSLocalizedString(@"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_TITLE", comment | ||
|  |                                                    : @"Alert title for when the user has just tried to edit a " | ||
|  |                                                      @"contacts after declining to give Signal contacts " | ||
|  |                                                      @"permissions") | ||
|  |                          message:NSLocalizedString(@"EDIT_CONTACT_WITHOUT_CONTACTS_PERMISSION_ALERT_BODY", comment | ||
|  |                                                    : @"Alert body for when the user has just tried to edit a " | ||
|  |                                                      @"contacts after declining to give Signal contacts " | ||
|  |                                                      @"permissions") | ||
|  |                   preferredStyle:UIAlertControllerStyleAlert]; | ||
|  | 
 | ||
|  |     [alertController | ||
|  |         addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"AB_PERMISSION_MISSING_ACTION_NOT_NOW", | ||
|  |                                                      @"Button text to dismiss missing contacts permission alert") | ||
|  |                                            style:UIAlertActionStyleCancel | ||
|  |                                          handler:nil]]; | ||
|  | 
 | ||
|  |     [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OPEN_SETTINGS_BUTTON", | ||
|  |                                                                   @"Button text which opens the settings app") | ||
|  |                                                         style:UIAlertActionStyleDefault | ||
|  |                                                       handler:^(UIAlertAction *_Nonnull action) { | ||
| 
											8 years ago
										 |                                                           [CurrentAppContext() openSystemSettings]; | ||
| 
											8 years ago
										 |                                                       }]]; | ||
|  | 
 | ||
| 
											8 years ago
										 |     [viewController presentViewController:alertController animated:YES completion:nil]; | ||
| 
											8 years ago
										 | } | ||
|  | 
 | ||
| 
											9 years ago
										 | - (void)presentContactViewControllerForRecipientId:(NSString *)recipientId | ||
|  |                                 fromViewController:(UIViewController<ContactEditingDelegate> *)fromViewController | ||
|  |                                    editImmediately:(BOOL)shouldEditImmediately | ||
| 
											8 years ago
										 | { | ||
|  |     [self presentContactViewControllerForRecipientId:recipientId | ||
|  |                                   fromViewController:fromViewController | ||
|  |                                      editImmediately:shouldEditImmediately | ||
|  |                               addToExistingCnContact:nil]; | ||
|  | } | ||
|  | 
 | ||
|  | - (void)presentContactViewControllerForRecipientId:(NSString *)recipientId | ||
|  |                                 fromViewController:(UIViewController<ContactEditingDelegate> *)fromViewController | ||
|  |                                    editImmediately:(BOOL)shouldEditImmediately | ||
|  |                             addToExistingCnContact:(CNContact *_Nullable)addToExistingCnContact | ||
| 
											9 years ago
										 | { | ||
|  |     SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; | ||
|  | 
 | ||
| 
											9 years ago
										 |     if (!self.contactsManager.supportsContactEditing) { | ||
|  |         // Should not expose UI that lets the user get here. | ||
| 
											8 years ago
										 |         OWSFail(@"%@ Contact editing not supported.", self.logTag); | ||
| 
											9 years ago
										 |         return; | ||
|  |     } | ||
|  | 
 | ||
| 
											9 years ago
										 |     if (!self.contactsManager.isSystemContactsAuthorized) { | ||
| 
											8 years ago
										 |         [self presentMissingContactAccessAlertControllerFromViewController:fromViewController]; | ||
| 
											9 years ago
										 |         return; | ||
|  |     } | ||
|  | 
 | ||
|  |     CNContactViewController *_Nullable contactViewController; | ||
| 
											8 years ago
										 |     CNContact *_Nullable cnContact = nil; | ||
|  |     if (addToExistingCnContact) { | ||
|  |         CNMutableContact *updatedContact = [addToExistingCnContact mutableCopy]; | ||
|  |         NSMutableArray<CNLabeledValue *> *phoneNumbers | ||
|  |             = (updatedContact.phoneNumbers ? [updatedContact.phoneNumbers mutableCopy] : [NSMutableArray new]); | ||
|  |         // Only add recipientId as a phone number for the existing contact | ||
|  |         // if its not already present. | ||
|  |         BOOL hasPhoneNumber = NO; | ||
|  |         for (CNLabeledValue *existingPhoneNumber in phoneNumbers) { | ||
|  |             CNPhoneNumber *phoneNumber = existingPhoneNumber.value; | ||
|  |             if ([phoneNumber.stringValue isEqualToString:recipientId]) { | ||
| 
											8 years ago
										 |                 OWSFail(@"We currently only should the 'add to existing contact' UI for phone numbers that don't " | ||
|  |                         @"correspond to an existing user."); | ||
| 
											8 years ago
										 |                 hasPhoneNumber = YES; | ||
|  |                 break; | ||
| 
											9 years ago
										 |             } | ||
|  |         } | ||
| 
											8 years ago
										 |         if (!hasPhoneNumber) { | ||
|  |             CNPhoneNumber *phoneNumber = [CNPhoneNumber phoneNumberWithStringValue:recipientId]; | ||
|  |             CNLabeledValue<CNPhoneNumber *> *labeledPhoneNumber = | ||
|  |                 [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMain value:phoneNumber]; | ||
|  |             [phoneNumbers addObject:labeledPhoneNumber]; | ||
|  |             updatedContact.phoneNumbers = phoneNumbers; | ||
|  | 
 | ||
|  |             // When adding a phone number to an existing contact, immediately enter | ||
|  |             // "edit" mode. | ||
|  |             shouldEditImmediately = YES; | ||
|  |         } | ||
|  |         cnContact = updatedContact; | ||
|  |     } | ||
|  |     if (signalAccount && !cnContact) { | ||
|  |         cnContact = signalAccount.contact.cnContact; | ||
|  |     } | ||
|  |     if (cnContact) { | ||
|  |         if (shouldEditImmediately) { | ||
|  |             // Not actually a "new" contact, but this brings up the edit form rather than the "Read" form | ||
|  |             // saving our users a tap in some cases when we already know they want to edit. | ||
|  |             contactViewController = [CNContactViewController viewControllerForNewContact:cnContact]; | ||
|  | 
 | ||
|  |             // Default title is "New Contact". We could give a more descriptive title, but anything | ||
|  |             // seems redundant - the context is sufficiently clear. | ||
|  |             contactViewController.title = @""; | ||
|  |         } else { | ||
|  |             contactViewController = [CNContactViewController viewControllerForContact:cnContact]; | ||
|  |         } | ||
| 
											9 years ago
										 |     } | ||
|  | 
 | ||
|  |     if (!contactViewController) { | ||
|  |         CNMutableContact *newContact = [CNMutableContact new]; | ||
|  |         CNPhoneNumber *phoneNumber = [CNPhoneNumber phoneNumberWithStringValue:recipientId]; | ||
|  |         CNLabeledValue<CNPhoneNumber *> *labeledPhoneNumber = | ||
|  |             [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMain value:phoneNumber]; | ||
|  |         newContact.phoneNumbers = @[ labeledPhoneNumber ]; | ||
| 
											8 years ago
										 | 
 | ||
| 
											8 years ago
										 |         newContact.givenName = [self.profileManager profileNameForRecipientId:recipientId]; | ||
| 
											9 years ago
										 | 
 | ||
|  |         contactViewController = [CNContactViewController viewControllerForNewContact:newContact]; | ||
|  |     } | ||
|  | 
 | ||
|  |     contactViewController.delegate = fromViewController; | ||
|  |     contactViewController.allowsActions = NO; | ||
|  |     contactViewController.allowsEditing = YES; | ||
|  |     contactViewController.navigationItem.leftBarButtonItem = | ||
| 
											8 years ago
										 |         [[UIBarButtonItem alloc] initWithTitle:CommonStrings.cancelButton | ||
| 
											9 years ago
										 |                                          style:UIBarButtonItemStylePlain | ||
|  |                                         target:fromViewController | ||
|  |                                         action:@selector(didFinishEditingContact)]; | ||
|  | 
 | ||
|  |     UINavigationController *navigationController = | ||
|  |         [[UINavigationController alloc] initWithRootViewController:contactViewController]; | ||
|  | 
 | ||
|  |     // We want the presentation to imply a "replacement" in this case. | ||
|  |     if (shouldEditImmediately) { | ||
|  |         navigationController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; | ||
|  |     } | ||
|  |     [fromViewController presentViewController:navigationController animated:YES completion:nil]; | ||
|  | 
 | ||
|  |     // HACK otherwise CNContactViewController Navbar is shown as black. | ||
|  |     // RADAR rdar://28433898 http://www.openradar.me/28433898 | ||
|  |     // CNContactViewController incompatible with opaque navigation bar | ||
|  |     [UIUtil applyDefaultSystemAppearence]; | ||
|  | } | ||
|  | 
 | ||
| 
											9 years ago
										 | @end | ||
|  | 
 | ||
|  | NS_ASSUME_NONNULL_END |