개발

[Swift] Contacts 사용하기 - CNContactStore, CNKeyDescriptor

Onve 2024. 4. 2. 01:11

프로젝트 개발을 하다보면 Contacts(연락처)를 사용하게 되는 경우가 꽤 있다.

 

이를 위해서 Apple에서는 읽기에 조금 더 최적화된 Contacts라는 패키지를 제공한다.

 

오늘은 읽는 기능에 집중하여 Contacts의 사용법을 정리하고자 한다.

 


 

1. Privacy - Contacts Usage Description 설정

우선, Contacts를 사용하기 위해서는 Info.plist에서 해당 권한을 설정을 해야한다.

 

필자는 SwiftUI를 통해 개발을 진행하고 있으므로, 어플리케이션 정보 > Info로 들어가서 plist를 수정해주도록 하자.

Privacy - Contacts Usage Description 항목을 열어줘야한다.

여기서 Key 중 Privacy - Conatcts Usage Description 항목을 만들어 주면 된다.

 

해당 항목의 Value 값은 어째서 이 어플리케이션이 Contacts 데이터를 사용하는지에 대해서 유저에게 알려주는 부분이기 때문에, HIG를 따르기 위해서는 작성해주는 것이 좋다.

 

 

 

 

2. Contacts 사용하기

a. Contacts 적용 및 CNContactStore 사용

import Contacts

extension ContactView {
	func getContactLists() {
		let store = CNContactStore()
        
        // More...
	}
}

 

우선, Contacts를 import 하고, CNContactStore를 store로 선언해준다.

 

CNContactStore는 Swift에서 연락처 정보를 가지고 있는 Store로, 앞으로 store를 통해 Contacts에 접근할 것이다. 

 

 

 

b. 권한 상태 파악

import Contacts

extension ContactView {
	func getContactLists() {
		let store = CNContactStore()
        
        switch CNContactStore.authorizationStatus(for: .contacts) {
        case .authorized:
        	// 승인된 경우
        case .notDetermined:
        	// 아직 결정되지 않음(첫 실행일 경우)
        case .restricted:
        	// 제한됨
        case .denied:
        	// 거부됨(권한 없음)
        @unknown default:
            print("unknown default") // 경우에 수에 없는 상황(진짜 큰일)
        }
    }
}

 

이제 권한에 따라 경우의 수를 나누어줄 차례이다.

 

switch문을 통해서 경우의 수를 나누어주고, CNContactStore.authorizationStatus(for: .contacts)를 통해서 권한의 상황을 확인한다.

 

이 때, for: .contacts는 연락처에 대한 권한에 대해 파악하는 것이라고 생각하면 된다. 

 

해당 함수가 나타날 때 나타날 수 있는 경우의 수는 다음과 같다.

 

.authorized 연락처 정보 접근 권한이 허용된 경우

 

.notDetermined 연락처 정보 접근 권한이 아직 선택되지 않은 경우(첫 실행시 작동하는 것으로 생각하면 된다.)

 

.restricted 연락처 정보가 제한된 상황

 

.denied 연락처 정보 접근 권한이 거부된 경우

 

이제 이 경우의 수에 따라서 경우만 나누면 된다.

 

 

 

 

c. Key 설정 및 연락처 정보 받아오기 - .authorized

do {
    let keys = [CNContactPhoneNumbersKey,
                CNContactGivenNameKey,
                CNContactFamilyNameKey, 
                CNContactThumbnailImageDataKey,
                CNContactOrganizationNameKey,
                CNContactEmailAddressesKey] as [CNKeyDescriptor]

    let request = CNContactFetchRequest(keysToFetch: keys)
    request.sortOrder = .userDefault

    try CNStore.enumerateContacts(with: request, usingBlock: {contact, _ in
        contactList.append(contact)
    })

} catch {
    print("error occured while authorized job: \(error)")
}

 

do-catch 문으로 혹시 모를 문제 상황을 막아주고, key를 설정해야 한다.

 

Contacts는 매우 다양한 정보를 담고 있다. 따라서 이를 모두 불러오기보다는 필요한 정보만 가져가서 쓰는 것이 훨씬 효율적인데, 그를 위해 정보들을 CNKeyDescriptor를 이용하여 찾는 편이다.

 

본 예제에서는 전화번호, 이름, 성, 연락처 썸네일 이미지, 직장 이름, 이메일을 가지고 왔다.

 

그 후에는 request를 통해서 key를 store에 전송하고, 유저 기본 설정에 맞추어 sortOrder를 변경한다.

 

해당 Contacts는 Using Block의 형태로 들어오기 때문에, 보는 것과 같이 코드를 작성하면 쓰고 있던 컴포넌트의 제대로 저장되는 것을 볼 수 있다.

 

 

 

 

d. 권한 받기(권한 모달 띄우기) - .notDetermined

만약 처음 어플리케이션 또는 기능에 접속하는 것이라면, 해당 부분에 대해서 .notDetermined인 경우가 생긴다.

 

이 때는 정보를 요구하는 모달창을 띄워야 하는데, 이가 바로 그것이다.

case .notDetermined:
        print("notDetermined")
        CNStore.requestAccess(for: .contacts) { granted, error in
            if granted {
                getContactList()
            } else if let error = error {
                print("Error requesting contact access: \(error)")
            }

 

CNStore를 통해서 Access Request를 .contacts인 연락처로 보내고, granted를 통해서 Boolean 값으로 해당 결과를 확인 할 수 있다.

 

예를 들어 권한을 요청하는 팝업이 Access Request를 통해서 나타나고, 확인을 누르면 granted == true로, 거부를 누르면 granted가 false가 되어 else if error = error 쪽으로 이동하게 된다.

 

 

이 때, 요청이 true로 변경되었다면 바로 자기 자신을 재귀적으로 호출하여 해당 정보 값을 전달할 수 있도록 하였다.

 

 

 


 

전체 코드

import Foundation
import Contacts
import UIKit

extension ContactView {
    func getContactList() {
        let CNStore = CNContactStore()
        
        switch CNContactStore.authorizationStatus(for: .contacts) {
        case .authorized:
            do {
                let keys = [CNContactPhoneNumbersKey,
                            CNContactGivenNameKey,
                            CNContactFamilyNameKey, 
                            CNContactThumbnailImageDataKey,
                            CNContactOrganizationNameKey,
                            CNContactEmailAddressesKey] as [CNKeyDescriptor]
                
                let request = CNContactFetchRequest(keysToFetch: keys)
                request.sortOrder = .userDefault
                
                try CNStore.enumerateContacts(with: request, usingBlock: {contact, _ in
                    contactList.append(contact)
                })
                
            } catch {
                print("error occured while authorized job: \(error)")
            }
        case .notDetermined:
            print("notDetermined")
            CNStore.requestAccess(for: .contacts) { granted, error in
                if granted {
                    getContactList()
                } else if let error = error {
                    print("Error requesting contact access: \(error)")
                }
            }
        case .restricted:
            print("restriced")
        case .denied:
            print("denied")
        @unknown default:
            print("unknown default")
        }
    }
}