SwiftUI: Update Navigation Bar Color - swiftui

Goal: Update Navigation Bar color on press of a button.
Current: NavBar Color doesn't change at all.
Attempted: I am currently using an init() to set the navBarTintColor & I have also tried this answer but neither will update on demand.
Code:
var navColor = Color.green
struct ContentView: View {
#State var themeColor = Color.green
#State var items = [0,1,2,3,4,5,6,7,8,9]
#State var showSheet = false
init() {
UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.black]
UINavigationBar.appearance().barTintColor = UIColor(navColor)
UINavigationBar.appearance().backgroundColor = UIColor(navColor)
}
var body: some View {
NavigationView{
List{
ForEach(0..<items.count){ i in
Text("Item: \(items[i])")
}.listRowBackground(themeColor)
}
.navigationBarTitle("Theme", displayMode: .inline)
.navigationBarItems(leading: Button(action: {
showSheet.toggle()
}, label: {
Text("Sheet")
})
,trailing: Button(action: {
self.themeColor = Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1))
navColor = Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1))
items.shuffle()
}, label: {
Text("Random Color")
}))
// .background(NavigationConfigurator { nc in
// nc.navigationBar.barTintColor = UIColor(navColor)
// nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.black]
// })
.sheet(isPresented: $showSheet, content: {
popSheet()
})
}
}
}
struct popSheet: View {
var body: some View{
NavigationView{
Text("Hello")
.navigationBarTitle("Sheet", displayMode: .inline)
}
}
}
Here is the navigation configurator I also attempted to use.
struct NavigationConfigurator: UIViewControllerRepresentable {
var configure: (UINavigationController) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
if let nc = uiViewController.navigationController {
self.configure(nc)
}
}
}

You can force refresh the NavigationView using .id(themeColor).
Here is a possible solution:
struct ContentView: View {
#State var themeColor = Color.green
init() {
updateNavigationBarColor()
}
var body: some View {
NavigationView {
VStack {
Button("Change to blue") {
themeColor = .blue
updateNavigationBarColor()
}
Button("Change to red") {
themeColor = .red
updateNavigationBarColor()
}
}
.navigationTitle("Title")
}
.id(themeColor)
}
func updateNavigationBarColor() {
UINavigationBar.appearance().barTintColor = UIColor(themeColor)
UINavigationBar.appearance().backgroundColor = UIColor(themeColor)
}
}

Related

Admob BannerView in SwiftUI

I am attempting to wrap the GADBannerView with a UIViewControllerRepresentable so that I can use it in my SwiftUI views but I encountered a situation where the background layer of the viewController covers the main list view. This resulted in a situation where the list is not scrollable.
I would like the bannerView to be anchored to the bottom of the view, but just above the tabView.
Code:
struct LaunchView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label {
Text("Home")
} icon: {
Image(systemName: "gear")
}
}
}
}
}
struct HomeView: View {
var body: some View {
NavigationView {
ZStack {
List {
ForEach(0..<20, id: \.self) { _ in
Text("Hello, World!")
}
}
VStack {
Spacer()
JRBannerView()
}
}
.navigationTitle(Text("Home"))
}
}
}
class JRBannerViewController: UIViewController {
lazy var bannerView: GADBannerView = {
let v = GADBannerView()
v.translatesAutoresizingMaskIntoConstraints = false
v.adUnitID = TEST_AD_ID
v.rootViewController = self
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
loadBannerAd()
}
func setupViews() {
view.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.5) //<== obscures the main list view behind
view.addSubview(bannerView)
bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
func loadBannerAd() {
let viewWidth = view.frame.inset(by: view.safeAreaInsets).size.width
bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
bannerView.load(GADRequest())
}
}
struct JRBannerView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> JRBannerViewController {
return JRBannerViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
Results:
How do I compact the background view of the viewController to the same height of the bannerView so that the list is scrollable?

Get PDF-Scanner in fullscreen in a sheet in SwiftUI

I am working on a PDF-Scanner and want to realize it with SwiftUI. I know that I have to work with UIViewRepresentable to get this done.
So the user should open the Camera (to scan the document) in a Sheetview, but the camera doesn't open. If I put it in a Navigationview, it works fine.
My questions are:
Is it possible to open the camera in Sheetviews (fullscreen). Or is it possible put the CameraView in a limited frame in the SheetView to get this done.
Thanks a lot
import SwiftUI
import VisionKit
import PDFKit
import UIKit
#main
struct testetApp: App {
var body: some Scene {
WindowGroup {
ContentView(scannerModel: ScannerModel())
}
}
}
struct ContentView: View {
#State var showNextView = false
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
var body: some View {
NavigationView {
VStack {
Button {
showNextView.toggle()
} label: {
Text("via Sheet")
}.sheet(isPresented: $showNextView) {
ContentView2(scannerModel: scannerModel, showNextView: $showNextView).environment(\.managedObjectContext, vm.mangager.container.viewContext)
}.padding()
NavigationLink("via Navigationlink", destination: ContentView3(scannerModel: scannerModel))
}
.padding()
}
}
}
struct ContentView2: View {
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
#State var files : [String] = []
#State var PDFview = false
#State var addDoc = true
#Binding var showNextView: Bool
var body: some View {
ZStack{
NavigationView{
VStack{
VStack {
NavigationLink(destination: ScanView(files: $files, scannerModel: scannerModel, vm: vm)){
VStack{
Image(systemName: "plus").font(.largeTitle).padding(.bottom)
Text("Scan Document").font(.caption)
}
}
}
}
}
}
}
}
struct ContentView3: View {
#StateObject var vm = CoreDataRelationshipViewModel()
#ObservedObject var scannerModel: ScannerModel
#State var files : [String] = []
#State var PDFview = false
#State var addDoc = true
var body: some View {
ZStack{
VStack{
VStack {
NavigationLink(destination: ScanView(files: $files, scannerModel: scannerModel, vm: vm)){
VStack{
Image(systemName: "plus").font(.largeTitle).padding(.bottom)
Text("Scan Document").font(.caption)
}
}
}
}
}
}
}
struct ScanView: View{
#Environment(\.presentationMode) var mode
#State var pdfName = ""
#State var addDoc = true
#Binding var files : [String]
#ObservedObject var scannerModel: ScannerModel
#ObservedObject var vm: CoreDataRelationshipViewModel
var body: some View{
ZStack{
VStack{
if let error = scannerModel.errorMessage {
Text(error)
} else {
ForEach(scannerModel.imageArray, id: \.self) { image in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit).contextMenu {
Button {
let items = [image]
let ac = UIActivityViewController(activityItems: items, applicationActivities: nil)
UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController?.present(ac, animated: true)
} label: {
Label("share Document", systemImage: "square.and.arrow.up")
}
Divider()
Button {
scannerModel.removeImage(image: image)
} label: {
Label("delete document", systemImage: "delete.left")
}
}
}
}
}.navigationBarItems( trailing: Button(action:{
vm.createSavedPDF(a: scannerModel.imageArray)
guard pdfName.count > 0 else{
return
}
self.mode.wrappedValue.dismiss()
saveDocument(a: scannerModel.imageArray, pdfName: pdfName)
scannerModel.imageArray.removeAll()
files = getDocumentsDirectory()
}){
Text("Save")
})
if(addDoc){
VStack{
VStack{
Button(action: {
UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController?.present(scannerModel.getDocumentCameraViewController(), animated: true, completion: nil)
addDoc = false
}){
VStack {
Image(systemName: "camera").font(.title)
Text("Scan Doc").font(.title)
}
}
}.padding()
}
}
}
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.pageBreakMargins = UIEdgeInsets(top: 50, left: 30, bottom: 50, right:30)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
}
}
struct ActivityViewController: UIViewControllerRepresentable {
var activityItems: [Any]
var applicationActivities: [UIActivity]? = nil
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityViewController>) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityViewController>) {}
}

View not refreshing when Core Data are updated via another function

EDIT: added more context
I have a List in the ContentView which shows a list of Core Data items. In the list I call the function that should show if one of those items has a "check" that day ( imagine a checklist app ).
The statusView works ok when it loads but it does not refresh when , from a button on the ContentView I create an entry that should effect the fetchRequest.
What I am doing wrong?
This is the statusView:
struct statusView: View {
#FetchRequest var fetchRequest: FetchedResults<Check>
let dateFormatter = DateFormatter()
var body: some View {
if(fetchRequest.count > 0 ) {
Image(systemName: "checkmark")
.imageScale(.large)
.font(.title2)
.foregroundColor(Color(.sRGB, red: 91/255, green: 200/255, blue: 175/255))
} else {
Image(systemName: "exclamationmark.circle")
.imageScale(.large)
.font(.title2)
.foregroundColor(Color.yellow)
}
}
init(filter: String) {
let p1 = NSPredicate(format: "habit_id == %#", filter)
let p2 = NSPredicate(format: "date >= %# && date <= %#", Calendar.current.startOfDay(for: Date()) as CVarArg, Calendar.current.startOfDay(for: Date() + 86400) as CVarArg)
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [p1, p2])
_fetchRequest = FetchRequest<Check>(sortDescriptors: [], predicate: predicate)
dateFormatter.dateFormat = "dd/MM/YY"
}
}
and this is the ContentView that is calling the statusView
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(sortDescriptors: []) var habits: FetchedResults<Habit>
#FetchRequest(sortDescriptors: []) var checks: FetchedResults<Check>
#FetchRequest(sortDescriptors: []) var todayChecks: FetchedResults<Check>
#State private var thisID: String = ""
#State private var selection: String? = nil
#State private var title = ""
#State private var forceRefresh = ""
var color_purple: Color = Color(.sRGB, red: 161/255, green: 78/255, blue: 191/255)
func createCheckToday(habit_id: UUID) {
let new_check = Check(context: moc)
new_check.id = UUID()
new_check.date = Date()
new_check.habit_id = habit_id
try? moc.save()
}
func deleteHabit(obj: NSManagedObject) {
moc.delete(obj)
try! moc.save()
}
func _riga(title: String, subtitle: String, id: UUID, obj: NSManagedObject) -> some View {
print(id.uuidString)
var body: some View {
Button {
createCheckToday(habit_id: id)
} label: {
HStack {
//statusView(filter: id.uuidString)
VStack(alignment: .leading) {
Text(title)
.font(.headline)
Text(subtitle)
.font(.subheadline)
}
}
}
}
return body
}
func delete(at offsets: IndexSet) {
// delete the objects here
}
var body: some View {
NavigationView {
VStack {
Text("")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
Image(systemName: "note")
.imageScale(.large)
.font(.title2)
.foregroundColor(Color(.sRGB, red: 161/255, green: 78/255, blue: 191/255))
Text("Habit-o")
.font(Font.system(.largeTitle, design: .rounded).weight(.heavy))
.foregroundColor(Color(.sRGB, red: 161/255, green: 78/255, blue: 191/255))
.padding(10)
}
}
}
//---
List(habits) { habit in
HStack {
statusView(filter: habit.id!.uuidString)
_riga(title: habit.title!, subtitle: habit.subtitle!, id: habit.id!, obj: habit)
}
}
.listStyle(.plain)
Spacer()
//---
NavigationLink(destination: newForm(), tag: "A", selection: $selection) { EmptyView() }
Button(action: {
selection="A"
}) {
Text("New Habit")
.font(.title)
.fontWeight(.heavy)
.padding()
.background(color_purple)
.cornerRadius(40)
.foregroundColor(Color.white)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.purple, lineWidth: 5)
)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
//---->
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI: Interaction between Views

I have the following problem and i'm completely new to the complete swift/swiftui-"thing".
I have a ContentView containing a MapView and MapViewControls - when pressing the buttons in MapViewControls the MapView should change to show a specific annotation centered on my map (2nd button is for centering on User Location).
How can i accomplish?
ContentView:
import SwiftUI
import MapKit
struct ContentView: View
{
#State private var expandedInfoView: Bool = false
var body: some View
{
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom))
{
GeometryReader
{
proxy in
VStack(alignment: .center)
{
MapView()
VStack
{
HStack
{
Spacer()
MapViewControls()
.padding(.top, -700)
}
}
LogoView()
.onTapGesture
{
withAnimation
{
expandedInfoView.toggle()
}
}
.padding(.top, -110)
}
.sheet(isPresented: $expandedInfoView)
{
InfoView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider
{
static var previews: some View
{
ContentView()
}
}
MapView:
import SwiftUI
import MapKit
import CoreLocation
struct MapView: UIViewRepresentable
{
let fszCoordinates = CLLocation(latitude: XXX, longitude: YYY)
var locationManager = CLLocationManager()
func setupManager()
{
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
func makeUIView(context: Context) -> MKMapView
{
setupManager()
let fszAnnotation = MKPointAnnotation()
fszAnnotation.coordinate = CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude)
fszAnnotation.title = "TITLE"
fszAnnotation.subtitle = "SUBTITLE"
let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.showsUserLocation = true
mapView.userTrackingMode = .followWithHeading
mapView.showsScale = true
mapView.showsTraffic = true
mapView.showsCompass = true
mapView.showAnnotations([fszAnnotation], animated: true)
let buttonLocateUser = MKUserTrackingButton(mapView: mapView)
buttonLocateUser.layer.backgroundColor = UIColor(white: 1, alpha: 0.8).cgColor
buttonLocateUser.layer.borderColor = UIColor.white.cgColor
buttonLocateUser.layer.borderWidth = 1
buttonLocateUser.layer.cornerRadius = 5
buttonLocateUser.translatesAutoresizingMaskIntoConstraints = false
mapView.addSubview(buttonLocateUser)
let scale = MKScaleView(mapView: mapView)
scale.legendAlignment = .trailing
scale.translatesAutoresizingMaskIntoConstraints = false
mapView.addSubview(scale)
NSLayoutConstraint.activate([buttonLocateUser.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25),
buttonLocateUser.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -10),
scale.trailingAnchor.constraint(equalTo: buttonLocateUser.leadingAnchor, constant: -10),
scale.centerYAnchor.constraint(equalTo: buttonLocateUser.centerYAnchor)])
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context)
{
}
}
struct MapView_Previews: PreviewProvider
{
static var previews: some View
{
MapView()
}
}
MapViewControls:
import SwiftUI
struct MapViewControls: View
{
var body: some View
{
VStack(spacing: 6)
{
VStack(spacing: 12)
{
Button
{
buttonActionZoomToFSZIT()
}
label:
{
Image(systemName: "house.circle")
}
Divider()
Button
{
buttonActionZoomToUser()
}
label:
{
Image(systemName: "location.circle")
}
}
.frame(width: 40)
.padding(.vertical, 12)
.background(Color(UIColor.secondarySystemGroupedBackground))
.cornerRadius(8)
}
.font(.system(size: 20))
.foregroundColor(.blue)
.padding()
.shadow(color: Color(UIColor.black.withAlphaComponent(0.1)), radius: 4)
}
func buttonActionZoomToFSZIT()
{
print("button tapped")
}
func buttonActionZoomToUser()
{
print("button tapped")
}
}
struct MapViewControls_Previews: PreviewProvider
{
static var previews: some View
{
MapViewControls()
}
}
Nevermind, i solved it by myself after #vadian's comment (thank you mate).
ContentView:
import SwiftUI
import MapKit
struct ContentView: View
{
#State private var expandedInfoView: Bool = false
#State private var buttonDecision: Int = 0
var body: some View
{
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom))
{
GeometryReader
{
proxy in
VStack(alignment: .center)
{
MapView(mapCenter: $buttonDecision)
VStack
{
HStack
{
Spacer()
MapViewControls(mapCenter: $buttonDecision)
.padding(.top, -700)
}
}
LogoView()
.onTapGesture
{
withAnimation
{
expandedInfoView.toggle()
}
}
.padding(.top, -150)
}
.sheet(isPresented: $expandedInfoView)
{
InfoView()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider
{
static var previews: some View
{
ContentView()
}
}
MapView:
import SwiftUI
import MapKit
import CoreLocation
struct MapView: UIViewRepresentable
{
#Binding var mapCenter: Int
let fszCoordinates = CLLocation(latitude: XXX, longitude: YYY)
var locationManager = CLLocationManager()
func setupManager()
{
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
func makeUIView(context: Context) -> MKMapView
{
setupManager()
let fszAnnotation = MKPointAnnotation()
fszAnnotation.coordinate = CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude)
fszAnnotation.title = "TITLE"
fszAnnotation.subtitle = "SUBTITLE"
let mapView = MKMapView(frame: UIScreen.main.bounds)
mapView.userTrackingMode = .follow
mapView.showsScale = true
mapView.showsCompass = true
mapView.setCenter(CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude), animated: true)
mapView.showAnnotations([fszAnnotation], animated: false)
let buttonLocateUser = MKUserTrackingButton(mapView: mapView)
buttonLocateUser.layer.backgroundColor = UIColor(white: 1, alpha: 0.8).cgColor
buttonLocateUser.layer.borderColor = UIColor.white.cgColor
buttonLocateUser.layer.borderWidth = 1
buttonLocateUser.layer.cornerRadius = 5
buttonLocateUser.translatesAutoresizingMaskIntoConstraints = false
mapView.addSubview(buttonLocateUser)
let scale = MKScaleView(mapView: mapView)
scale.legendAlignment = .trailing
scale.translatesAutoresizingMaskIntoConstraints = false
mapView.addSubview(scale)
NSLayoutConstraint.activate([buttonLocateUser.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25),
buttonLocateUser.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -10),
scale.trailingAnchor.constraint(equalTo: buttonLocateUser.leadingAnchor, constant: -10),
scale.centerYAnchor.constraint(equalTo: buttonLocateUser.centerYAnchor)])
return mapView
}
func updateUIView(_ uiView: MKMapView, context: Context)
{
if ($mapCenter.wrappedValue == 0)
{
uiView.setCenter(CLLocationCoordinate2D(latitude: fszCoordinates.coordinate.latitude, longitude: fszCoordinates.coordinate.longitude), animated: true)
}
else if ($mapCenter.wrappedValue == 1)
{
uiView.setCenter(CLLocationCoordinate2D(latitude: uiView.userLocation.coordinate.latitude, longitude: uiView.userLocation.coordinate.longitude), animated: true)
}
}
}
struct MapView_Previews: PreviewProvider
{
#State static var fszCoordinates = 0
static var previews: some View
{
MapView(mapCenter: $fszCoordinates)
}
}
MapViewControls:
import SwiftUI
struct MapViewControls: View
{
#Binding var mapCenter: Int
var body: some View
{
VStack(spacing: 6)
{
VStack(spacing: 12)
{
Button
{
buttonActionZoomToFSZIT()
}
label:
{
Image(systemName: "house.circle")
}
Divider()
Button
{
buttonActionZoomToUser()
}
label:
{
Image(systemName: "location.circle")
}
}
.frame(width: 40)
.padding(.vertical, 12)
.background(Color(UIColor.secondarySystemGroupedBackground))
.cornerRadius(8)
}
.font(.system(size: 20))
.foregroundColor(.blue)
.padding()
.shadow(color: Color(UIColor.black.withAlphaComponent(0.1)), radius: 4)
}
func buttonActionZoomToFSZIT()
{
self.mapCenter = 0
}
func buttonActionZoomToUser()
{
self.mapCenter = 1
}
}
struct MapViewControls_Previews: PreviewProvider
{
#State static var fszCoordinates = 0
static var previews: some View
{
MapViewControls(mapCenter: $fszCoordinates)
}
}

SwiftUI doesn't update state to #ObservedObject cameraViewModel object

I'm new to SwiftUI and manual camera functionality, and I really need help.
So I trying to build a SwiftUI camera view that has a UIKit camera as a wrapper to control the focus lens position via SwiftUI picker view, display below a fucus value, and want to try have a correlation between AVcaptureDevice.lensPosition from 0 to 1.0 and feats that are displayed in the focus picker view. But for now, I only want to display that fucus number on screen.
And the problem is when I try to update focus via coordinator focus observation and set it to the camera view model then nothing happened. Please help 🙌
Here's the code:
import SwiftUI
import AVFoundation
import Combine
struct ContentView: View {
#State private var didTapCapture = false
#State private var focusLensPosition: Float = 0
#ObservedObject var cameraViewModel = CameraViewModel(focusLensPosition: 0)
var body: some View {
VStack {
ZStack {
CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
VStack {
FocusPicker(selectedFocus: $focusLensPosition)
Text(String(cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
Spacer()
CaptureButton(didTapCapture: $didTapCapture)
.frame(width: 100, height: 100, alignment: .center)
.padding(.bottom, 20)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
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 CameraPreviewRepresentable: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var didTapCapture: Bool
#ObservedObject var cameraViewModel: CameraViewModel
let cameraController: CustomCameraController = CustomCameraController()
func makeUIViewController(context: Context) -> CustomCameraController {
cameraController.delegate = context.coordinator
return cameraController
}
func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
if (self.didTapCapture) {
cameraViewController.didTapRecord()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self, cameraViewModel: cameraViewModel)
}
class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
let parent: CameraPreviewRepresentable
var cameraViewModel: CameraViewModel
var focusLensPositionObserver: NSKeyValueObservation?
init(_ parent: CameraPreviewRepresentable, cameraViewModel: CameraViewModel) {
self.parent = parent
self.cameraViewModel = cameraViewModel
super.init()
focusLensPositionObserver = self.parent.cameraController.currentCamera?.observe(\.lensPosition, options: [.new]) { [weak self] camera, _ in
print(Float(camera.lensPosition))
//announcing changes via Publisher
self?.cameraViewModel.focusLensPosition = camera.lensPosition
}
}
deinit {
focusLensPositionObserver = nil
}
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()
}
}
}
class CameraViewModel: ObservableObject {
#Published var focusLensPosition: Float = 0
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
class CustomCameraController: UIViewController {
var image: UIImage?
var captureSession = AVCaptureSession()
var backCamera: AVCaptureDevice?
var frontCamera: AVCaptureDevice?
var currentCamera: AVCaptureDevice?
var photoOutput: AVCapturePhotoOutput?
var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
//DELEGATE
var delegate: AVCapturePhotoCaptureDelegate?
func showFocusLensPosition() -> Float {
// guard let camera = currentCamera else { return 0 }
// try! currentCamera!.lockForConfiguration()
// currentCamera!.focusMode = .autoFocus
//// currentCamera!.setFocusModeLocked(lensPosition: currentCamera!.lensPosition, completionHandler: nil)
// currentCamera!.unlockForConfiguration()
return currentCamera!.lensPosition
}
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.currentCamera = self.backCamera
}
func setupInputOutput() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: currentCamera!)
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
// view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
}
func startRunningCaptureSession() {
captureSession.startRunning()
}
}
struct FocusPicker: View {
var feets = ["∞ ft", "30", "15", "10", "7", "5", "4", "3.5", "3", "2.5", "2", "1.5", "1", "0.5", "Auto"]
#Binding var selectedFocus: Float
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(0 ..< feets.count) {
Text(feets[$0])
.foregroundColor(.white)
.font(.subheadline)
.fontWeight(.medium)
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}
The problem with your provided code is that the type of selectedFocus within the FocusPicker view should be Integer rather than Float. So one option is to change this type to Integer and find a way to express the AVCaptureDevice.lensPosition as an Integer with the given range.
The second option is to replace the feets array with an enumeration. By making the enumeration conform to the CustomStringConvertible protocol, you can even provide a proper description. Please see my example below.
I've stripped your code a bit as you just wanted to display the number in the first step and thus the code is more comprehensible.
My working example:
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var cameraViewModel = CameraViewModel(focusLensPosition: 0.5)
var body: some View {
VStack {
ZStack {
VStack {
FocusPicker(selectedFocus: $cameraViewModel.focusLensPosition)
Text(String(self.cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class CameraViewModel: ObservableObject {
#Published var focusLensPosition: Float
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
enum Feets: Float, CustomStringConvertible, CaseIterable, Identifiable {
case case1 = 0.0
case case2 = 0.5
case case3 = 1.0
var id: Float { self.rawValue }
var description: String {
get {
switch self {
case .case1:
return "∞ ft"
case .case2:
return "4"
case .case3:
return "Auto"
}
}
}
}
struct FocusPicker: View {
#Binding var selectedFocus: Float
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(Feets.allCases) { feet in
Text(feet.description)
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}