Skip to content
Eric Kwon / 권승용 edited this page Jun 20, 2023 · 3 revisions

Eric의 개발 중 주요 경험 문서

Timer를 이용한 반복 애니메이션 구현

  • 반복적인 운동 이미지를 구현하기 위해, 서로 다른 이미지 두 장을 일정 시간마다 반복적으로 바꿔 보여주는 방식으로 구현하였습니다.
  // 한 운동마다 불리는 일회성 메서드
  func startExercise() {
      self.currentCount = 0
      
      // 1.5초마다 이미지 바꾸기
      Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { timer in
          if !self.isWorkoutPaused {
              if self.isWorkoutStopped {
                  timer.invalidate()
                  self.currentImageIndex = 0
                  self.stopWorkout()
              }
              
              if self.isPreparingTime {
                  timer.invalidate()
              }
              
              let workoutImagesCount = self.workouts[self.currentWorkoutIndex].workoutImageNames.count
              self.currentImageIndex += 1
              self.currentImageName = self.workouts[self.currentWorkoutIndex].workoutImageNames[self.currentImageIndex % workoutImagesCount]
          }
      }
      
      // 1초마다 숫자 세기
      Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
          if !self.isWorkoutPaused {
              if self.isWorkoutStopped {
                  timer.invalidate()
                  self.currentWorkoutIndex = 0
                  self.stopWorkout()
              }
              
              self.currentCount += 1
              
              // 설정 횟수 모두 도달하게 되면 Rest 하고 다음 인덱스로 넘어가기
              if self.currentCount == 45 {
                  // 만약 마지막 운동이었다면 WorkoutSummary뷰 불러올 수 있도록 flag 변수 설정
                  if self.currentWorkoutIndex + 1 == self.workouts.count {
                      timer.invalidate()
                      self.currentCount = 0
                      self.isExercising = false
                      self.isPreparingTime = false
                      self.isWorkoutStopped = true
                      // 마지막 운동이 아니라면 RestTimeView 불러올 수 있도록 flag 변수 설정
                  } else {
                      timer.invalidate()
                      self.selectedWorkouts.remove(at: 0)
                      self.currentCount = 0
                      self.isExercising = false
                      self.isPreparingTime = true
                  }
              }
          }
      }
  }

Timer를 이용한 커스텀 원형 프로그레스 바 구현

  • 남은 운동 시간에 따른 원형 프로그레스 바 구현이 필요하여, Circle과 ZStack, Timer를 사용해 구현해 보았습니다.
struct CircularProgressBar: View {
    
    // 상위 뷰에서 1초마다 변경되는 currentCount 바인딩하여 사용
    @Binding var currentCount: Int
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
                .fill(LinearGradient(colors: [Color("buttonBackgroundStart"), Color("buttonBackgroundEnd")], startPoint: .topLeading, endPoint: .bottomTrailing))
                .frame(width: 100, height: 100)
                .overlay(
                    Circle()
                        .trim(from: 0, to: progress())
                        .stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
                        .foregroundColor(.red)
                        .rotationEffect(Angle(degrees: -90))
                )
        }
        .onChange(of: currentCount) { newValue in
            print(newValue)
        }
    }
    
    func progress() -> CGFloat {
        return CGFloat(currentCount) / 45
    }
}

커스텀 모달 뷰 구현

  • 앞으로 남은 운동은 무엇이 있는지 확인하고, 운동에 대한 자세한 설명을 보기 위한 디테일 화면을 커스텀 모달 뷰를 사용해 구현했습니다.
struct ExerciseListModalView: View {
    
    @Binding var workouts: [Workout]
    
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                ForEach(Array(zip(workouts.indices, workouts)), id: \.1) { index, workout in
                    VStack(spacing: 0) {
                        HStack {
                            if index == 0 {
                                Text("현재 동작")
                            } else if index == workouts.count - 1 {
                                Text("마지막 동작")
                            } else {
                                Text("다음 동작")
                            }
                            Spacer()
                        }
                        .padding(EdgeInsets(top: 0, leading: 25, bottom: 5, trailing: 0))
                        .foregroundColor(.white)
                        
                        CustomTableViewCell(imageNames: workout.workoutImageNames, exerciseName: workout.workoutName, instructions: workout.instructions, considerations: workout.considerations)
                    }
                }
            }
            .background(Color("sheetBackground"))
            .edgesIgnoringSafeArea(.bottom)
            // bottom sheet가 덜 올라와 있으면 ScrollView 사이즈 줄이기
            .if(geometry.frame(in: .global).minY > UIScreen.main.bounds.midY) { view in
                view.frame(maxHeight: UIScreen.main.bounds.height * 0.2)
            }
            .if(geometry.frame(in: .global).minY < UIScreen.main.bounds.midY) { view in
                view.frame(maxHeight: UIScreen.main.bounds.height * 0.8)
            }
        }
    }
}