Arquitetura
VisΓ£o completa da arquitetura do Camera App, padrΓ΅es utilizados e fluxo de dados.
ποΈ PadrΓ£o Arquitetural
A aplicaΓ§Γ£o utiliza MVVM (Model-View-ViewModel) com comunicaΓ§Γ£o reativa via Combine, garantindo:
- β SeparaΓ§Γ£o clara de responsabilidades
- β Testabilidade
- β Manutenibilidade
- β Reatividade declarativa
π Diagrama Geral
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SwiftUI Views β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β ContentView β β Teleprompter β β CameraPreviewβ β
β β β β Overlay β β View β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β β β β β
βββββββββββΌββββββββββββββββββΌββββββββββββββββββΌββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ViewModels β
β ββββββββββββββββββββββββββββ βββββββββββββββββββββββ β
β β CameraViewModel β β TeleprompterViewModelβ β
β β @Published properties β β @Published props β β
β β Business logic β β Scroll management β β
β ββββββββββ¬ββββββββββββββββββ βββββββββββββββββββββββ β
β β β
βββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Controllers & Services β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β
β β CaptureSession β β SegmentedRecorder β β
β β Controller β β β β
β β - Session management β β - Recording segments β β
β β - Device config β β - Delegate callbacks β β
β β - Format selection β β β β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AVFoundation β
β AVCaptureSession β’ AVCaptureDevice β
β AVCaptureMovieFileOutput β’ AVCaptureDeviceInput β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π¦ Responsabilidades por Camada
1. View (SwiftUI)
Responsabilidades:
- RenderizaΓ§Γ£o da interface
- Captura de gestos do usuΓ‘rio
- Binding bidirecional com ViewModels via
@Published
CaracterΓsticas:
- Declarativa e reativa
- Sem lΓ³gica de negΓ³cio
- Observa mudanΓ§as do ViewModel via
@ObservedObject
ou@StateObject
Exemplo:
struct ContentView: View {
@StateObject private var model = CameraViewModel()
var body: some View {
ZStack {
CameraPreviewRepresentable(controller: model.controller)
Button("Record") {
model.toggleRecording() // Delega para ViewModel
}
}
}
}
2. ViewModel
Responsabilidades:
- Estado da aplicaΓ§Γ£o (
@Published
properties) - LΓ³gica de negΓ³cio e coordenaΓ§Γ£o
- TransformaΓ§Γ£o de dados para a View
- ComunicaΓ§Γ£o com Controllers
CaracterΓsticas:
- ObservΓ‘vel (
ObservableObject
) - Sem dependΓͺncia de SwiftUI
- TestΓ‘vel isoladamente
Exemplo:
class CameraViewModel: ObservableObject {
@Published var isRecording: Bool = false
@Published var segments: [RecordedSegment] = []
private let controller: CaptureSessionController
private var recorder: SegmentedRecorder?
func toggleRecording() {
if isRecording {
recorder?.stopCurrentSegment()
} else {
recorder?.startNewSegment()
}
isRecording.toggle()
}
}
3. Controller/Service
Responsabilidades:
- Gerenciamento direto de APIs do sistema
- ConfiguraΓ§Γ£o e controle do AVCaptureSession
- GravaΓ§Γ£o e processamento de arquivos
- OperaΓ§Γ΅es assΓncronas em queues dedicadas
CaracterΓsticas:
- Sem estado UI
- Thread-safe (usa dispatch queues)
- Encapsula complexidade de AVFoundation
Exemplo:
class CaptureSessionController {
private let session = AVCaptureSession()
private let sessionQueue = DispatchQueue(label: "camera.session.queue")
func configureSession(desiredFrameRate: DesiredFrameRate) {
sessionQueue.async {
self.session.beginConfiguration()
// ConfiguraΓ§Γ£o pesada...
self.session.commitConfiguration()
}
}
}
4. AVFoundation
Responsabilidades:
- Camada de hardware/sistema
- Captura de vΓdeo e Γ‘udio
- CodificaΓ§Γ£o e gravaΓ§Γ£o em disco
CaracterΓsticas:
- Framework do sistema
- APIs nΓ£o modificΓ‘veis
- Configurado via Controllers
π Fluxo de Dados
Fluxo de InicializaΓ§Γ£o
sequenceDiagram
participant ContentView
participant CameraViewModel
participant Controller
participant System
ContentView->>CameraViewModel: .onAppear()
CameraViewModel->>CameraViewModel: requestPermissionsAndConfigure()
CameraViewModel->>Controller: requestPermissions()
Controller->>System: AVCaptureDevice.requestAccess(.video)
Controller->>System: AVCaptureDevice.requestAccess(.audio)
System-->>Controller: granted = true
Controller-->>CameraViewModel: completion(granted: true)
CameraViewModel->>CameraViewModel: isAuthorized = true
CameraViewModel->>Controller: configureSession(fps: .fps60)
Controller->>System: Configure devices and session
Controller-->>CameraViewModel: completion(error: nil)
CameraViewModel->>Controller: startSession()
CameraViewModel-->>ContentView: isSessionRunning = true
Fluxo de GravaΓ§Γ£o
sequenceDiagram
participant User
participant ContentView
participant CameraViewModel
participant SegmentedRecorder
participant AVFoundation
participant FileSystem
User->>ContentView: Tap Record Button
ContentView->>CameraViewModel: toggleRecording()
CameraViewModel->>SegmentedRecorder: startNewSegment()
SegmentedRecorder->>FileSystem: Create temp URL
SegmentedRecorder->>AVFoundation: output.startRecording(to: tempURL)
AVFoundation-->>SegmentedRecorder: didStartRecordingTo
SegmentedRecorder-->>CameraViewModel: (recording started)
CameraViewModel-->>ContentView: isRecording = true
Note over User,AVFoundation: User records...
User->>ContentView: Tap Stop Button
ContentView->>CameraViewModel: toggleRecording()
CameraViewModel->>SegmentedRecorder: stopCurrentSegment()
SegmentedRecorder->>AVFoundation: output.stopRecording()
AVFoundation-->>SegmentedRecorder: didFinishRecordingTo: URL
SegmentedRecorder-->>CameraViewModel: delegate.didFinishSegment(url)
CameraViewModel->>CameraViewModel: Generate thumbnail
CameraViewModel-->>ContentView: segments.append(segment)
Ver fluxo completo de dados β
π§© Componentes Principais
CameraViewModel
Papel: ViewModel central da aplicaΓ§Γ£o
Responsabilidades:
- Gerenciar estado de gravaΓ§Γ£o
- Coordenar CaptureSessionController e SegmentedRecorder
- Processar e concatenar segmentos
- Aplicar filtros e salvar vΓdeos
CaptureSessionController
Papel: Controller de baixo nΓvel para AVFoundation
Responsabilidades:
- Configurar AVCaptureSession
- Gerenciar dispositivos de captura
- Controlar zoom, foco, exposiΓ§Γ£o
- Aplicar estabilizaΓ§Γ£o e codec
SegmentedRecorder
Papel: Gerenciador de gravaΓ§Γ£o segmentada
Responsabilidades:
- Gravar mΓΊltiplos takes independentes
- Gerenciar arquivos temporΓ‘rios
- Notificar via delegate quando segmento termina
TeleprompterOverlay
Papel: Componente UI do teleprompter
Responsabilidades:
- Renderizar overlay flutuante
- Gerenciar scroll automΓ‘tico
- Responder a interaΓ§Γ΅es (drag, resize)
π DependΓͺncias entre MΓ³dulos
CameraApp (Entry Point)
βββ ContentView
βββ CameraViewModel
β βββ CaptureSessionController
β β βββ AVFoundation
β βββ SegmentedRecorder
β βββ AVFoundation
βββ CameraPreviewView (UIKit Bridge)
β βββ AVCaptureVideoPreviewLayer
βββ TeleprompterOverlay
βββ TeleprompterViewModel
βββ TeleprompterTextView (UIKit Bridge)
βββ UITextView
PrincΓpio: DependΓͺncias fluem para baixo. Views nΓ£o conhecem Controllers. Controllers nΓ£o conhecem Views.
π§΅ Threading Model
Queues Utilizadas
Queue | PropΓ³sito | Tipo |
---|---|---|
Main |
AtualizaΓ§Γ£o de UI | Serial |
sessionQueue |
ConfiguraΓ§Γ£o AVCapture | Serial |
Global(.userInitiated) |
Thumbnails | Concurrent |
Global(.utility) |
Export de vΓdeo | Concurrent |
Regras de Threading
β οΈ CrΓtico
Session Configuration: Sempre nasessionQueue
sessionQueue.async {
session.beginConfiguration()
// modifications...
session.commitConfiguration()
}
β οΈ CrΓtico
UI Updates: Sempre naMain
thread
DispatchQueue.main.async {
self.isSessionRunning = true
}
β Best Practice
Device Lock: NasessionQueue
com try-catch
sessionQueue.async {
try device.lockForConfiguration()
device.videoZoomFactor = 2.0
device.unlockForConfiguration()
}
π PadrΓ΅es de Design
Observer Pattern
Usado via Combine para reatividade:
@Published var isRecording: Bool = false
View observa automaticamente:
.onChange(of: model.isRecording) { newValue in
// React to change
}
Delegation Pattern
Usado para callbacks de gravaΓ§Γ£o:
protocol SegmentedRecorderDelegate: AnyObject {
func recorder(_ recorder: SegmentedRecorder, didFinishSegment url: URL)
func recorder(_ recorder: SegmentedRecorder, didFailWith error: Error)
}
CameraViewModel implementa:
extension CameraViewModel: SegmentedRecorderDelegate {
func recorder(_ recorder: SegmentedRecorder, didFinishSegment url: URL) {
generateThumbnail(for: url)
}
}
Coordinator Pattern
Usado em UIViewRepresentable bridges:
struct TeleprompterTextView: UIViewRepresentable {
class Coordinator: NSObject, UITextViewDelegate {
var isProgrammaticScroll: Bool = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard !isProgrammaticScroll else { return }
// Update SwiftUI binding
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
ποΈ Arquitetura de Arquivos
Camera/
βββ App Layer
β βββ CameraApp.swift # Entry point
β
βββ View Layer
β βββ ContentView.swift # Main orchestrator
β βββ CameraPreviewView.swift # UIKit bridge
β βββ TeleprompterOverlay.swift # Teleprompter UI
β βββ TeleprompterTextView.swift # UITextView bridge
β βββ GlassCompat.swift # Reusable UI components
β
βββ ViewModel Layer
β βββ CameraViewModel.swift # Main state & logic
β βββ TeleprompterViewModel.swift # Teleprompter state
β
βββ Controller/Service Layer
β βββ CaptureSessionController.swift # AVFoundation manager
β βββ SegmentedRecorder.swift # Recording manager
β
βββ Resources
βββ Assets.xcassets/ # Visual assets
βββ Camera.entitlements # Permissions
π PrincΓpios Seguidos
Single Responsibility
Cada classe tem uma ΓΊnica responsabilidade clara:
CameraViewModel
: Estado e coordenaΓ§Γ£oCaptureSessionController
: ConfiguraΓ§Γ£o de cΓ’meraSegmentedRecorder
: GravaΓ§Γ£o de segmentosTeleprompterViewModel
: LΓ³gica de scroll
Dependency Inversion
Controllers nΓ£o conhecem ViewModels. ViewModels conhecem Controllers (injeΓ§Γ£o de dependΓͺncia):
class CameraViewModel {
private let controller: CaptureSessionController
init(controller: CaptureSessionController = .init()) {
self.controller = controller
}
}
Open/Closed
ExtensΓvel sem modificar cΓ³digo existente. Exemplo: adicionar novos filtros:
enum VideoFilter: String, CaseIterable {
case none
case mono
case sepia // β Novo filtro
}
π PrΓ³ximos Passos
Agora que vocΓͺ entende a arquitetura geral:
- Explore os Componentes - Detalhes de cada mΓ³dulo
- Veja o Fluxo de Dados - Como os dados fluem
- Entenda as Escolhas TΓ©cnicas - Por que essas decisΓ΅es
β ComeΓ§ando | Componentes β |