Files
PlantGuide/PlantGuide/Presentation/Scenes/Camera/CameraPreviewView.swift
Trey t 136dfbae33 Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow
- Add Core Data persistence for plants, care schedules, and cached API data
- Create collection view with grid/list layouts and filtering
- Build plant detail views with care information display
- Integrate Trefle botanical API for plant care data
- Add local image storage for captured plant photos
- Implement dependency injection container for testability
- Include accessibility support throughout the app

Bug fixes in this commit:
- Fix Trefle API decoding by removing duplicate CodingKeys
- Fix LocalCachedImage to load from correct PlantImages directory
- Set dateAdded when saving plants for proper collection sorting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:18:01 -06:00

114 lines
2.8 KiB
Swift

//
// CameraPreviewView.swift
// PlantGuide
//
// Created by Trey Tartt on 1/21/26.
//
import SwiftUI
import AVFoundation
/// UIViewRepresentable wrapper for AVCaptureVideoPreviewLayer
struct CameraPreviewView: UIViewRepresentable {
let session: AVCaptureSession
func makeUIView(context: Context) -> CameraPreviewUIView {
let view = CameraPreviewUIView()
view.session = session
return view
}
func updateUIView(_ uiView: CameraPreviewUIView, context: Context) {
uiView.session = session
}
}
/// UIView subclass that hosts the AVCaptureVideoPreviewLayer
final class CameraPreviewUIView: UIView {
// MARK: - Properties
var session: AVCaptureSession? {
didSet {
previewLayer.session = session
}
}
private var previewLayer: AVCaptureVideoPreviewLayer {
guard let layer = layer as? AVCaptureVideoPreviewLayer else {
fatalError("Expected AVCaptureVideoPreviewLayer but got \(type(of: layer))")
}
return layer
}
// MARK: - Layer Class Override
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
configurePreviewLayer()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configurePreviewLayer()
}
// MARK: - Configuration
private func configurePreviewLayer() {
previewLayer.videoGravity = .resizeAspectFill
backgroundColor = .black
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
// Update preview layer connection orientation if needed
updatePreviewLayerOrientation()
}
private func updatePreviewLayerOrientation() {
guard let connection = previewLayer.connection,
connection.isVideoRotationAngleSupported(0) else {
return
}
// Get the current interface orientation
let windowScene = window?.windowScene
let interfaceOrientation = windowScene?.interfaceOrientation ?? .portrait
// Map interface orientation to video rotation angle
let rotationAngle: CGFloat
switch interfaceOrientation {
case .portrait:
rotationAngle = 90
case .portraitUpsideDown:
rotationAngle = 270
case .landscapeLeft:
rotationAngle = 180
case .landscapeRight:
rotationAngle = 0
@unknown default:
rotationAngle = 90
}
if connection.isVideoRotationAngleSupported(rotationAngle) {
connection.videoRotationAngle = rotationAngle
}
}
}
// MARK: - Preview Provider
#Preview {
CameraPreviewView(session: AVCaptureSession())
.ignoresSafeArea()
}