포스트

MapKit (2)

Container??

Container는 3가지 Database를 가지고 있다.

Public: 모든사람이 앱을 통해 DB를 볼 수 있다.

Private: 사용자가 각자 자기자신의 데이터만 볼 수 있다.

Shared: 사용자간 데이터를 공유 할 수 있다.

CKRecord Docs
CKReference Docs
CKOperation Docs

Cloudkit 사이트에서 값 설정하기

예전버전과는 사이트구성이 달라 기록해본다.

먼저 개발자 사이트로 가서 설정을 한다.

이떄 개발자 계정이 있어야함..

Record Fields, Record Type 만들기

Record Fields를 추가할땐 사진처럼 순서대로 하면된다.

CleanShot 2024-12-20 at 18 08 56

그리고 이렇게 값을 추가해주면 된다.

CleanShot 2024-12-20 at 18 10 34

새로운 Record Type이 필요할땐

CleanShot 2024-12-20 at 18 11 29

이렇게 추가해주면된다.

CleanShot 2024-12-20 at 18 18 56

DDGLocation에 대해 다음과 같이 Field들을 추가해 주었다.

CoreData로 생각하면 DDGLocation은 Entity이고, Field들은 Attribute로 생각하면 편하다.

Index 추가하기

Index 역시도 예전에는 Field를 추가하고나서 바로 생성이 가능했는데 지금은 분리가 되었다.

CleanShot 2024-12-20 at 18 20 46

이렇게 추가를 누르고 아래와 같이 적어주었다.

CleanShot 2024-12-20 at 18 22 38

예전에는 Name이라는 Field가 없었는데 새로 추가 된것같다.

나는 제일 하단의 Field와 Name을 일치시켜 주었다.

CleanShot 2024-12-20 at 18 24 08

이렇게 DDGLocation Record에 2개의 Index를 추가해 주었다.

그리고 다시 Record Types로 가보면

CleanShot 2024-12-20 at 18 24 46

이렇게 우리가 추가한게 None에서 바뀌어있다.

위의 방식으로

CleanShot 2024-12-20 at 18 28 56

DDGProfile도 만들어주자.

데이터 추가하기

CleanShot 2024-12-20 at 18 32 36

위의 사진순서대로 하면 우측에 New Record라고 우리가 수동으로 값을 입력할 수 있다.

이때 2번에서 DDGLocation으로 한 이유는 여기에 데이터를 추가할것이기 때문.

2번에서 데이터를 추가하고싶은 Record Type을 선택하여 추가를 하면 된다.

CleanShot 2024-12-20 at 18 37 18

값을 추가하고 조회를 하면 이렇게 등록이 된걸 확인할 수 있다.

한개가 추가되고난 이후에는

CleanShot 2024-12-20 at 18 39 20

이걸로 추가하자.

물론 그전부터 이걸로 추가해도 된다.

데이터 모델링 (Xcode)

iCloud의 DB설정이 끝났으니 이제 Xcode에서 데이터 모델링을 한다.

아까 사이트에서 만든 설정대로 적용을 해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import CloudKit

struct DDGProfile {
    static let kFirstName   = "firstName"
    static let kLastName    = "lastName"
    static let kAvatar      = "avatar"
    static let kCompanyName = "companyName"
    static let kBio         = "bio"
    static let kIsCheckedIn = "isCheckedIn"
    
    let ckRecordID: CKRecord.ID
    let firstName: String
    let lastName: String
    let avatar: CKAsset!
    let companyName: String
    let bio: String
    let isCheckedIn: CKRecord.Reference? = nil
    
    init(record: CKRecord) {
        ckRecordID  = record.recordID
        firstName   = record[DDGProfile.kFirstName] as? String ?? "N/A"
        lastName    = record[DDGProfile.kLastName] as? String ?? "N/A"
        avatar      = record[DDGProfile.kAvatar] as? CKAsset
        companyName = record[DDGProfile.kCompanyName] as? String ?? "N/A"
        bio         = record[DDGProfile.kBio] as? String ?? "N/A"
    }
}

그중 하나만 가져왔는데,

Firebase와 마찬가지로 Record의 Field를 다 직접 입력을 해줘야하기에 오타가 날 수 있다.

그래서 static let 을 사용하여 string값을 가져오게 했다.

그리고 init을 할때도 Firebase에서 데이터를 가져올때와 마찬가지로 Type Casting을 반드시 해줘야 한다는것.

MockData 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import CloudKit

struct MockData {
    
    static var location: CKRecord {
        let record = CKRecord(recordType: "DDGLocation")
        record[DDGLocation.kName]           = "Sean's Bar and Grill"
        record[DDGLocation.kAddress]        = "123 Main Street"
        record[DDGLocation.kDescription]    = "This is a test decription. Isn't it awesome. Not sure how long to make it to test the 3 lines."
        record[DDGLocation.kWebsiteURL]     = "https://seanallen.co"
        record[DDGLocation.kLocation]       = CLLocation(latitude: 37.331516, longitude: -121.891054)
        record[DDGLocation.kPhoneNumber]    = "111-111-1111"
        
        return record
    }
}

크게 언급할 부분이 없어서 패스.

이후 Listview에 값들을 바인딩 해주었다.

이부분도 크게 업급할게 없어서 생략.

CloudKitManager 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import CloudKit

struct CloudKitManager {
    
    static func getLocation(completed: @escaping (Result<[DDGLocation], Error>) -> Void) {
        
        let sortDescriptor = NSSortDescriptor(key: DDGLocation.kName, ascending: true)
        let query = CKQuery(recordType: RecordType.location, predicate: NSPredicate(value: true))
        
        query.sortDescriptors = [sortDescriptor]
        
        CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil) { records, error in
            guard error == nil else {
                completed(.failure(error!))
                return
            }
            
            guard let records = records else { return }
            
            var locations: [DDGLocation] = []
            
            for record in records {
                let location = DDGLocation(record: record)
                locations.append(location)
            }
            
            completed(.success(locations))
        }
    }
    
}

enum RecordType {
    static let location = "DDGLocation"
    static let profile = "DDGProfile"
}

이것도 역시 지금은

CleanShot 2024-12-21 at 07 59 36

Deprecated 되었다.

이후에 수정하는걸로…


  1. let sortDescriptor = NSSortDescriptor(key: DDGLocation.kName, ascending: true)
    • DDGLocation에서 name Field에 대해 이름을 오름차순 순서로 가져오겠다는 것.
  2. let query = CKQuery(recordType: RecordType.location, predicate: NSPredicate(value: true))
    • DDGLocation의 모든 데이터를 가져오겠다.
  3. query.sortDescriptors = [sortDescriptor]
    • 우리가 만든 Query들을 배열로 담는다.
      • 배열로 담는 이유는 Query가 여러개 일 수 있으니까.
      • ex: 이름과 날짜를 함께 정렬해야 할 때는 Query를 더 추가해야한다.
  4. CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil)
    • 현재는 Deprecated된 방식(추후 수정 예정)
    • 우리가 만든 Cloud내 DB가 public이므로 publicCloudDatabase를 사용.
    • query를 적용시켜 perform(수행) 하도록 한다.
    • records와 error를 리턴한다.
    • Escaping Closure를 통하여 Completion으로 성공, 실패에 따른 records와 error를 리턴

extension 으로 편의 개선

지금은

1
2
3
4
5
6
var locations: [DDGLocation] = []

for record in records {
    let location = DDGLocation(record: record)
    locations.append(location)
}

이렇게 for loop를 통해 하나씩 배열에 담고 있다.

조금더 성능 개선을 위해 conver를 하기위한 Extension을 구현한다.

1
2
3
4
5
6
7
8
9
10
extension CKRecord {
    
    func convertToDDGLocation() -> DDGLocation {
        DDGLocation(record: self)
    }
    
    func convertToDDGProfile() -> DDGProfile {
        DDGProfile(record: self)
    }
}

현재 records의 타입은 CKRecord이기에, 해당 타입으로 Extension을 위와 같이 만든다.

그러면 적용할때는

1
let locations = records.map { $0.convertToDDGLocation() }

이렇게 코드가 간결해진다.

CloudKitManager 적용 확인하기

LocationListMapView에서 테스트를 해본다.

1
2
3
4
5
6
7
8
9
10
.onAppear {
    CloudKitManager.getLocation { result in
        switch result {
            case .success(let locations):
            print(locations)
        case .failure(let error):
            print(error)
        }
    }
}

우선은 값을 제대로 가져오는지 출력만 해본다.

1
2
3
4
5
[DubDubGrub.DDGLocation(ckRecordID: <CKRecordID: 0x6000004204c0; recordName=434EEF9C-32FF-401E-9CE4-87315B85C934,
zoneID=_defaultZone:__defaultOwner__>,
name: "AC Kitchen & Lounge",
description: "Thrill your palate with diverse and delectable dining for breakfast and dinner at AC Kitchen & Lounge."
// 너무길어서 생략

값을 잘 가져오는걸 확인했다.


Github: Dub-Dub-Grub Repository

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.