Início rápido
Vamos construir uma tela que mostra a câmera, o ângulo do cotovelo ao vivo e as repetições. No fim você terá as três peças que qualquer integração usa: fonte de poses, sessão e eventos.
-
Crie a fonte de poses e a sessão
VisionPoseProviderlê a câmera frontal e entrega umaPosepor quadro.MotionSessionrecebe essas poses para um exercício e um lado do corpo.import KinesiaMotionimport KinesiaMotionVisionlet camera = VisionPoseProvider(camera: .front)let session = MotionSession(exercise: ExerciseLibrary.elbowFlexion, side: .right) -
Peça permissão e comece
if !VisionPoseProvider.isAuthorized {guard await VisionPoseProvider.requestAccess() else { return }}session.start(with: camera)A câmera liga quando a sessão começa a ler poses e desliga sozinha ao chamar
session.stop(). -
Desenhe com o snapshot
MotionSessioné@Observable: o SwiftUI redesenha sempre quesession.snapshotmuda.Text(session.snapshot.value.map { "\(Int($0))°" } ?? "—")Text("\(session.snapshot.repCount) repetições") -
Reaja aos eventos
Os eventos são para o que acontece uma única vez: uma repetição terminada, uma correção de técnica.
for await event in session.events() {switch event {case .repCompleted(let rep): print("✓ \(Int(rep.peak))°")case .formFault(let fault): print("Corrija: \(fault)")default: break}}
O exemplo completo
Seção intitulada “O exemplo completo”import SwiftUIimport AVFoundationimport KinesiaMotionimport KinesiaMotionVision
struct ElbowView: View { @State private var camera = VisionPoseProvider(camera: .front) @State private var session = MotionSession(exercise: ExerciseLibrary.elbowFlexion, side: .right) @State private var message = "Fique de lado e mostre o braço inteiro"
var body: some View { ZStack(alignment: .bottom) { CameraPreview(session: camera.captureSession) .ignoresSafeArea()
VStack(spacing: 8) { Text(session.snapshot.value.map { "\(Int($0))°" } ?? "—") .font(.system(size: 64, weight: .bold, design: .rounded)) Text("\(session.snapshot.repCount) repetições · melhor \(Int(session.snapshot.best))°") Text(message).font(.callout) } .padding() .background(.ultraThinMaterial, in: .rect(cornerRadius: 20)) .padding() } .task { if !VisionPoseProvider.isAuthorized { guard await VisionPoseProvider.requestAccess() else { return } } session.start(with: camera)
for await event in session.events() { switch event { case .repCompleted(let rep): message = "Muito bem! \(Int(rep.peak))°" case .repRejected: message = "Essa não conta: cuide da técnica" case .formFault(.keepUpperArmStill): message = "Mantenha o braço junto ao corpo" case .visibilityChanged(let visibility) where !visibility.isVisible: message = "Não vejo o braço inteiro" default: break } } } .onDisappear { session.stop() } }}
/// The camera image behind everything.struct CameraPreview: UIViewRepresentable { let session: AVCaptureSession
func makeUIView(context: Context) -> PreviewView { let view = PreviewView() view.previewLayer.session = session view.previewLayer.videoGravity = .resizeAspectFill return view }
func updateUIView(_ uiView: PreviewView, context: Context) {}
final class PreviewView: UIView { override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self } var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer } }}Próximos passos
Seção intitulada “Próximos passos”- Entenda como o motor funciona por dentro.
- Adicione uma medição inicial para saber de onde o paciente parte.
- Use a qualidade da medição para orientar o posicionamento diante da câmera.