3

The following code works correctly in iOS7, but not in iOS8 (the variable recordID is set correctly):

    CFErrorRef error = nil;
    const ABAddressBookRef addressBook = (ABAddressBookCreateWithOptions (NULL, &error));
    ABRecordRef contactRef = ABAddressBookGetPersonWithRecordID (addressBook, recordID);
    ABPersonViewController *personViewController = [[ABPersonViewController alloc] init];
    personViewController.addressBook     = addressBook;
    personViewController.displayedPerson = contactRef;
    CFRelease(addressBook);
    NSArray *displayedProperties = @[@(kABPersonFirstNameProperty),
                                     @(kABPersonLastNameProperty),
                                     @(kABPersonMiddleNameProperty),
                                     @(kABPersonPrefixProperty),
                                     @(kABPersonSuffixProperty),
                                     @(kABPersonOrganizationProperty),
                                     @(kABPersonJobTitleProperty),
                                     @(kABPersonDepartmentProperty),
                                     @(kABPersonEmailProperty),
                                     @(kABPersonBirthdayProperty),
                                     @(kABPersonKindProperty),
                                     @(kABPersonAddressProperty),
                                     @(kABPersonPhoneProperty),
                                     @(kABPersonInstantMessageProperty),
                                     @(kABPersonURLProperty),
                                     @(kABPersonSocialProfileProperty),
                                     @(kABPersonNoteProperty)];
    personViewController.displayedProperties  = displayedProperties;
    personViewController.navigationItem.title = NSLocalizedString(@"CONTACT_DETAILS", nil);
    personViewController.allowsActions        = YES;
    personViewController.allowsEditing        = YES; // if NO, no back button is shown
    personViewController.personViewDelegate   = self;
    personViewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"ADDRESSES",nil) style:UIBarButtonItemStylePlain target:self action:@selector(personviewDoneButtonPressed:)];

    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:personViewController];
    [self presentViewController:navigationController animated:YES completion:nil];

Bugs in iOS8:

  1. When allowsEditing is set to YES, the contact is shown, but only the name is displayed. The navigation bar shows left the back button (named "Addresses") and right the edit button. When the edit button is pressed, the contact is displayed with all fields empty except the name, and the edit button is displayed as a done button. If this done button is pressed without any editing before, all information about the contact is displayed.
  2. When allowsEditing is set to NO, no back button is shown, so that the screen can no longer be left.

Has anybody a workaround?

UPDATE:

I realized by now, that problem 1 only sometimes occurs on the simulator, although always on my device.

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116

2 Answers2

5

montuno (thanks again), who commented my first answer, got a hint from Apple technical support how to solve the 1st problem, and I understand now also the 2nd problem:

1) Only part of the contact displayed:

Apple claimed that the following code is wrong:

ABRecordRef contactRef = ABAddressBookGetPersonWithRecordID (addressBook, recordID);
ABPersonViewController *personViewController = [[ABPersonViewController alloc] init];
personViewController.addressBook     = addressBook;
personViewController.displayedPerson = contactRef;
CFRelease(addressBook);

Here addressBook is erroneously released, after it has been assigned to the ABPersonViewController.

In my original code, I did not have the CFRelease statement. But then the static analyzer warned of a „Potential leak of an object stored in addressBook“. After I inserted the release statement, the build succeeded without warning, so I assumed that the personViewController retained the addressBook, which is apparently not the case, at least not with iOS8. I removed now the release statement again, got the warning back, but everything works fine.

2) Missing back button:
The docs of the ABPersonViewController say „IMPORTANT Person view controllers must be used with a navigation controller in order to function properly.

So what I did was the following:
I set up an ABPersonViewController, assigned to its navigationItem.backBarButtonItem a „Done“ button, and initialized a new UINavigationController with this personViewController, using initWithRootViewController:. Then I presented the new navigationController modally.
The personViewController with the back button was displayed, and when pressed, the presenting view controller dismissed the modally presented navigation controller.
This worked fine on iOS < 8, but in iOS8, the back button was not displayed, when allowsEditing was set to NO.

But this is apparently not how Apple wants to display the personViewController:
I think Apple assumes that a navigationController already exists, and presents the current view, so that a new personViewController is just pushed onto the stack of the navigationController. In this case, the personViewController is not the rootViewController of the navigationController, and the navigationController displays automatically a back button (which is not done for the rootViewController). If implemented this way, everything works fine also for iOS8.

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
  • Resolving way of first problem helped for me too. – Eugene Biryukov Nov 26 '14 at 12:37
  • But I have little bit another situation: after first accessing to addressBook property of ABPersonViewController it will created if not set. So when I created instance of a controller described above I called addressBook property, it was created and released at once, if I understood correctly. I resolved this problem by this way: 'code' CFRetain(vc.addressBook); vc.displayedPerson = [contact displayedPersonFromAddressBook:vc.addressBook]; CFRelease(vc.addressBook); 'code' Please, if I did it wrong tell me and guide me to the right path. – Eugene Biryukov Nov 26 '14 at 12:49
  • The docs say "When unset, an address book is created and assigned to this property when needed." Indeed a reference to the addressBook is created when you read this property. But this reference is apparently not retained when you assign an APPersonRecord to displayedPerson. If this is true, the CFRelease statement would release the addressBook again, and if your person record is displayed properly is a matter of luck, because it could be overwritten anytime. So I am not sure, but it seems you should not use the CFRelease before you won't need the person record anymore... – Reinhard Männer Nov 26 '14 at 14:29
  • interesting enough the `CFRelease(addressBook)` does work in the simulator on iOS8, but not on the device... – Christian Nov 26 '14 at 16:08
  • @ReinhardMänner, it depends on what ABAddressBookCopyArrayOfAllPeople returns - real copy (second instance of persons from addressBook) or just link their instances. Unfortunately, 'displayedPersonFromAddressBook:' method written not by me, but my colleague and I don't know exactly. The docs say that return value is array containing the person records in addressBook, but it is not unambiguously. – Eugene Biryukov Nov 26 '14 at 22:31
0

UPDATE: This hack does no longer work and is completely useless (see my new answer)

If somebody has the same problem 1) described above, here is a really dirty workaround until Apple fixes the bug (my bug reports are still open):
Since it is in certain situations required to toggle the edit button twice after the ABPersonViewController screen is shown, one can do this programmatically:

Define a property for the UINavigationController that presents the ABPersoViewController:

@property (nonatomic, strong) UINavigationController *  navigationControllerForPersonViewController;

After the navigation controller has presented the person view controller, perform a selector to do the toggling:

[self performSelector:@selector(toggleEditButton) withObject:nil afterDelay:0.7];  

The delay is set so that the person view controller screen can appear with animation. The method toggleEditButton is defined as:

- (void)toggleEditButton
{
    UIBarButtonItem *editButton  = self.navigationControllerForPersonViewController.topViewController.navigationItem.rightBarButtonItem;
    id target = editButton.target;
    SEL editAction = editButton.action;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [target performSelector:editAction withObject:editButton];
    [target performSelector:editAction withObject:editButton];
#pragma clang diagnostic pop
}

The #pragmas are required to suppress compiler warnings for potential memory leaks.
The result is that all selected information of the person view controller is actually displayed.

Reinhard Männer
  • 14,022
  • 5
  • 54
  • 116
  • I am running into issue #1 on iOS8 as well, only on devices (and not in the simulator) - although in my case I am setting allowsEditing to NO. Any idea how to work around this when editing is disabled? – montuno Nov 10 '14 at 23:41
  • Unfortunately, I have no idea for a workaround. Maybe you can also file a bug report just to show that I am not the only one with this problem? – Reinhard Männer Nov 11 '14 at 06:30
  • FYI - the demo source code 'QuickContacts' from Apple (and available from within Xcode) demonstrates how to use ABPersonViewController. Strangely, it appears to work as expected on iOS 8 - even on devices. However, even after analyzing it carefully, I can't explain why I see different behavior in my own app. – montuno Nov 11 '14 at 13:47
  • I am not sure if this is possible, but if the new version of the ABPersonViewController uses an uninitialized variable, then the behavior depends on how the memory was left by other code before the ABPersonViewController is executed, i.e. sometimes it may work and sometimes not. Actually this happened with a little demo program that I submitted with my bug report to Apple. This demo program works sometimes in the simulator, but not always, and it failed always on the device. – Reinhard Männer Nov 11 '14 at 15:45
  • That would explain what I'm seeing. – montuno Nov 11 '14 at 15:48
  • Could you please share your demo program? Apple is asking for sample code, and if you have it handy, it would help our cause :) – montuno Nov 11 '14 at 18:32
  • Of course, but how can I upload it to SO? Or is there any other way to share it? – Reinhard Männer Nov 11 '14 at 19:37
  • Just sent you an email through your website. – montuno Nov 11 '14 at 19:45
  • Strange - I did not receive anything, but my email address works. Could you please try it once more? – Reinhard Männer Nov 11 '14 at 20:32