Address Book External Change Callback in Swift (with C Function Pointers?)
Asked Answered
F

4

9

Original Question (see solution below):

I am trying to use the AddressBook.framework in my Swift App, but can't figure out how to implement the ABAddressBookRegisterExternalChangeCallback function.

In Objective-C, I just implement the callback as a C function and pass its pointer:

// somewhere in the initializer of the MyAddressBook class:
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(nil, nil);
ABAddressBookRegisterExternalChangeCallback(addressBook, externalChangeCallback, (__bridge void *)(self));

// somewhere else in the MyAddressBook class:
void externalChangeCallback(ABAddressBookRef reference, CFDictionaryRef info, void *context)
{
    [(__bridge MyAddressBook *)context addressBookDidChangeExternally];
}

- (void)addressBookDidChangeExternally
{
    // good old Obj-C from here on!
}

In Swift, it is proving very difficult for me to handle C functions. I found that Apple added the ability to pass C function pointers around in beta 3, but how do I declare such a function? It would be good to use Swift's closure syntax, but is that even possible here?

This is where I create the ABAddressBookRef:

var addressBookRef: ABAddressBookRef = {
    let addressBookRef: ABAddressBookRef = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()

    // TODO: how do I make this work?
    let externalChangeCallback: ABExternalChangeCallback = {
        println("Address book changed externally!")
    }
    ABAddressBookRegisterExternalChangeCallback(addressBookRef, externalChangeCallback, nil)

    return addressBookRef
}()

So how can I implement this in Swift?


Solution (with flaws):

As suggested by pNre, this is how I implemented it now:

In Objective-C:

AddressBookExternalChangeCallback.h:

#import <AddressBook/AddressBook.h>
void registerExternalChangeCallbackForAddressBook(ABAddressBookRef addressBookRef);

AddressBookExternalChangeCallback.m:

#import "AddressBookExternalChangeCallback.h"

void addressBookExternalChangeCallback(ABAddressBookRef addressBookRef, CFDictionaryRef info, void *context)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"AddressBookDidChangeExternallyNotification" object:nil];
    });
}

void registerExternalChangeCallbackForAddressBook(ABAddressBookRef addressBookRef)
{
    ABAddressBookRegisterExternalChangeCallback(addressBookRef, addressBookExternalChangeCallback, nil);
}

In Swift:

after importing bridging header:

registerExternalChangeCallbackForAddressBook(addressBookRef)

A notification is posted whenever the address book changes. Only @objc classes can register for notifications, though, so is there a way to call a Swift function or method instead?

Flume answered 17/8, 2014 at 5:31 Comment(1)
So how you finished? I did registerExternalChangeCallbackForAddressBook(addressBookRef) in Bridging header and typealias ABExternalChangeCallback = CFunctionPointer<((ABAddressBookRef!, CFDictionary!, UnsafeMutablePointer<()>) -> Void)> declared globally in My swift class Next how to call registerExternalChangeCallbackForAddressBook in Swift ? Please can you post your detail answer for swift that will be really helpful for user like me.Iolaiolande
R
5

ABExternalChangeCallback is defined as

typealias ABExternalChangeCallback = CFunctionPointer<((ABAddressBookRef!, CFDictionary!, UnsafeMutablePointer<()>) -> Void)>

From the Xcode release notes:

However, you cannot call a C function pointer (CFunctionPointer) or convert a closure to C function pointer type.

This means you can't assign a block the way you're doing. However you can bypass this limitation calling ABAddressBookRegisterExternalChangeCallback in an objc function and calling it from your swift code.

Roentgenogram answered 17/8, 2014 at 15:39 Comment(0)
C
1

Swift 2.0

if let addressBook = ABAddressBookCreateWithOptions(nil, nil) {
    let ref = addressBook.takeRetainedValue()
    let callback: @convention(c) (addressBookRef: ABAddressBookRef!, info: CFDictionaryRef!, context: UnsafeMutablePointer<Void>) -> Void = {
        (addressBookRef, info, context) in

        // do the things you want

    }

    let addressBookChangeCallback = unsafeBitCast(callback, ABExternalChangeCallback.self)

    ABAddressBookRegisterExternalChangeCallback(ref, addressBookChangeCallback, nil)

}
Colorful answered 11/3, 2016 at 15:32 Comment(1)
The code construction build and run, but does not fired while any contact changed.Santee
D
0

in swift 4.0

class ABAddressBookManager: NSObject {

    static let shared = ABAddressBookManager()

    private var addressBook: ABAddressBook?

    override init() {
        super.init()

        let addressBookForObserving = ABAddressBookCreate().takeRetainedValue()
        let context = Unmanaged.passUnretained(self).toOpaque()

        DispatchQueue.main.async {
             ABAddressBookRegisterExternalChangeCallback(addressBookForObserving, { (addressBook, info, context) in 
                guard let context = context else {
                    return
                }
                let manager = Unmanaged<ABAddressBookManager>.fromOpaque(context).takeUnretainedValue()
                //call manager's method
            }
        }
        self.addressBook = addressBookForObserving
    }
}
Diplopod answered 23/4, 2018 at 6:49 Comment(0)
B
-2

I got the same issue. But I have cleared the cache by calling ABAddressBookRevert() in addressbook object.

Biathlon answered 25/8, 2015 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.