MapKit (2)
Container??
Container는 3가지 Database를 가지고 있다.
Public: 모든사람이 앱을 통해 DB를 볼 수 있다.
Private: 사용자가 각자 자기자신의 데이터만 볼 수 있다.
Shared: 사용자간 데이터를 공유 할 수 있다.
CKRecord Docs
CKReference Docs
CKOperation Docs
Cloudkit 사이트에서 값 설정하기
예전버전과는 사이트구성이 달라 기록해본다.
먼저 개발자 사이트로 가서 설정을 한다.
이떄 개발자 계정이 있어야함..
Record Fields, Record Type 만들기
Record Fields를 추가할땐 사진처럼 순서대로 하면된다.
그리고 이렇게 값을 추가해주면 된다.
새로운 Record Type이 필요할땐
이렇게 추가해주면된다.
DDGLocation에 대해 다음과 같이 Field들을 추가해 주었다.
CoreData로 생각하면 DDGLocation은 Entity이고, Field들은 Attribute로 생각하면 편하다.
Index 추가하기
Index 역시도 예전에는 Field를 추가하고나서 바로 생성이 가능했는데 지금은 분리가 되었다.
이렇게 추가를 누르고 아래와 같이 적어주었다.
예전에는 Name이라는 Field가 없었는데 새로 추가 된것같다.
나는 제일 하단의 Field와 Name을 일치시켜 주었다.
이렇게 DDGLocation Record에 2개의 Index를 추가해 주었다.
그리고 다시 Record Types로 가보면
이렇게 우리가 추가한게 None에서 바뀌어있다.
위의 방식으로
DDGProfile도 만들어주자.
데이터 추가하기
위의 사진순서대로 하면 우측에 New Record라고 우리가 수동으로 값을 입력할 수 있다.
이때 2번에서 DDGLocation으로 한 이유는 여기에 데이터를 추가할것이기 때문.
2번에서 데이터를 추가하고싶은 Record Type을 선택하여 추가를 하면 된다.
값을 추가하고 조회를 하면 이렇게 등록이 된걸 확인할 수 있다.
한개가 추가되고난 이후에는
이걸로 추가하자.
물론 그전부터 이걸로 추가해도 된다.
데이터 모델링 (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"
}
이것도 역시 지금은
Deprecated 되었다.
이후에 수정하는걸로…
let sortDescriptor = NSSortDescriptor(key: DDGLocation.kName, ascending: true)
- DDGLocation에서 name Field에 대해 이름을 오름차순 순서로 가져오겠다는 것.
let query = CKQuery(recordType: RecordType.location, predicate: NSPredicate(value: true))
- DDGLocation의 모든 데이터를 가져오겠다.
query.sortDescriptors = [sortDescriptor]
- 우리가 만든 Query들을 배열로 담는다.
- 배열로 담는 이유는 Query가 여러개 일 수 있으니까.
- ex: 이름과 날짜를 함께 정렬해야 할 때는 Query를 더 추가해야한다.
- 우리가 만든 Query들을 배열로 담는다.
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