Fix camera issues: front flash reset, rotation, Center Stage
Fixes: 1. Front flash now properly resets after capture - restorePreviewAfterFlash() called in photo delegate - Preview no longer stays white after taking photo 2. Device rotation support - updateVideoOrientation() updates capture connections - Uses modern videoRotationAngle API (iOS 17+) - Listens to UIDevice.orientationDidChangeNotification 3. Center Stage support for supported devices - Detects Center Stage availability on front camera - Toggle button in top control bar (person.crop.rectangle icon) - Yellow highlight when enabled - Updates availability on camera switch
This commit is contained in:
parent
9066635a4d
commit
c442acf464
@ -32,6 +32,12 @@ class CameraViewModel: NSObject {
|
|||||||
/// Toast message to display
|
/// Toast message to display
|
||||||
var toastMessage: String?
|
var toastMessage: String?
|
||||||
|
|
||||||
|
/// Whether Center Stage is available on this device
|
||||||
|
var isCenterStageAvailable = false
|
||||||
|
|
||||||
|
/// Whether Center Stage is currently enabled
|
||||||
|
var isCenterStageEnabled = false
|
||||||
|
|
||||||
let settings = SettingsViewModel() // Shared config
|
let settings = SettingsViewModel() // Shared config
|
||||||
|
|
||||||
// MARK: - Screen Brightness Handling
|
// MARK: - Screen Brightness Handling
|
||||||
@ -90,12 +96,74 @@ class CameraViewModel: NSObject {
|
|||||||
session.commitConfiguration()
|
session.commitConfiguration()
|
||||||
session.startRunning()
|
session.startRunning()
|
||||||
|
|
||||||
|
// Check Center Stage availability
|
||||||
|
updateCenterStageAvailability()
|
||||||
|
|
||||||
UIApplication.shared.isIdleTimerDisabled = true
|
UIApplication.shared.isIdleTimerDisabled = true
|
||||||
saveCurrentBrightness()
|
saveCurrentBrightness()
|
||||||
// Set screen to full brightness for best ring light effect
|
// Set screen to full brightness for best ring light effect
|
||||||
setBrightness(1.0)
|
setBrightness(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Center Stage
|
||||||
|
|
||||||
|
/// Updates Center Stage availability based on current camera
|
||||||
|
private func updateCenterStageAvailability() {
|
||||||
|
isCenterStageAvailable = AVCaptureDevice.isCenterStageEnabled || checkCenterStageSupport()
|
||||||
|
isCenterStageEnabled = AVCaptureDevice.isCenterStageEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the current device supports Center Stage
|
||||||
|
private func checkCenterStageSupport() -> Bool {
|
||||||
|
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Center Stage is available if the device has it as an active format feature
|
||||||
|
return device.activeFormat.isCenterStageSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles Center Stage on/off
|
||||||
|
func toggleCenterStage() {
|
||||||
|
guard isCenterStageAvailable else { return }
|
||||||
|
|
||||||
|
AVCaptureDevice.centerStageControlMode = .app
|
||||||
|
AVCaptureDevice.isCenterStageEnabled.toggle()
|
||||||
|
isCenterStageEnabled = AVCaptureDevice.isCenterStageEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Orientation
|
||||||
|
|
||||||
|
/// Updates video orientation based on device orientation
|
||||||
|
func updateVideoOrientation(for orientation: UIDeviceOrientation) {
|
||||||
|
guard let connection = photoOutput?.connection(with: .video) else { return }
|
||||||
|
|
||||||
|
// Calculate rotation angle (in degrees)
|
||||||
|
let rotationAngle: CGFloat
|
||||||
|
switch orientation {
|
||||||
|
case .portrait:
|
||||||
|
rotationAngle = 90
|
||||||
|
case .portraitUpsideDown:
|
||||||
|
rotationAngle = 270
|
||||||
|
case .landscapeLeft:
|
||||||
|
rotationAngle = 0
|
||||||
|
case .landscapeRight:
|
||||||
|
rotationAngle = 180
|
||||||
|
default:
|
||||||
|
rotationAngle = 90 // Default to portrait
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use modern rotation angle API (iOS 17+)
|
||||||
|
if connection.isVideoRotationAngleSupported(rotationAngle) {
|
||||||
|
connection.videoRotationAngle = rotationAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update video output connection
|
||||||
|
if let videoConnection = videoOutput?.connection(with: .video),
|
||||||
|
videoConnection.isVideoRotationAngleSupported(rotationAngle) {
|
||||||
|
videoConnection.videoRotationAngle = rotationAngle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func switchCamera() {
|
func switchCamera() {
|
||||||
guard let session = captureSession else { return }
|
guard let session = captureSession else { return }
|
||||||
session.beginConfiguration()
|
session.beginConfiguration()
|
||||||
@ -109,6 +177,9 @@ class CameraViewModel: NSObject {
|
|||||||
session.addInput(input)
|
session.addInput(input)
|
||||||
}
|
}
|
||||||
session.commitConfiguration()
|
session.commitConfiguration()
|
||||||
|
|
||||||
|
// Update Center Stage availability (only works on front camera)
|
||||||
|
updateCenterStageAvailability()
|
||||||
}
|
}
|
||||||
|
|
||||||
func capturePhoto() {
|
func capturePhoto() {
|
||||||
@ -131,13 +202,14 @@ class CameraViewModel: NSObject {
|
|||||||
|
|
||||||
let captureSettings = AVCapturePhotoSettings()
|
let captureSettings = AVCapturePhotoSettings()
|
||||||
photoOutput?.capturePhoto(with: captureSettings, delegate: self)
|
photoOutput?.capturePhoto(with: captureSettings, delegate: self)
|
||||||
|
|
||||||
// Restore preview after capture completes
|
|
||||||
try? await Task.sleep(for: .milliseconds(200))
|
|
||||||
isPreviewHidden = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restores the preview after front flash capture
|
||||||
|
func restorePreviewAfterFlash() {
|
||||||
|
isPreviewHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
func startRecording() {
|
func startRecording() {
|
||||||
guard let videoOutput = videoOutput, !isRecording else { return }
|
guard let videoOutput = videoOutput, !isRecording else { return }
|
||||||
let url = FileManager.default.temporaryDirectory.appendingPathComponent("video.mov")
|
let url = FileManager.default.temporaryDirectory.appendingPathComponent("video.mov")
|
||||||
@ -224,6 +296,9 @@ extension CameraViewModel: AVCapturePhotoCaptureDelegate {
|
|||||||
let image = UIImage(data: data) else { return }
|
let image = UIImage(data: data) else { return }
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
|
// Restore preview first (in case front flash was used)
|
||||||
|
restorePreviewAfterFlash()
|
||||||
|
|
||||||
// Store the captured image for preview
|
// Store the captured image for preview
|
||||||
capturedMedia = .photo(image)
|
capturedMedia = .photo(image)
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,9 @@ struct ContentView: View {
|
|||||||
.onDisappear {
|
.onDisappear {
|
||||||
viewModel.restoreBrightness()
|
viewModel.restoreBrightness()
|
||||||
}
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||||
|
viewModel.updateVideoOrientation(for: UIDevice.current.orientation)
|
||||||
|
}
|
||||||
.sheet(isPresented: $showPaywall) {
|
.sheet(isPresented: $showPaywall) {
|
||||||
ProPaywallView()
|
ProPaywallView()
|
||||||
}
|
}
|
||||||
@ -163,6 +166,22 @@ struct ContentView: View {
|
|||||||
|
|
||||||
private var topControlBar: some View {
|
private var topControlBar: some View {
|
||||||
HStack {
|
HStack {
|
||||||
|
// Center Stage button (only shown when available)
|
||||||
|
if viewModel.isCenterStageAvailable {
|
||||||
|
Button {
|
||||||
|
viewModel.toggleCenterStage()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: viewModel.isCenterStageEnabled ? "person.crop.rectangle.fill" : "person.crop.rectangle")
|
||||||
|
.font(.body)
|
||||||
|
.foregroundStyle(viewModel.isCenterStageEnabled ? .yellow : .white)
|
||||||
|
.padding(Design.Spacing.small)
|
||||||
|
.background(.ultraThinMaterial, in: .circle)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(String(localized: "Center Stage"))
|
||||||
|
.accessibilityValue(viewModel.isCenterStageEnabled ? "On" : "Off")
|
||||||
|
.accessibilityHint(String(localized: "Keeps you centered in frame"))
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// Grid toggle
|
// Grid toggle
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user