Contacts 프레임워크
Contacts 프레임워크를 이용한 주소록 액세스
iOS8 까지는 AddressBook 프레임워크를 이용해서 시스템의 주소록 데이터베이스에 액세스할 수 있었는데, iOS9에서는 이 프레임워크 자체가 deprecated 되었다. (헐…) 대신에 연락처를 액세스하는 별도의 프레임워크인 Contacts
가 신설되었다.
!주의! 현재 Contacts 프레임워크에는 버그가 많이 있는 듯… 저장할 때 툭하면 에러가 나는 경우가 많다.
액세스 방법은 다음과 같다.
CNContactStore
인스턴스를 만든다. 이는 연락처 저장소에 액세스할 수 있는 추상화된 인터페이스를 제공한다.addressbook.people()
과 같은 인터페이스는 더 이상 제공되지 않는다. 대신 코어데이터와 유사하게fetchRequest
를 이용해서 필요한 연락처들을 페칭할 수 있다.- 변경사항 전체를 한 번제 저장할 수 없다. 개별 연락처 혹은 그룹을 대상으로 부분적으로 업데이트한다. 저장시에는
CNSaveRequest
객체를 만들고, 어떤 그룹이나 연락처를 업데이터/추가/삭제할 것인지를 명시해서 이를 실행하는 형태로 저장한다.
저장소 만들기
저장소(CNContactStore)는 연락처 저장소를 액세스하기 위한 인터페이스를 제공하는 객체이다. 생성시에는 별도의 파라미터가 필요하지 않다,
let store = CNContactStore()
iOS9에서 주소록 액세스 방식이 변경된 것은 페이스북 연락처와 같은 외부 연락처 데이터가 iOS의 연락처 데이터와 통합된 것처럼 동작하기 때문이다. 2개 이상의 개별 연락처는 하나의 통합된 연락처 항목으로 링크되어 통합될 수 있고, 이를 처리하기 위한 새로운 인터페이스를 지원하기 위해 새로운 프레임워크가 도입되지 않았나 본다. (게다가 iOS는 OSX와 달리, AddressBook 프레임워크가 코코아 프레임워크가 아닌 코어파운데이션으로 되어 있었다.)
개별 연락처 항목의 ID를 알고 있다면 unifiedConatactWithIdentifier:keysToFetch:error:
1를 이용해서 바로 해당 연락처 정보 중 원하는 키-값 쌍 정보를 얻을 수 있다. 만약 전체 연락처 정보를 알고 싶다면, enumerateContactWithFetchRequest:error:usingBlock:
을 이용하면 된다.
참고로 predicate는 CNContact
에서 제공하는 4가지 종류의 predicate 생성함수를 이용해야 하며, 이름, ID, 그룹ID, 컨테이너ID를 가지고 얻을 수 있다.
불러오기
연락처를 불러오기 위한 가장 좋은 방법은 enumerateContactsWithFetchRequest:error:usingBlock:
을 쓰는 것이다. 이를 위해서는 NSContactFetchRequest
객체가 필요한데, 별다른 옵션이 없으면 전체 연락처를 가져온다. 대신, 모든 연락처의 모든 키를 가져오는 것은 아니며, 사용을 원하는 모든 키를 명시해야 한다.
let fetchRequest = CNContactFetchRequest(keysToFetch:[
CNContactGivenNameKey, CNContactNicknameKey
])
만약 특정 ID, 특정 그룹을 대상으로 하고 싶다면, 여기에도 predicate 옵션을 줄 수 있다. 역시 CNContact
에서 제공하는 predicate만 적용이 가능하다.
전체 연락처를 순회하면서 어떤 동작을 하는 코드는 다음과 같다.
try! store.enumerateContactsWithFetchRequest(fetchRequest){
contact, stop in
print(contact.givenName) // givenName은 `firstname`에 해당한다.
}
변경, 편집
fetching된 결과인 CNContact
인스턴스의 각 키는 희한하게도 immuatble하다. 결국 mutable한 사본을 만들어서 변경해야 한다.
let mutableContact = contact.mutableCopy() as! CNMuatbleContact
mutableContact.givenName = "아무개"
저장
저장을 위해서는 CNSaveRequest
객체를 만들어서 어떤 항목을 추가/삭제/업데이트하는지 그 문맥정보를 기입한 후 스토어 객체를 통해서 처리한다.
let saveReqeust = CNSaveRequest()
saveRequest.updateContact(mutableContact) // 이름을 바꿨음
try! store.executeSaveRequest(saveReqeust)
예제
다음은 한글 이름으로부터 초성자음으로 이루어진 닉네임을 생성하고, 이를 이용해서 닉네임을 업데이트하는 함수를 작성해보도록 하겠다.2 먼저 한글로 된 문자열을 주었을 때 초성을 추려내는 변환함수를 생각해보자.
func makeNewNickname(name:String) -> String {
let transform: UnicodeScalar -> String = { u in
let iu = Int(u.value)
guard iu >= 0xac00 && iu <= 0xd7a3 else { return String(u) }
let index = (iu - 0xac00) / 28 / 21
return String(UnicodeScalar(index + 0x1100))
}
return name.UnicodeScalars.map(transform).joinWithSeparator("")
}
그러면 모든 연락처를 순회하면서 새로운 닉네임을 만들고, 업데이트가 필요한 닉네임만 업데이트하는 코드를 작성해 보겠다.
func updateAllNicknames() {
let store = CNContactStore()
let fetchRequest = CNContactFetchRequest(keysToFetch:[
CNContactGivenNameKey, CNContactNicknameKey
])
let saveRequest = CNSaveRequest()
try! store.enumerateContactsWithFetchRequest(fetchRequest){
contact, stop in
let mutableContact = contact.mutableCopy() as! CNMuatbleContact
let newNickname = makeNewNickname(mutableContact.givenName)
if newNickname != mutableContact.nickname {
mutableContact.nickname = newNickname
saveRequest.updateContact(mutableContact)
}
}
try! store.executeSaveRequest(saveRequest)
}