애플워치 앱에서 아래 영상의 운동 기록 삭제 여부를 묻는 모달과 같은 컴포넌트 구현하는 방법에 대한 설명입니다.
모달을 표시하는 방법은 위 영상의 삭제 여부를 묻는 모달이 실제로 구현되어 있는 코드를 예시로 설명하였습니다.
1.
모달이 표시될 View에 모달 표시하는 함수 구현하기
모달이 표시되어야 하는 화면/요소에 모달 컴포넌트를 사용하는 React Native와 다르게, SwiftUI에서는 모달이 표시되어야 하는 화면/요소 컴포넌트 함수에 대해 모달을 표시하는 메서드를 호출하는 형식으로 모달을 구현해야 합니다.
영상의 모달이 표시되는 화면은 WorkoutListView 라는 컴포넌트인데, 해당 컴포넌트에 애플이 자체적으로 구현해둔 fullScreenCover() 라는 함수를 호출해서 메서드에 필요한 인자를 전달하는 형식으로 구현되어 있습니다. ( fullScreenCover 애플 공식문서 )
// 운동 삭제 모달이 표시되는 WorkoutListView 코드 예시
// WorkoutListView내에서 컨테이너 역할을 하는 컴포넌트인 VStack 컴포넌트의
// fullScreenCover() 메서드를 호출합니다.
VStack {
//...화면 구성하는 컴포넌트 코드
}
.fullScreenCover(isPresented: $isWorkoutDeleteCoverShown, content: {
// 모달의 내용으로 표시되는 컴포넌트를 여기에 구현합니다.
NavigationView {
WorkoutDeleteView(isWorkoutDeleteCoverShown: $isWorkoutDeleteCoverShown)
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
})
// isPresented - 모달 표시여부를 결정하는 변수를 받는 인자입니다.
// content - 모달의 내용으로 표시될 컴포넌트를 {} 블록 안에 구현합니다.
// 각 인자 관련 구체적인 내용은 위에 첨부한 공식 문서를 참고해주세요.
Swift
복사
번핏 애플워치 앱에 구현되어 있는 모달 컴포넌트는 대부분 위 코드처럼 NavigationView 라는 컴포넌트로 감싸져 있습니다.
fullScreenCover() 의 content 로 표시된 컴포넌트의 경우 default로 좌상단에 취소 버튼이 표시되는데,(아래 사진 참고) 이를 제거하기 위해서 필요한 처리입니다.
좌상단 취소 버튼이 필요한 경우가 아니라면 동일한 코드로 모달 컴포넌트를 감싸줘야 합니다. ( NavigationView 컴포넌트에 달려있는 navigationTitle, navigationBarTitleDisplayMode 코드도 항상 세트로 붙여야 합니다. )
2.
모달로 표시될 컴포넌트 생성
React Native에서의 모달은 통상적으로 Modal 컴포넌트를 사용하는 것과 다르게, SwiftUI에서는 모달에 렌더링 되는 컴포넌트를 일반적인 View와 비슷하게 구성하면 됩니다. (즉 모달 안에 표시되는 컴포넌트라고 해서 따로 더 처리해야하거나 하는 부분은 없습니다.)
// 1번에서 모달의 내용으로 표시될 컴포넌트로 사용된 WorkoutDeletView 코드
// 모달 관련 처리가 따로 필요없이 VStack, Text등 일반적인 View를 구성하는 SwiftUI 컴포넌트들로
// 구성된 컴포넌트입니다.
struct WorkoutDeleteView: View {
//... 기타 코드
// 아래 코드 부분이 UI를 보여주는 부분입니다.
// 어떤 코드가 안에 있는지보다, 모달 관련해서 따로 처리하는 부분 없이 일반적인 컴포넌트
// 만드는 것과 동일하게 컴포넌트를 구성하면 된다는 점이 포인트입니다.
var body: some View {
VStack {
Text("WORKOUTDELETEVIEW_TITLE")
.font(.system(size: 16 * sizeScaleFactor.widthFactor, weight: .bold))
.lineLimit(1)
.minimumScaleFactor(0.1)
Spacer()
VStack {
Text("WORKOUTDELETEVIEW_TEXT")
.font(.system(size: 14 * sizeScaleFactor.widthFactor, weight: .medium))
.lineSpacing(2)
.minimumScaleFactor(0.1)
.multilineTextAlignment(.center)
}
.padding(.horizontal, textPadding * sizeScaleFactor.widthFactor)
Spacer()
HStack(spacing: 8 * sizeScaleFactor.widthFactor) {
Button {
isWorkoutDeleteCoverShown = false
dismiss()
} label: {
Text("BUTTON_CANCEL")
.basicButtonTextFormat(fontSize: 15,fontWeight: (currentLanguage == "en") ? .medium : .bold)
.basicButtonFormat(foregroundColor: .white, backgroundColor: Color("#1F2D3A"))
}
.buttonStyle(.plain)
Button {
isWorkoutDeleteCoverShown = false
realmManager.deleteExercise()
realmManager.emptyWorkoutTimer()
hkWorkoutManager.endWorkout()
// TODO: (헬스킷) 데이터 리셋 필요한지 파악 필요
} label: {
Text("BUTTON_DELETE")
.basicButtonTextFormat(fontSize: 15,fontWeight: (currentLanguage == "en") ? .medium : .bold)
.basicButtonFormat(foregroundColor: .white, backgroundColor: .red)
}
.buttonStyle(.plain)
}//: Button HStack
}//: VStack
}//: body
}
Swift
복사
