I have a tabview (page style) that I am using to create an automatic slideshow. Each slide is not assigned to the currentIndex for some odd reason. Neither does the timer work or manually switching tabs with the default dot controls given with tabview.
If i set the tag to selectedIndex the tabview will default to the first slide.
No errors to the console.
Any help is greatly appreciated!
struct HomeView: View {
#EnvironmentObject private var hvm: HomeViewModel
#State private var selectedFilter: String = "Popular"
#State private var selectedMedia: MediaModelResult? = nil
#State private var showDetailView: Bool = false
#State private var currentIndex: Int = 0
#State var isFilterSelected: Bool = true
#Namespace var animation
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View {
NavigationView {
ZStack {
Color.theme.background.ignoresSafeArea()
VStack {
header
VStack(alignment: .leading, spacing: 15) {
Text("Upcoming Movies")
.foregroundColor(Color.theme.secondaryAccent)
.font(.subheadline)
.padding(.leading, 15)
TabView(selection: $currentIndex) {
ForEach(hvm.upcomingFilms) { film in
MediaImageView(mediaPath: film, poster: false)
.scaledToFill()
.tag(film)
}
}
.tabViewStyle(PageTabViewStyle())
.clipShape(RoundedRectangle(cornerRadius: 5))
.padding(.horizontal, 15)
.frame(width: UIScreen.main.bounds.width, height: 180)
.onReceive(timer, perform: { _ in
withAnimation(.default) {
currentIndex = currentIndex < hvm.upcomingFilms.count ? currentIndex + 1 : 0
}
})
}
scrollViewContent
}
}
.preferredColorScheme(.dark)
.navigationBarHidden(true)
}
}
}
struct MediaImageView: View {
#StateObject var mivm: MediaImageViewModel
var poster: Bool
init(mediaPath: MediaModelResult, poster: Bool) {
self.poster = poster
_mivm = StateObject(wrappedValue: MediaImageViewModel(mediaPath: mediaPath))
}
var body: some View {
if poster {
ZStack {
if let image = mivm.poster {
Image(uiImage: image)
.resizable()
.scaledToFit()
} else if mivm.isLoading {
ProgressView()
}
}
} else {
ZStack {
if let image = mivm.backdrop {
Image(uiImage: image)
.resizable()
} else if mivm.isLoading {
ProgressView()
}
}
}
}
}
class HomeViewModel: ObservableObject {
#Published var tabBarImageNames = ["house", "rectangle.stack", "clock.arrow.circlepath", "magnifyingglass"]
#Published var filterTitles = ["Popular Now", "Top Rated", "New"]
#Published var popularFilms: [MediaModelResult] = []
#Published var topRatedFilms: [MediaModelResult] = []
#Published var upcomingFilms: [MediaModelResult] = []
#Published var popularTV: [MediaModelResult] = []
#Published var topRatedTV: [MediaModelResult] = []
private let dataService = MediaDataService()
private var cancellables = Set<AnyCancellable>()
init() {
addSubscribers()
}
func addSubscribers() {
dataService.$popularFilms
.sink { [weak self] (returnedFilms) in
self?.popularFilms = returnedFilms
}
.store(in: &cancellables)
dataService.$topRatedFilms
.sink { [weak self] (returnedFilms) in
self?.topRatedFilms = returnedFilms
}
.store(in: &cancellables)
dataService.$upcomingFilms
.sink { [weak self] (returnedFilms) in
self?.upcomingFilms = returnedFilms
}
.store(in: &cancellables)
dataService.$popularTV
.sink { [weak self] (returnedFilms) in
self?.popularTV = returnedFilms
}
.store(in: &cancellables)
dataService.$topRatedTV
.sink { [weak self] (returnedFilms) in
self?.topRatedTV = returnedFilms
}
.store(in: &cancellables)
}
}
Solution:
TabView(selection: $currentIndex) {
ForEach(0..<hvm.upcomingFilms.count) { film in
MediaImageView(mediaPath: hvm.upcomingFilms[film], poster: false)
.scaledToFill()
}
}
Related
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>) {}
}
well so the problem: when i open the EditViewScreen i need to get the title(-text) and body(-text) of that post. But I always get the title and body of the first cell.
then I didn’t understand the meaning of this post either: 2022-06-03 06:52:57.858609+0500 SwiftuiMVVM[4334:105229] [Presentation] Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x127e734d0> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x127e0ac70> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentGS1_VVS_22_VariadicView_Children7ElementVS_24NavigationColumnModifier_GVS_18StyleContextWriterVS_19SidebarStyleContext__: 0x127e12bc0>) which is already presenting <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x12a604820>.
HomeViewScreen()
ZStack {
List {
ForEach(viewModel.posts, id: \.self) { post in
PostCell(post: post).onLongPressGesture {
showingEdit.toggle()
}.sheet(isPresented: $showingEdit) {
EditViewScreen(post: post)
}
}
.onDelete(perform: delete)
}.listStyle(.plain)
if viewModel.isLoading {
ProgressView()
}
}
EditViewScreen()
import SwiftUI
struct EditViewScreen: View {
#ObservedObject var viewModel = EditViewModel()
#Environment(\.presentationMode) var presentation
var post: Post
#State var p_title = ""
#State var p_body = ""
func updatePost() {
let post = Post(id: post.id!, title: p_title, body: p_body)
viewModel.apiPostUpdate(post: post, handler: { isUpdated in
if isUpdated {
presentation.wrappedValue.dismiss()
}
})
}
var body: some View {
NavigationView {
ZStack {
VStack(spacing: 10) {
TextField("Edit title", text: $p_title)
.frame(height: 60)
.padding(.leading)
.background(.gray.opacity(0.1))
.cornerRadius(10)
.font(.system(size: 18))
TextField("Edit body", text: $p_body)
.frame(height: 60)
.padding(.leading)
.background(.gray.opacity(0.1))
.cornerRadius(10)
.font(.system(size: 18))
Button(action: {
updatePost()
}, label: {
Spacer()
Text("Update")
Spacer()
})
.padding()
.frame(height: 60)
.background(.black.opacity(0.7))
.foregroundColor(.white)
.cornerRadius(10)
.font(.system(size: 18))
Spacer()
}
.padding(.leading)
.padding(.trailing)
.padding(.top, -35)
if viewModel.isLoading {
ProgressView()
}
}
.navigationBarItems(leading: Button(action: {
presentation.wrappedValue.dismiss()
}, label: {
Text("Cancel")
.foregroundColor(.black)
.font(.system(size: 18, weight: .light))
}))
}.onAppear {
p_title = post.title!
p_body = post.body!
}
}
}
Well, some shortcomings have passed. I've made changes to the HomeViewModel().
class HomeViewModel: ObservableObject {
#Published var isLoading = false
#Published var posts = [Post]()
#Published var post = Post() // <-- here
func apiPostList() {
isLoading = true
AFHttp.get(url: AFHttp.API_POST_LIST, params: AFHttp.paramsEmpty(), handler: { response in
self.isLoading = false
switch response.result {
case .success:
let posts = try! JSONDecoder().decode([Post].self, from: response.data!)
self.posts = posts
case let .failure(error):
print(error)
}
})
}
}
HomeViewScreen()
struct HomeViewScreen: View {
#ObservedObject var viewModel = HomeViewModel()
#State private var showingEdit = false
var body: some View {
NavigationView {
ZStack {
List {
ForEach(viewModel.posts, id: \.self) { post in
PostCell(post: post)
.onLongPressGesture {
showingEdit.toggle()
viewModel.post = post // <-- here
}.sheet(isPresented: $showingEdit) {
EditViewScreen(post: viewModel.post)
// 'll be viewmodel.post, not just the post itself
}
}
.onDelete(perform: delete)
}
.listStyle(.plain)
if viewModel.isLoading {
ProgressView()
}
}
Thank you for your attention(awa #workingdog)
try this approach using #StateObject var viewModel in your HomeViewScreen,
and passing that to EditViewScreen. In addition use .sheet(item: ...)
outside the ForEach loop, as in the example code. Importantly never
use forced unwrapping, that is, do not use ! in your code at all, use if let ... or guard ....
struct HomeViewScreen: View {
#StateObject var viewModel = EditViewModel() // <-- here
#State var selectedPost: Post? // <-- here
var body: some View {
ZStack {
List {
ForEach(viewModel.posts, id: \.self) { post in
PostCell(post: post).onLongPressGesture {
selectedPost = post // <-- here
}
}
.onDelete(perform: delete)
}.listStyle(.plain)
// -- here
.sheet(item: $selectedPost) { post in
EditViewScreen(post: post, viewModel: viewModel) // <-- here
}
if viewModel.isLoading {
ProgressView()
}
}
}
}
struct EditViewScreen: View {
#ObservedObject var viewModel: EditViewModel // <-- here
#Environment(\.presentationMode) var presentation
var post: Post
#State var p_title = ""
#State var p_body = ""
//....
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'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()
}
}
Starting point is a NavigationView within a TabView. I'm struggling with finding a SwiftUI solution to pop to the root view within the navigation stack when the selected tab is tapped again. In the pre-SwiftUI times, this was as simple as the following:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let navController = viewController as! UINavigationController
navController.popViewController(animated: true)
}
Do you know how the same thing can be achieved in SwiftUI?
Currently, I use the following workaround that relies on UIKit:
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: MyCustomView() // -> this is a normal SwiftUI file
.environment(\.managedObjectContext, context)))
navigationController.tabBarItem = UITabBarItem(title: "My View 1", image: nil, selectedImage: nil)
// add more controllers that are part of tab bar controller
let tabBarController = UITabBarController()
tabBarController.viewControllers = [navigationController /* , additional controllers */ ]
window.rootViewController = tabBarController // UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
Here is possible approach. For TabView it gives the same behaviour as tapping to the another tab and back, so gives persistent look & feel.
Tested & works with Xcode 11.2 / iOS 13.2
Full module code:
import SwiftUI
struct TestPopToRootInTab: View {
#State private var selection = 0
#State private var resetNavigationID = UUID()
var body: some View {
let selectable = Binding( // << proxy binding to catch tab tap
get: { self.selection },
set: { self.selection = $0
// set new ID to recreate NavigationView, so put it
// in root state, same as is on change tab and back
self.resetNavigationID = UUID()
})
return TabView(selection: selectable) {
self.tab1()
.tabItem {
Image(systemName: "1.circle")
}.tag(0)
self.tab2()
.tabItem {
Image(systemName: "2.circle")
}.tag(1)
}
}
private func tab1() -> some View {
NavigationView {
NavigationLink(destination: TabChildView()) {
Text("Tab1 - Initial")
}
}.id(self.resetNavigationID) // << making id modifiable
}
private func tab2() -> some View {
Text("Tab2")
}
}
struct TabChildView: View {
var number = 1
var body: some View {
NavigationLink("Child \(number)",
destination: TabChildView(number: number + 1))
}
}
struct TestPopToRootInTab_Previews: PreviewProvider {
static var previews: some View {
TestPopToRootInTab()
}
}
Here's an approach that uses a PassthroughSubject to notify the child view whenever the tab is re-selected, and a view modifier to allow you to attach .onReselect() to a view.
import SwiftUI
import Combine
enum TabSelection: String {
case A, B, C // etc
}
private struct DidReselectTabKey: EnvironmentKey {
static let defaultValue: AnyPublisher<TabSelection, Never> = Just(.Mood).eraseToAnyPublisher()
}
private struct CurrentTabSelection: EnvironmentKey {
static let defaultValue: Binding<TabSelection> = .constant(.Mood)
}
private extension EnvironmentValues {
var tabSelection: Binding<TabSelection> {
get {
return self[CurrentTabSelection.self]
}
set {
self[CurrentTabSelection.self] = newValue
}
}
var didReselectTab: AnyPublisher<TabSelection, Never> {
get {
return self[DidReselectTabKey.self]
}
set {
self[DidReselectTabKey.self] = newValue
}
}
}
private struct ReselectTabViewModifier: ViewModifier {
#Environment(\.didReselectTab) private var didReselectTab
#State var isVisible = false
let action: (() -> Void)?
init(perform action: (() -> Void)? = nil) {
self.action = action
}
func body(content: Content) -> some View {
content
.onAppear {
self.isVisible = true
}.onDisappear {
self.isVisible = false
}.onReceive(didReselectTab) { _ in
if self.isVisible, let action = self.action {
action()
}
}
}
}
extension View {
public func onReselect(perform action: (() -> Void)? = nil) -> some View {
return self.modifier(ReselectTabViewModifier(perform: action))
}
}
struct NavigableTabViewItem<Content: View>: View {
#Environment(\.didReselectTab) var didReselectTab
let tabSelection: TabSelection
let imageName: String
let content: Content
init(tabSelection: TabSelection, imageName: String, #ViewBuilder content: () -> Content) {
self.tabSelection = tabSelection
self.imageName = imageName
self.content = content()
}
var body: some View {
let didReselectThisTab = didReselectTab.filter( { $0 == tabSelection }).eraseToAnyPublisher()
NavigationView {
self.content
.navigationBarTitle(tabSelection.localizedStringKey, displayMode: .inline)
}.tabItem {
Image(systemName: imageName)
Text(tabSelection.localizedStringKey)
}
.tag(tabSelection)
.navigationViewStyle(StackNavigationViewStyle())
.keyboardShortcut(tabSelection.keyboardShortcut)
.environment(\.didReselectTab, didReselectThisTab)
}
}
struct NavigableTabView<Content: View>: View {
#State private var didReselectTab = PassthroughSubject<TabSelection, Never>()
#State private var _selection: TabSelection = .Mood
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
let selection = Binding(get: { self._selection },
set: {
if self._selection == $0 {
didReselectTab.send($0)
}
self._selection = $0
})
TabView(selection: selection) {
self.content
.environment(\.tabSelection, selection)
.environment(\.didReselectTab, didReselectTab.eraseToAnyPublisher())
}
}
}
Here's how I did it:
struct UIKitTabView: View {
var viewControllers: [UIHostingController<AnyView>]
init(_ tabs: [Tab]) {
self.viewControllers = tabs.map {
let host = UIHostingController(rootView: $0.view)
host.tabBarItem = $0.barItem
return host
}
}
var body: some View {
TabBarController(controllers: viewControllers).edgesIgnoringSafeArea(.all)
}
struct Tab {
var view: AnyView
var barItem: UITabBarItem
init<V: View>(view: V, barItem: UITabBarItem) {
self.view = AnyView(view)
self.barItem = barItem
}
}
}
struct TabBarController: UIViewControllerRepresentable {
var controllers: [UIViewController]
func makeUIViewController(context: Context) -> UITabBarController {
let tabBarController = UITabBarController()
tabBarController.viewControllers = controllers
tabBarController.delegate = context.coordinator
return tabBarController
}
func updateUIViewController(_ uiViewController: UITabBarController, context: Context) { }
}
extension TabBarController {
func makeCoordinator() -> TabBarController.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITabBarControllerDelegate {
var parent: TabBarController
init(_ parent: TabBarController){self.parent = parent}
var previousController: UIViewController?
private var shouldSelectIndex = -1
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
shouldSelectIndex = tabBarController.selectedIndex
return true
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if shouldSelectIndex == tabBarController.selectedIndex {
if let navVC = tabBarController.viewControllers![shouldSelectIndex].nearestNavigationController {
if (!(navVC.popViewController(animated: true) != nil)) {
navVC.viewControllers.first!.scrollToTop()
}
}
}
}
}
}
extension UIViewController {
var nearestNavigationController: UINavigationController? {
if let selfTypeCast = self as? UINavigationController {
return selfTypeCast
}
if children.isEmpty {
return nil
}
for child in self.children {
return child.nearestNavigationController
}
return nil
}
}
extension UIViewController {
func scrollToTop() {
func scrollToTop(view: UIView?) {
guard let view = view else { return }
switch view {
case let scrollView as UIScrollView:
if scrollView.scrollsToTop == true {
scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.safeAreaInsets.top), animated: true)
return
}
default:
break
}
for subView in view.subviews {
scrollToTop(view: subView)
}
}
scrollToTop(view: view)
}
}
Then in ContentView.swift I use it like this:
struct ContentView: View {
var body: some View {
ZStack{
UIKitTabView([
UIKitTabView.Tab(
view: FirstView().edgesIgnoringSafeArea(.top),
barItem: UITabBarItem(title: "Tab1", image: UIImage(systemName: "star"), selectedImage: UIImage(systemName: "star.fill"))
),
UIKitTabView.Tab(
view: SecondView().edgesIgnoringSafeArea(.top),
barItem: UITabBarItem(title: "Tab2", image: UIImage(systemName: "star"), selectedImage: UIImage(systemName: "star.fill"))
),
])
}
}
}
Note that when the user is already on the root view, it scrolls to top automatically
Here's what I did with introspect swiftUI library.
https://github.com/siteline/SwiftUI-Introspect
struct TabBar: View {
#State var tabSelected: Int = 0
#State var navBarOne: UINavigationController?
#State var navBarTwo: UINavigationController?
#State var navBarThree: UINavigationController?
var body: some View {
return TabView(selection: $tabSelected){
NavView(navigationView: $navBarOne).tabItem {
Label("Home1",systemImage: "bag.fill")
}.tag(0)
NavView(navigationView: $navBarTwo).tabItem {
Label("Orders",systemImage: "scroll.fill" )
}.tag(1)
NavView(navigationView: $navBarThree).tabItem {
Label("Wallet", systemImage: "dollarsign.square.fill" )
// Image(systemName: tabSelected == 2 ? "dollarsign.square.fill" : "dollarsign.square")
}.tag(2)
}.onTapGesture(count: 2) {
switch tabSelected{
case 0:
self.navBarOne?.popToRootViewController(animated: true)
case 1:
self.navBarTwo?.popToRootViewController(animated: true)
case 2:
self.navBarThree?.popToRootViewController(animated: true)
default:
print("tapped")
}
}
}
}
NavView:
import SwiftUI
import Introspect
struct NavView: View {
#Binding var navigationView: UINavigationController?
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: Text("Detail view")) {
Text("Go To detail")
}
}.introspectNavigationController { navController in
navigationView = navController
}
}
}
}
This actually isn't the best approach because it makes the entire tab view and everything inside of it have the double-tap gesture which would pop the view to its root. My current fix for this allows for one tap to pop up root view haven't figured out how to add double tap
struct TabBar: View {
#State var tabSelected: Int = 0
#State var navBarOne: UINavigationController?
#State var navBarTwo: UINavigationController?
#State var navBarThree: UINavigationController?
#State var selectedIndex:Int = 0
var selectionBinding: Binding<Int> { Binding(
get: {
self.selectedIndex
},
set: {
if $0 == self.selectedIndex {
popToRootView(tabSelected: $0)
}
self.selectedIndex = $0
}
)}
var body: some View {
return TabView(selection: $tabSelected){
NavView(navigationView: $navBarOne).tabItem {
Label("Home1",systemImage: "bag.fill")
}.tag(0)
NavView(navigationView: $navBarTwo).tabItem {
Label("Orders",systemImage: "scroll.fill" )
}.tag(1)
NavView(navigationView: $navBarThree).tabItem {
Label("Wallet", systemImage: "dollarsign.square.fill" )
// Image(systemName: tabSelected == 2 ? "dollarsign.square.fill" : "dollarsign.square")
}.tag(2)
}
}
func popToRootView(tabSelected: Int){
switch tabSelected{
case 0:
self.navBarOne?.popToRootViewController(animated: true)
case 1:
self.navBarTwo?.popToRootViewController(animated: true)
case 2:
self.navBarThree?.popToRootViewController(animated: true)
default:
print("tapped")
}
}
}
I took an approach similar to Asperi
Use a combination of a custom binding, and a separately stored app state var for keeping state of the navigation link.
The custom binding allows you to see all taps basically even when the current tab is the one thats tapped, something that onChange of tab selection binding doesn't show. This is what imitates the UIKit TabViewDelegate behavior.
This doesn't require a "double tap", if you just a single tap of the current, if you want double tap you'll need to implement your own tap/time tracking but shouldn't be too hard.
class AppState: ObservableObject {
#Published var mainViewShowingDetailView = false
}
struct ContentView: View {
#State var tabState: Int = 0
#StateObject var appState = AppState()
var body: some View {
let binding = Binding<Int>(get: { tabState },
set: { newValue in
if newValue == tabState { // tapped same tab they're already on
switch newValue {
case 0: appState.mainViewShowingDetailView = false
default: break
}
}
tabState = newValue // make sure you actually set the storage
})
TabView(selection: binding) {
MainView()
.tabItem({ Label("Home", systemImage: "list.dash") })
.tag(0)
.environmentObject(appState)
}
}
}
struct MainView: View {
#EnvironmentObject var appState: AppState
var body: {
NavigationView {
VStack {
Text("Hello World")
NavigationLink(destination: DetailView(),
isActive: $appState.mainViewShowingDetailView,
label: { Text("Show Detail") })
}
}
}
}
struct DetailView: View {
...
}
iOS 16 / NavigationStack approach with PassthroughSubject
Uses willSet on selectedTab to get the tap event, and uses a PassthroughSubject for sending the event to the children. This is picked up by the .onReceived and calls a function for popping the views from the NavigationStack
Did a full write up here: https://kentrobin.com/home/tap-tab-to-go-back/ and created a working demo project here: https://github.com/kentrh/demo-tap-tab-to-go-back
class HomeViewModel: ObservableObject {
#Published var selectedTab: Tab = .tab1 {
willSet {
if selectedTab == newValue {
subject.send(newValue)
}
}
}
let subject = PassthroughSubject<Tab, Never>()
enum Tab: Int {
case tab1 = 0
}
}
struct HomeView: View {
#StateObject var viewModel: HomeViewModel = .init()
var body: some View {
TabView(selection: $viewModel.selectedTab) {
Tab1View(subject: viewModel.subject)
.tag(HomeViewModel.Tab.tab1)
.tabItem {
Label("Tab 1", systemImage: "1.lane")
Text("Tab 1", comment: "Tab bar title")
}
}
}
}
struct Tab1View: View {
#StateObject var viewModel: Tab1ViewModel = .init()
let subject: PassthroughSubject<HomeViewModel.Tab, Never>
var body: some View {
NavigationStack(path: $viewModel.path) {
List {
NavigationLink(value: Tab1ViewModel.Route.viewOne("From tab 1")) {
Text("Go deeper to OneView")
}
NavigationLink(value: Tab1ViewModel.Route.viewTwo("From tab 1")) {
Text("Go deeper to TwoView")
}
}
.navigationTitle("Tab 1")
.navigationDestination(for: Tab1ViewModel.Route.self, destination: { route in
switch route {
case let .viewOne(text):
Text(text)
case let .viewTwo(text):
Text(text)
}
})
.onReceive(subject) { tab in
if case .tab1 = tab { viewModel.tabBarTapped() }
}
}
}
}
class Tab1ViewModel: ObservableObject {
#Published var path: [Route] = []
func tabBarTapped() {
if path.count > 0 {
path.removeAll()
}
}
enum Route: Hashable {
case viewOne(String)
case viewTwo(String)
}
}