I would make possible to take two photos, with one UIViewControllerRepresentable and Coordinator, but photo will not appear, why?
struct ContentView: View {
#State var camera = false
#State private var holeTop: UIImage?
#State private var holeBottom: UIImage?
#State private var selectedImage: UIImage?
var body: some View {
VStack(spacing: 0) {
Form {
Section {
HStack {
Circle()
.foregroundColor(.red)
.shadow(radius: 4)
.frame(width: 44, height: 44)
.overlay {
holeTop != nil ? Image(uiImage: holeTop!).resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white) :
Image(systemName: "camera")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white)
}.onTapGesture {
selectedImage = holeTop
camera = true
}.padding(24)
Text("Hole top")
.foregroundColor(.gray)
}
HStack {
Circle()
.foregroundColor(.red)
.shadow(radius: 4)
.frame(width: 44, height: 44)
.overlay {
holeTop != nil ? Image(uiImage: holeBottom!).resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white) :
Image(systemName: "camera")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white)
}.onTapGesture {
selectedImage = holeBottom
camera = true
}.padding(24)
Text("Hole bottom")
.foregroundColor(.gray)
}
} header: {
Text("")
}
}
}.sheet(isPresented: $camera) {
CameraView(selectedImage: $selectedImage)
}
}
}
struct CameraView: UIViewControllerRepresentable {
#Binding var selectedImage: UIImage?
#Environment(\.presentationMode) var isPresented
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
uiViewController.sourceType = .camera
}
func makeCoordinator() -> Coordinator {
Coordinator(picker: self)
}
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var picker: CameraView
init(picker: CameraView) {
self.picker = picker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let selectedImage = info[.originalImage] as? UIImage else { return }
self.picker.selectedImage = selectedImage
self.picker.isPresented.wrappedValue.dismiss()
}
}
Related
I have a barcode scanner that is using Vision, and it works great. How do I get the camera to play nicely with my VStack? Currently when I add the cameraView, the cameraView doesn't play nicely with the VStack, meaning it gets added as AVCaptureVideoPreviewLayer frame that I have to use to display the camera.
How can I make this work with the VStack so it's easier modified?
struct BarcodeScannerView: View {
var pitchDegrees: CGFloat
var cameraView = CameraView()
var body: some View {
VStack(spacing: 20) {
Rectangle()
.frame(width: 40, height: 5)
.cornerRadius(3)
Text("Barcode Scanner")
.multilineTextAlignment(.center)
.font(.body)
Spacer()
cameraView
Spacer()
Button(action: {
print("Scan")
}, label: {
Text("Scan")
})
.frame(width: 200, height: 44)
.background(Color.black)
.foregroundColor(Color.white)
.clipShape(Rectangle())
}//VStack
.frame(maxWidth: .infinity)
.frame(height: 300)
.padding()
.background(Color("Warm White"))
.clipShape(Rectangle())
.shadow(radius: 5)
.padding(.all, 5.0)
}//body
}
CameraView
import Foundation
import SwiftUI
struct CameraView: UIViewControllerRepresentable {
typealias UIViewControllerType = CameraViewController
private let cameraViewController: CameraViewController
init() {
cameraViewController = CameraViewController()
}//init
func makeUIViewController(context: Context) -> CameraViewController {
cameraViewController
}//makeUIViewController
func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {
}//updateUIViewController
}//struct
CameraViewController
class CameraViewController: UIViewController {
...
func setupPreview() {
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.connection?.videoOrientation = .portrait
previewLayer.frame = CGRect(x: 0, y: 0, width: view.frame.width - 30, height: 200)
previewLayer.cornerRadius = 15.0
previewLayer.borderWidth = 1
previewLayer.borderColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
previewLayer.shadowRadius = 5.0
view.layer.addSublayer(previewLayer)
}//setupPreview
...
}
I am trying to build an app where a user can insert the name of the movie and can add an image directly into the app from the photo library (using UIKit. Thankfully the part where the user can insert the text and image from the photo library works. My issue is transferring that data from the .sheet to a list. The info in the TextFields that the user inserts works fine and is shown in the list, but the image doesn't show. I keep getting the error "Cannot convert value of type 'ImagePickerView' to expected argument type 'String'". I don't know how to fix this issue. This issue comes in the ContentView.swift file, in the MovieRow struct when I try to insert the Image(). Any help would be appreciated. Thanks in advance.
Below is my ContentView file. d
// ContentView.swift
// MovieListEditttt
//
import SwiftUI
struct ContentView: View {
#State var movieAdd: [MovieAdd] = []
#State private var newMovieName: String = ""
#State private var showNewMovie = false
#State private var newMovieImage = UIImage()
var body: some View {
ZStack {
VStack {
HStack {
Text("Movies Watched Ratings")
.font(.system(size: 40, weight: .black, design: .rounded
))
Spacer()
Button(action: {
self.showNewMovie = true
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle)
.foregroundColor(.yellow)
}
}
List{
ForEach(movieAdd) {movie in
movieRow(movieAdd: movie)
}
}
}
if showNewMovie {
BlankView(bGColor: .black)
.opacity(0.5)
.onTapGesture {
self.showNewMovie = false
}
NewMovieView(isShow: $showNewMovie, addMovie: $movieAdd, newMovieName: newMovieName)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct movieRow: View {
#ObservedObject var movieAdd : MovieAdd
var body: some View {
VStack {
Image(movieAdd.movieImage)
.resizable()
.frame(width: 100, height: 100)
Text(movieAdd.movieName)
}
}
}
struct BlankView: View {
var bGColor: Color
var body: some View {
VStack {
Spacer()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(bGColor)
.edgesIgnoringSafeArea(.all)
}
}
Here is my MovieAdd.swift file where I initialize all variables that will be put inside the list.
import Foundation
class MovieAdd: ObservableObject, Identifiable {
var id = UUID()
#Published var movieName = ""
#Published var isComplete : Bool = false
#Published var movieImage : ImagePickerView
init(movieName: String, isComplete: Bool = false, movieImage: ImagePickerView) {
self.movieName = movieName
self.isComplete = isComplete
self.movieImage = movieImage
}
}
And here is my NewMovieView.swift file where the user will be able to insert their Movie information into a TextField, and insert an image from their Photos library. Here is also where I used UIKit.
import SwiftUI
struct NewMovieView: View {
#Binding var isShow: Bool
#Binding var addMovie: [MovieAdd]
#State var newMovieName: String = ""
#State var isShowingImagePicker = false
#State var imageInBlackBox = UIImage()
var body: some View {
ScrollView {
VStack {
VStack (alignment: .leading) {
HStack {
Text("Add a New Movie")
.font(.system(.title, design: .rounded))
.bold()
}
ZStack {
VStack {
HStack (alignment: .center){
Spacer()
Image(uiImage: imageInBlackBox)
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.border(Color.black, width: 3)
.clipped()
Spacer()
}
VStack {
Spacer()
Button(action: {
self.isShowingImagePicker.toggle()
}, label: {
Text("Select Image")
.font(.system(size: 15))
})
.sheet(isPresented: $isShowingImagePicker, content: { ImagePickerView(isPresented: $isShowingImagePicker, selectedImage: $imageInBlackBox)})
}
}
}
Group {
TextField("Enter the movie name", text: $newMovieName)
.padding()
.background(Color(.systemGray6))
}
Button(action: {
if self.newMovieName.trimmingCharacters(in: .whitespaces) == "" {
return
}
if self.isShowingImagePicker {
return
}
self.isShow = false
self.addMovieTask(movieName: self.newMovieName, movieImage: ImagePickerView(isPresented: $isShowingImagePicker, selectedImage: $imageInBlackBox))
}) {
Text("Save")
.font(.system(.headline, design: .rounded))
.foregroundColor(.red)
}
}
}
.background(Color.white)
}
}
private func addMovieTask(movieName: String, isComplete: Bool = false, movieImage: ImagePickerView) {
let task = MovieAdd(movieName: movieName, movieImage: movieImage)
addMovie.append(task)
}
}
struct NewMovieView_Previews: PreviewProvider {
static var previews: some View {
NewMovieView(isShow: .constant(true), addMovie: .constant([]), newMovieName: "", isShowingImagePicker: true)
}
}
struct ImagePickerView: UIViewControllerRepresentable {
#Binding var isPresented: Bool
#Binding var selectedImage: UIImage
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerView>) -> some UIViewController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
return controller
}
func makeCoordinator() -> ImagePickerView.Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePickerView
init(parent: ImagePickerView){
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
print(selectedImage)
self.parent.selectedImage = selectedImage
}
self.parent.isPresented = false
}
}
func updateUIViewController(_ uiViewController: ImagePickerView.UIViewControllerType, context: UIViewControllerRepresentableContext<ImagePickerView>) {
//
}
}
Change #1:
Your model should usually be a struct unless there's a really compelling reason to make it an ObservableObject. In this case, struct works very well:
struct MovieAdd: Identifiable {
var id = UUID()
var movieName = ""
var isComplete : Bool = false
var movieImage : UIImage
}
Note that I've made movieImage a UIImage.
Change #2:
Use Image(uiImage:) in MovieRow. The MovieAdd property no longer needs #ObservableObject since it's just a struct.
Also notice that types in Swift should be capitalized to follow convention).
struct MovieRow: View {
var movieAdd : MovieAdd
var body: some View {
VStack {
Image(uiImage: movieAdd.movieImage)
.resizable()
.frame(width: 100, height: 100)
Text(movieAdd.movieName)
}
}
}
Complete code in case I forgot to mention any other changes:
struct ContentView: View {
#State var movieAdd: [MovieAdd] = []
#State private var newMovieName: String = ""
#State private var showNewMovie = false
#State private var newMovieImage = UIImage()
var body: some View {
ZStack {
VStack {
HStack {
Text("Movies Watched Ratings")
.font(.system(size: 40, weight: .black, design: .rounded
))
Spacer()
Button(action: {
self.showNewMovie = true
}) {
Image(systemName: "plus.circle.fill")
.font(.largeTitle)
.foregroundColor(.yellow)
}
}
List{
ForEach(movieAdd) {movie in
MovieRow(movieAdd: movie)
}
}
}
if showNewMovie {
BlankView(bGColor: .black)
.opacity(0.5)
.onTapGesture {
self.showNewMovie = false
}
NewMovieView(isShow: $showNewMovie, addMovie: $movieAdd, newMovieName: newMovieName)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(stiffness: 200.0, damping: 25.0, initialVelocity: 10.0))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MovieRow: View {
var movieAdd : MovieAdd
var body: some View {
VStack {
Image(uiImage: movieAdd.movieImage)
.resizable()
.frame(width: 100, height: 100)
Text(movieAdd.movieName)
}
}
}
struct BlankView: View {
var bGColor: Color
var body: some View {
VStack {
Spacer()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(bGColor)
.edgesIgnoringSafeArea(.all)
}
}
struct MovieAdd: Identifiable {
var id = UUID()
var movieName = ""
var isComplete : Bool = false
var movieImage : UIImage
}
struct NewMovieView: View {
#Binding var isShow: Bool
#Binding var addMovie: [MovieAdd]
#State var newMovieName: String = ""
#State var isShowingImagePicker = false
#State var imageInBlackBox = UIImage()
var body: some View {
ScrollView {
VStack {
VStack (alignment: .leading) {
HStack {
Text("Add a New Movie")
.font(.system(.title, design: .rounded))
.bold()
}
ZStack {
VStack {
HStack (alignment: .center){
Spacer()
Image(uiImage: imageInBlackBox)
.resizable()
.scaledToFill()
.frame(width: 200, height: 200)
.border(Color.black, width: 3)
.clipped()
Spacer()
}
VStack {
Spacer()
Button(action: {
self.isShowingImagePicker.toggle()
}, label: {
Text("Select Image")
.font(.system(size: 15))
})
.sheet(isPresented: $isShowingImagePicker, content: { ImagePickerView(isPresented: $isShowingImagePicker, selectedImage: $imageInBlackBox)})
}
}
}
Group {
TextField("Enter the movie name", text: $newMovieName)
.padding()
.background(Color(.systemGray6))
}
Button(action: {
if self.newMovieName.trimmingCharacters(in: .whitespaces) == "" {
return
}
if self.isShowingImagePicker {
return
}
self.isShow = false
self.addMovieTask(movieName: self.newMovieName, movieImage: ImagePickerView(isPresented: $isShowingImagePicker, selectedImage: $imageInBlackBox))
}) {
Text("Save")
.font(.system(.headline, design: .rounded))
.foregroundColor(.red)
}
}
}
.background(Color.white)
}
}
private func addMovieTask(movieName: String, isComplete: Bool = false, movieImage: ImagePickerView) {
let task = MovieAdd(movieName: movieName, movieImage: movieImage.selectedImage)
addMovie.append(task)
}
}
struct NewMovieView_Previews: PreviewProvider {
static var previews: some View {
NewMovieView(isShow: .constant(true), addMovie: .constant([]), newMovieName: "", isShowingImagePicker: true)
}
}
struct ImagePickerView: UIViewControllerRepresentable {
#Binding var isPresented: Bool
#Binding var selectedImage: UIImage
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerView>) -> some UIViewController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
return controller
}
func makeCoordinator() -> ImagePickerView.Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePickerView
init(parent: ImagePickerView){
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage {
print(selectedImage)
self.parent.selectedImage = selectedImage
}
self.parent.isPresented = false
}
}
func updateUIViewController(_ uiViewController: ImagePickerView.UIViewControllerType, context: UIViewControllerRepresentableContext<ImagePickerView>) {
//
}
}
I want users to be able to upload two separate images to two different parts of the same view.
I'm able to get the first image to show correctly at the top. But whenever the user adds the second image, the image at the top is updated again instead of the image at the bottom.
Screenshot
Below is my code. Any help would be appreciated!
ImagePicker
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var image: UIImage?
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let uiImage = info[.originalImage] as? UIImage {
parent.image = uiImage
}
parent.presentationMode.wrappedValue.dismiss()
}
}
ContentView
import SwiftUI
struct ContentView: View {
#State private var firstImage: Image? = Image("PlaceholderImage")
#State private var secondImage: Image? = Image("PlaceholderImage")
#State private var inputImage1: UIImage?
#State private var inputImage2: UIImage?
#State private var showingImagePicker = false
var body: some View {
VStack{
Form {
Section(header: Text("First Image")){
firstImage!
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipShape(Rectangle())
.onTapGesture { self.showingImagePicker = true }
.sheet(isPresented: $showingImagePicker, onDismiss: loadImage1) {
ImagePicker(image: self.$inputImage1)
}
}
Section(header: Text("Second Image")){
secondImage!
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipShape(Rectangle())
.onTapGesture { self.showingImagePicker = true }
.sheet(isPresented: $showingImagePicker, onDismiss: loadImage2) {
ImagePicker(image: self.$inputImage2)
}
}
}
}
}
func loadImage1() {
guard let inputImage = inputImage1 else { return }
firstImage = Image(uiImage: inputImage)
}
func loadImage2() {
guard let inputImage = inputImage2 else { return }
secondImage = Image(uiImage: inputImage)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The root cause of the problem is using sheet(isPresented:) which can be problematic on iOS 14 because it loads the sheet content of the first render of it (in your case, the ImagePicker for the first image) and then doesn't update as one might expect (see SwiftUI #State and .sheet() ios13 vs ios14 for example).
The solution is to use sheet(item:). In your case, this also involved some other refactoring to get things to work as expected. Here's what I came up with:
struct ContentView: View {
#State private var inputImage1: UIImage?
#State private var inputImage2: UIImage?
#State private var activeImagePicker : ImagePickerIdentifier? = nil
enum ImagePickerIdentifier : String, Identifiable {
case picker1, picker2
var id : String {
return self.rawValue
}
}
var image1 : Image {
if let inputImage1 = inputImage1 {
return Image(uiImage: inputImage1)
} else {
return Image("Placeholder")
}
}
var image2 : Image {
if let inputImage2 = inputImage2 {
return Image(uiImage: inputImage2)
} else {
return Image("Placeholder")
}
}
var body: some View {
VStack{
Form {
Section(header: Text("First Image")){
image1
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipShape(Rectangle())
.onTapGesture { self.activeImagePicker = .picker1 }
}
Section(header: Text("Second Image")) {
image2
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipShape(Rectangle())
.onTapGesture { self.activeImagePicker = .picker2 }
}
}
.sheet(item: $activeImagePicker) { picker in
switch picker {
case .picker1:
ImagePicker(image: $inputImage1)
case .picker2:
ImagePicker(image: $inputImage2)
}
}
}
}
}
Note that I got rid of your onDismiss function, because it was unnecessary here, but if you did need it for your real implementation, I'd suggest using onDisappear on the individual ImagePickers, which will let you differentiate which one is being dismissed.
I'm new to SwiftUI and Combine. What I trying to build is a manual camera app, and there's only 4 UI component:
CaptureButton for making a shot from the camera
FocusPicker for controlling manually camera focus exposure
OffsetView for displaying a level of exposure
CameraPreviewRepresentable for integrating UIKit camera into SwiftUI view
Also added Privacy requests into.Info.plist file from a user to allow camera feature and saving to Apple Photo App
For updating data and passing it to the UI, I'm using CameraViewModel, currentCameraSubject and currentCamera Publisher to showing new values from AVCaptureDevice and setting it to CameraViewModel.
And I'm noticing a really interesting behavior/bug of FocusPicker when I start interacting with it and piking a new focus it constantly get back to started position and when OffsetView is getting a new value each time.
But interesting enough for example when OffsetView has the same value then FocusPicker is doing normal. And I do not know why this is happening. Please help, it's really frustrating to fix for me.
By the way, it will only work on a real device only.
Here's all the code:
import SwiftUI
//#main
//struct StackOverflowCamApp: App {
// var cameraViewModel = CameraViewModel(focusLensPosition: 0)
// let cameraController: CustomCameraController = CustomCameraController()
//
// var body: some Scene {
// WindowGroup {
// ContentView(cameraViewModel: cameraViewModel, cameraController: cameraController)
// }
// }
//}
struct ContentView: View {
#State private var didTapCapture = false
#ObservedObject var cameraViewModel: CameraViewModel
let cameraController: CustomCameraController
var body: some View {
VStack {
ZStack {
CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel, cameraController: cameraController)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
VStack {
FocusPicker(selectedFocus: $cameraViewModel.focusChoice)
Text(String(format: "%.2f", cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
Spacer()
OffsetView(levelValue: cameraViewModel.exposureTargetOffset, height: 100)
.frame(maxWidth: .infinity, alignment: .leading)
CaptureButton(didTapCapture: $didTapCapture)
.frame(width: 100, height: 100, alignment: .center)
.padding(.bottom, 20)
}
}
}
struct CaptureButton: View {
#Binding var didTapCapture : Bool
var body: some View {
Button {
didTapCapture.toggle()
} label: {
Image(systemName: "photo")
.font(.largeTitle)
.padding(30)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.red)
)
}
}
}
struct OffsetView: View {
var levelValue: Float
let height: CGFloat
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.red)
.frame(maxWidth: height / 2, maxHeight: height, alignment: .trailing)
Rectangle()
.foregroundColor(.orange)
.frame(maxWidth: height / 2, maxHeight: height / 20, alignment: .trailing)
.offset(x: 0, y: min(CGFloat(-levelValue) * height / 2, height / 2))
}
}
}
struct FocusPicker: View {
#Binding var selectedFocus: FocusChoice
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(0..<FocusChoice.allCases.count) {
Text("\(FocusChoice.allCases[$0].caption)")
.foregroundColor(.white)
.font(.subheadline)
.fontWeight(.medium)
.tag(FocusChoice.allCases[$0])
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}
import SwiftUI
import Combine
import AVFoundation
struct CameraPreviewRepresentable: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var didTapCapture: Bool
#ObservedObject var cameraViewModel: CameraViewModel
let cameraController: CustomCameraController
func makeUIViewController(context: Context) -> CustomCameraController {
cameraController.delegate = context.coordinator
return cameraController
}
func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
if didTapCapture {
cameraViewController.didTapRecord()
}
// checking if new value is differnt from the previous value
if cameraViewModel.focusChoice.rawValue != cameraViewController.manualFocusValue {
cameraViewController.manualFocusValue = cameraViewModel.focusChoice.rawValue
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self, cameraViewModel: cameraViewModel)
}
class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
let parent: CameraPreviewRepresentable
var cameraViewModel: CameraViewModel
var tokens = Set<AnyCancellable>()
init(_ parent: CameraPreviewRepresentable, cameraViewModel: CameraViewModel) {
self.parent = parent
self.cameraViewModel = cameraViewModel
super.init()
// for showing focus lens position
self.parent.cameraController.currentCamera
.filter { $0 != nil }
.flatMap { $0!.publisher(for: \.lensPosition) }
.assign(to: \.focusLensPosition, on: cameraViewModel)
.store(in: &tokens)
// for showing exposure offset
self.parent.cameraController.currentCamera
.filter { $0 != nil }
.flatMap { $0!.publisher(for: \.exposureTargetOffset) }
.assign(to: \.exposureTargetOffset, on: cameraViewModel)
.store(in: &tokens)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
parent.didTapCapture = false
if let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
import Combine
import AVFoundation
class CameraViewModel: ObservableObject {
#Published var focusLensPosition: Float = 0
#Published var exposureTargetOffset: Float = 0
#Published var focusChoice: FocusChoice = .infinity
private var tokens = Set<AnyCancellable>()
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
enum FocusChoice: Float, CaseIterable {
case infinity = 1
case ft_30 = 0.95
case ft_15 = 0.9
case ft_10 = 0.85
case ft_7 = 0.8
case ft_5 = 0.5
case ft_4 = 0.7
case ft_3_5 = 0.65
case ft_3 = 0.6
case auto = 0
}
extension FocusChoice {
var caption: String {
switch self {
case .infinity: return "∞ft"
case .ft_30: return "30"
case .ft_15: return "15"
case .ft_10: return "10"
case .ft_7: return "7"
case .ft_5: return "5"
case .ft_4: return "4"
case .ft_3_5: return "3.5"
case .ft_3: return "3"
case .auto: return "Auto"
}
}
}
import UIKit
import Combine
import AVFoundation
class CustomCameraController: UIViewController {
var image: UIImage?
var captureSession = AVCaptureSession()
var backCamera: AVCaptureDevice?
var frontCamera: AVCaptureDevice?
lazy var currentCamera: AnyPublisher<AVCaptureDevice?, Never> = currentCameraSubject.eraseToAnyPublisher()
var photoOutput: AVCapturePhotoOutput?
var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
private var currentCameraSubject = CurrentValueSubject<AVCaptureDevice?, Never>(nil)
var manualFocusValue: Float = 1 {
didSet {
guard manualFocusValue != 0 else {
setAutoLensPosition()
return
}
setFocusLensPosition(manualValue: manualFocusValue)
}
}
//DELEGATE
var delegate: AVCapturePhotoCaptureDelegate?
func setFocusLensPosition(manualValue: Float) {
do {
try currentCameraSubject.value!.lockForConfiguration()
currentCameraSubject.value!.focusMode = .locked
currentCameraSubject.value!.setFocusModeLocked(lensPosition: manualValue, completionHandler: nil)
currentCameraSubject.value!.unlockForConfiguration()
} catch let error {
print(error.localizedDescription)
}
}
func setAutoLensPosition() {
do {
try currentCameraSubject.value!.lockForConfiguration()
currentCameraSubject.value!.focusMode = .continuousAutoFocus
currentCameraSubject.value!.unlockForConfiguration()
} catch let error {
print(error.localizedDescription)
}
}
func didTapRecord() {
let settings = AVCapturePhotoSettings()
photoOutput?.capturePhoto(with: settings, delegate: delegate!)
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
setupCaptureSession()
setupDevice()
setupInputOutput()
setupPreviewLayer()
startRunningCaptureSession()
}
func setupCaptureSession() {
captureSession.sessionPreset = .photo
}
func setupDevice() {
let deviceDiscoverySession =
AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .unspecified)
for device in deviceDiscoverySession.devices {
switch device.position {
case .front:
self.frontCamera = device
case .back:
self.backCamera = device
default:
break
}
}
self.currentCameraSubject.send(self.backCamera)
}
func setupInputOutput() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: currentCameraSubject.value!)
captureSession.addInput(captureDeviceInput)
photoOutput = AVCapturePhotoOutput()
captureSession.addOutput(photoOutput!)
} catch {
print(error)
}
}
func setupPreviewLayer() {
self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
let deviceOrientation = UIDevice.current.orientation
cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation(rawValue: deviceOrientation.rawValue)!
self.cameraPreviewLayer?.frame = self.view.frame
self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
}
func startRunningCaptureSession() {
captureSession.startRunning()
}
}
Your ContentView gets updated all the time from the Published values. To Fix that we first remove the declaration as ObservedObject from the ViewModel inside the ContentView and declare it like that:
let cameraViewModel: CameraViewModel
Now we will get some errors. For FocusView just use ProxyBinding.
FocusPicker(selectedFocus: Binding<FocusChoice>(
get: {
cameraViewModel.focusChoice
},
set: {
cameraViewModel.focusChoice = $0
}
))
For the updated text, just create another View. Here! we use ObservedObject.
struct TextView: View {
#ObservedObject var cameraViewModel: CameraViewModel
var body: some View {
Text(String(format: "%.2f", cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
}
Same for the OffsetView. Add ObservedObject there
struct OffsetView: View {
#ObservedObject var viewModel : CameraViewModel
let height: CGFloat
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.red)
.frame(maxWidth: height / 2, maxHeight: height, alignment: .trailing)
Rectangle()
.foregroundColor(.orange)
.frame(maxWidth: height / 2, maxHeight: height / 20, alignment: .trailing)
.offset(x: 0, y: min(CGFloat(-viewModel.exposureTargetOffset) * height / 2, height / 2))
}
}
}
The ContentView will then look like that:
struct ContentView: View {
#State private var didTapCapture = false
let cameraViewModel: CameraViewModel
let cameraController: CustomCameraController
var body: some View {
VStack {
ZStack {
CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel, cameraController: cameraController)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
VStack {
FocusPicker(selectedFocus: Binding<FocusChoice>(
get: {
cameraViewModel.focusChoice
},
set: {
cameraViewModel.focusChoice = $0
}
))
TextView(cameraViewModel: cameraViewModel)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
Spacer()
OffsetView(viewModel: cameraViewModel, height: 100)
.frame(maxWidth: .infinity, alignment: .leading)
CaptureButton(didTapCapture: $didTapCapture)
.frame(width: 100, height: 100, alignment: .center)
.padding(.bottom, 20)
}
}
}
Hence, we do not have any ObservedObject anymore in the ContentView and our Picker just works fine.
Attributed View is not updating layout correctly for attributed string. Below is my code.
I have created Attributed Label for SwiftUI.
import SwiftUI
class ViewLabel : UIView {
private var label = UILabel()
override init(frame: CGRect) {
super.init(frame:frame)
self.addSubview(label)
label.numberOfLines = 0
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.lineBreakMode = .byWordWrapping
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setString(_ attributedString:NSAttributedString) {
self.label.attributedText = attributedString
}
}
struct AHAttributedTextLabel: UIViewRepresentable {
var attributedString:NSAttributedString
func makeUIView(context: Context) -> ViewLabel {
let view = ViewLabel(frame:CGRect.zero)
return view
}
func updateUIView(_ uiView: ViewLabel, context: UIViewRepresentableContext<AHAttributedTextLabel>) {
uiView.setString(attributedString)
}
}
struct AHAttributedLabel_Previews: PreviewProvider {
static var previews: some View {
AHAttributedTextLabel(attributedString: NSAttributedString(string: "Test"))
}
}
CellView for List which takes 2 parameters title and subtitle
struct AHAttributtedTitleSubtitleView: View {
var title:NSAttributedString
var subTitle:String
var body: some View {
HStack{
VStack(alignment:.leading){
AHAttributedTextLabel(attributedString: self.title)
.font(Font.system(size: 14, weight: .medium, design: .default))
.padding(.top, 10.0)
.padding(.leading, 10.0)
.padding(.trailing, 10.0)
.padding(.bottom, 5.0)
Text(self.subTitle)
.font(Font.system(size: 14, weight: .light, design: .default))
.padding(.leading, 10.0)
.padding(.trailing, 10.0)
.padding(.bottom, 10.0)
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.overlay(AHDashBorderView())
}
}
Content View which displays List
import SwiftUI
struct AHVitalGraphContentView: View {
#ObservedObject var viewModel: AHVitalsGraphViewModel
var body: some View {
VStack{
List(self.viewModel.vitalGroup.vitals,id:\.vitalId) { vital in
AHAttributtedTitleSubtitleView(title: vital.attributedString, subTitle: vital.recorderDetails)
}
}.navigationBarTitle(Text(self.viewModel.vitalGroup.latestReadings.vitalName ?? ""), displayMode: .inline)
}
}
below is attached screenshot of issues. u can see .... at top of list cell.