포스트

MapKit (11)

CheckedIn/out 상태에 따라 ui 다르게 구분하기

1
2
3
4
5
6
7
8
9
10
11
12
13
if viewModel.checkedInProfiles.isEmpty {
    // empty state
    Text("Nobody's Here 😞")
        .bold()
        .font(.title2)
        .foregroundStyle(.secondary)
        .padding(.top, 30)
    
} else {
    ScrollView {
        // 생략
    }
}

사실 여긴 딱히 크게 언급할게 없다. checkedinProfiles는 하나의 배열인데 그 배열에 어떠한 값도 없을때 즉, 체크인 한 사람이 아무도 없을때는 Nobody’s here 이라는 문구가 보이게 하는것이다.

Image

그러면 위와 같이 작동이 잘 되는걸 알 수 있다.

체크인 profile Loading 부분 만들어주기

체크인 기능을 확인했으니, 체크인을 가져올때 로딩을 띄워주는 걸 해보려고한다.

이렇게 하게되면 사용자입장에서도 데이터를 가져오는 중이라는걸 직관적으로 알수있기에 UX 측면에서도 꽤나 중요하다.

우선 ZStack으로 감싸준다.

특정 코드 블럭을 {} 같은 Curly Brace로 감쌀때는 그냥 Curly Brace 그 자체를 코드 블럭을 잡고 입력하면 자동으로 감싸진다.

1
2
3
4
5
6
7
ZStack {
    if viewModel.checkedInProfiles.isEmpty {
        // 생략
    } else {
       //  생략
    }
}

LocationVM에 기존에 ProfileVM에 있던 로딩과 관련된 부분을 가져와서 그대로 붙여넣기 해준다

1
2
3
4
@Published var isLoading = false

private func showLoadingView() { isLoading = true }
private func hideLoadingView() { isLoading = false }

그리고 checkedinProfile을 가져오는 함수에 로딩뷰를 띄워주는 함수를 넣어준다.

1
2
3
4
5
6
7
8
9
10
11
func getCheckedInProfiles() {
    showLoadingView() // new
    CloudKitManager.shared.getCheckedInProfiles(for: location.id) { [self] result in
        DispatchQueue.main.async {
            switch result {
            // 생략
            }
            hideLoadingView() // new
        }
    }
}

다시 DetailView로 가서 ZStack 부분에 if를 넣어 로딩중이라면 LoadingView를 보이도록 하는 코드를 추가해준다.

1
2
3
4
5
6
7
8
9
10
11
ZStack {
    if viewModel.checkedInProfiles.isEmpty {
        // 생략
    } else {
       //  생략
    }
    
    if viewModel.isLoading {
        LoadingView()
    }    
}

그리고 실행해보면?

Image

위와같이 loading indicator가 보이는걸 알 수 있다.

체크인 상태 확인하여 버튼을 다르게 만들기

우선 체크인 상태를 확인할 함수를 만들어 줘야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func getCheckedInStatus() {
    guard let profileRecordID = CloudKitManager.shared.profileRecordID else {
        return
    }
    
    CloudKitManager.shared.fetchRecord(with: profileRecordID) { [self] result in
        switch result {
        case .success(let record):
            if let reference = record[DDGProfile.kIsCheckedIn] as? CKRecord.Reference {
                if reference.recordID == location.id {
                    isCheckedIn = true
                    print("isCheckedIn = true")
                } else {
                    isCheckedIn = false
                    print("isCheckedIn = false")
                }
            }
        case .failure(_):
            print("Failed to Fetch Record")
        }
    }
}

updateCheckInStatus 함수와 결이 비슷하다.

이후 detailview로 가서

1
2
3
4
.onAppear(perform: {
    viewModel.getCheckedInProfiles()
    viewModel.getCheckedInStatus() // new
})

해당 코드를 추가해주면 된다.

Image

실행하면 이렇게 현재 콘솔에서 true라고 보이는걸 알 수 있다.


이때

Image

이렇게 또 background 에서 실행하는게 허용되지 않는다고 뜬다.

DispatchQueue를 통해 해결하도록 하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CloudKitManager.shared.fetchRecord(with: profileRecordID) { [self] result in
    DispatchQueue.main.async {
        switch result {
        case .success(let record):
            if let reference = record[DDGProfile.kIsCheckedIn] as? CKRecord.Reference {
                if reference.recordID == location.id {
                    // 생략
                }
            } else {
                isCheckedIn = false // new
                print("isCheckedIn = false - reference is nil")
            }
        case .failure(_):
            print("Failed to Fetch Record")
        }
    }
}

그리고 reference 자체가 없는 경우(아무도 체크인을 안했을 경우)에도 체크인을 하지 않은 경우이므로 예외처리를 해준다.

Image

이렇게 아무도 체크인을 안했을때도 false라고 콘솔에서 확인이 가능하다.


1
2
3
4
5
6
7
if reference.recordID == location.id {
    isCheckedIn = true
    print("isCheckedIn = true")
} else {
    isCheckedIn = false
    print("isCheckedIn = false")
}

이 부분을 이전글과 같은 방식으로

1
 isCheckedIn = reference.recordID == location.id

이렇게 대체해준다.


그리고 이제 진짜 버튼을 다르게 해보도록 한다.

이미 여러 강의에서도 알다시피 이럴땐 삼항연산자를 쓰는게 제일 깔끔하다.

1
LocationActionButton(color: viewModel.isCheckedIn ? .grubRed : .brandPrimary, imageName: viewModel.isCheckedIn ? "person.fill.xmark" : "person.fill.checkmark")

이제 실행해서 작동을 확인해보도록 한다.

Image

아주 작동이 잘 되는걸 알 수 있다.

앱 새로 설치시 체크인 버튼 안나오게 하기

앱을 새로 설치했을때는 당연히 클라우드에 해당 유저에 대한 정보가 없기때문에 체크인이 작동해서는 안된다.

그러므로 해당 버튼을 비활성화하고, 유져가 클라우드에 등록했을때 즉 profile이 생성이 되었을때 버튼을 보여주게 하면된다.

1
2
3
4
5
if let _ = CloudKitManager.shared.profileRecordID {
    Button {
        // 생략
    }
}

let 뒤에 _를 붙인 이유는 해당 변수를 우리가 사용할 일이 없기 때문이다.

RecordID가 존재한다면 버튼을 보여지게 하는 코드이다.

Image

첫화면을 넘어가서 바로 spot을 찾아 들어가면 버튼이 3개만 있는걸 알 수 있다

Image

현재 다른 시뮬레이터에서 체크인을 했기때문에 아래 harold가 보이는 상태.

실패시 Alert 추가

지금까지 제대로 된 통신이 성공했을때의 경우만 코드를 작성했는데 이제는 실패했을 때의 경우도 단순히 Console에 출력 또는 Guard let 부분의 경우 단순히 return으로 만 끝냈었는데 이제는 Alert를 통해 유져에게도 전달을 해보려 한다.

먼저 Alert 전체를 관리하는 Alertitem에서 아래와 같이 코드를 추가해준다.

1
2
3
4
5
6
7
8
9
10
11
static let unableToGetCheckInStatus = AlertItem(title: Text("Server Error"),
                message: Text("Unable to retrieve checked in status of the current user.\nPlease try again."),
                dismissButton: .default(Text("Ok")))

static let unableToCheckInOrOut         = AlertItem(title: Text("Server Error"),
                message: Text("We are unable to check in/out at this time.\nPlease try again."),
                dismissButton: .default(Text("Ok")))
    
static let unableToGetCheckedInProfiles = AlertItem(title: Text("Server Error"),
                message: Text("We are unable to get users checked into this location at this time.\nPlease try again."),
                dismissButton: .default(Text("Ok")))                

이후 LocationDetailVM으로 가서

관련된 부분에 failure 부분을 전부 수정해준다. (해당 부분은 코드가 길어서 생략)


Github: Dub-Dub-Grub Repository

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