SwiftUI passing selected date from modal to parent variables - swiftui

Need help with this please.
I have a view with 2 date variables and I want to show a modal which have the datepicker and let user pick different dates for these variables.
Currently I have two buttons that show the same sheet but pass different variable to the modal.
The problem the variable don’t update after dismissing the modal.
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
#State private var secOneDate = Date()
#State private var secTwoDate = Date()
#State private var isDatepickerPresented = false
var body: some View {
VStack {
HStack{
Button{
isDatepickerPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.indigo)
}
.sheet(isPresented: $isDatepickerPresented){
DatePickView(selectDate: $secOneDate)
}
Text("SecOneDate: \(secOneDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
HStack{
Button{
isDatepickerPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.mint)
}
.sheet(isPresented: $isDatepickerPresented)
{
DatePickView(selectDate: $secTwoDate)
}
Text("SecTwoDate: \(secTwoDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
}
}
}
import SwiftUI
struct DatePickView: View {
#Environment(\.dismiss) private var dismiss
#Binding var selectDate: Date
var body: some View {
VStack(alignment: .center, spacing: 20) {
HStack {
Text("\(selectDate)")
.padding()
Spacer()
Button {
dismiss()
} label: {
Image(systemName: "delete.backward.fill")
.foregroundColor(.indigo)
}
}.padding()
DatePicker("", selection: $selectDate)
.datePickerStyle(.graphical)
}
}
}

First of all, thank you for your minimal, reproducible example: it is clear and can be immediately used for debugging. Answering to your question:
The problem with your code is that you have only one variable that opens the sheet for both dates. Even though you are correctly passing the two different #Bindings, when you toggle isDatepickerPresented you are asking SwiftUI to show both sheets, but this will never happen. Without knowing, you are always triggering the first of the sheet presentations - the one that binds secOneDate. The sheet that binds secTwoDate is never shown because you can't have two sheets simultaneously.
With that understanding, the solution is simple: use two different trigger variables. Here's the code corrected (DatePickView doesn't change):
struct Example: View {
#State private var secOneDate = Date()
#State private var secTwoDate = Date()
#State private var isDatepickerOnePresented = false
#State private var isDatepickerTwoPresented = false
var body: some View {
VStack {
HStack{
Button{
isDatepickerOnePresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.indigo)
}
.sheet(isPresented: $isDatepickerOnePresented){
DatePickView(selectDate: $secOneDate)
}
Text("SecOneDate: \(secOneDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
HStack{
Button{
isDatepickerTwoPresented = true
} label: {
Image(systemName: "calendar")
.imageScale(.large)
.foregroundColor(.mint)
}
.sheet(isPresented: $isDatepickerTwoPresented) {
DatePickView(selectDate: $secTwoDate)
}
Text("SecTwoDate: \(secTwoDate.formatted(date: .abbreviated, time: .shortened))")
}
.padding()
}
}
}

Related

NavigationLink in a Section doesn't behave like in a normalView with a simultaneous action

I have created a simple View with a NavigationLink in a Section an when the user presses on it, the value of the variable should change and should navigate the next View simultaneously. But it doesn't work like it should. If I press the "Text", the Value changes, but no navigation. If I press the "empty Space" it navigates to the next View, but the value doesn't change.
If I out the NavigationLink in a "normal" View, it does work like it should.
Is there a way to get this working without SubViews?
#State private var newValue = -1
var body: some View {
NavigationView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("\(newValue)")
List {
Section ("Navigationlink") {
NavigationLink(destination: EmptyView()) {
Text("to Emptyview")
}.simultaneousGesture(TapGesture().onEnded{
newValue = 100
})
}
}
}
}
}
}
struct EmptyView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
}
}
You need a #State for Navigation to take place, this is needed as a source of truth needs to change(including navigation) in SwiftUI for any View change to happen , you change the #State for newValue so it changes, but you need to do same for NavigationView, also try NavigationStack in place of NavigationView in future , try below code , good luck
struct ContentView: View {
#State private var newValue = -1
#State private var changeView = false
var body: some View {
NavigationView {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("\(newValue)")
List {
Section ("Navigationlink") {
NavigationLink(destination: EmptyView(), isActive: $changeView) {
Text("to Emptyview")
}.simultaneousGesture(TapGesture().onEnded{
newValue = 100
changeView = true
})
}
}
}
}
}
}

SwiftUI .searchable implementation in the wrong way?

I am trying to use a tab bar in order to use different views. On some of those views I have a list of items and I wish that list to be .searchable. If I go to each of the views and search it works like a charm, but when I embed that in the tabbed view the list becomes non-responsive to click but it responds to scroll gesture.
I will expand the idea with code that I have and screenshots, but I am pretty sure that the problem resides in how I'm implementing the combination of the tab bar view and the views that have the searchable modifier:
This code works well
import SwiftUI
struct ClientListView: View {
#ObservedObject var viewModel = ClientFeedViewModel()
#State var searchText: String
#State private var showingSheet = false
#State private var showList = false
var clients: [Client] {
if searchText.count > 2 {
return searchText.isEmpty ? viewModel.clients : viewModel.search(withText: searchText)
}
return viewModel.clients
}
init(){
searchText = ""
}
var body: some View {
NavigationView {
List(clients) { client in
NavigationLink(destination: {
}, label: {
VStack {
Text(client.clientName)
}
})
.listRowSeparator(.hidden)
}
.searchable(text: $searchText)
.listStyle(.plain)
}
}
}
struct ClientListView_Previews: PreviewProvider {
static var previews: some View {
ClientListView()
}
}
The problem starts when I do this and implement the ClientListView in a tab bar view like this:
Tab bar with different views not working searchable modifier
This is the code of the Tab Bar View:
import SwiftUI
struct MainTabView: View {
#EnvironmentObject var viewModel: AuthViewModel
#Binding var selectedIndex: Int
var body: some View {
NavigationView {
VStack {
TabView(selection: $selectedIndex) {
ClientListView()
.onTapGesture {
selectedIndex = 0
}
.tabItem {
Label("Clients", systemImage: "list.bullet")
}.tag(0)
ProjectListView()
.onTapGesture {
selectedIndex = 1
}
.tabItem {
Image(systemName: "person")
Label("Projects", systemImage: "list.dash")
}.tag(1)
TaskListView()
.tabItem {
Image(systemName: "person")
Label("Tasks", systemImage: "list.dash")
}.tag(2)
.onTapGesture {
selectedIndex = 2
}
ClientListView()
.tabItem {
Label("Settings", systemImage: "gear")
}.tag(3)
.onTapGesture {
selectedIndex = 3
}
}
.navigationTitle(tabTitle)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Image("logo_silueta")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
viewModel.signOut()
}, label: {
Text("logout")
})
}
}
.navigationBarTitleDisplayMode(.inline)
}
}
var tabTitle: String {
switch selectedIndex {
case 0: return "Clients"
case 1: return "Projects"
case 2: return "Tasks"
case 3: return "Settings"
default: return ""
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView(selectedIndex: .constant(0))
}
}
Navigation on the tabbed view works and displays the different names on the tab bar title, but when I click cancel or x button of the search bar, it doesn't work and also the list becomes unclickable
So far I haven't been able to find where the problem is but I am assuming its because the tab bar view is messing up with the searchable property
The culprit would seem to be your .onTapGesture modifiers, which will take precedence over any tap handling in your child views.
I'm not sure what value those modifiers bring, since using appropriate .tag values is enough for the tab view to keep track of its selected index. I'd start by removing them.
#ObservedObject var viewModel = ClientFeedViewModel() is a memory leak, try changing it to something like:
struct ClientListViewData {
var searchText: String = ""
var showingSheet = false
var showList = false
mutating func showSheet() {
showingSheet = true
}
}
struct ClientListView: View {
#Binding var data: ClientListViewData

Weird fullScreenCover and sheet behaviour in iOS 15

I have some problem with presenting sheet, sheet that use Identifiable item binding and fullScreenCover.
Main issue is that:
I choose identifiable item, corresponding sheet appears
I close that sheet with swipe
After it's dismiss i open normal sheet
It open again identifiable item sheet content
Another weird behaviour is if I open fullScreenCover after dismissing sheet it's open sheet but with content of fullScreenCover
This never happens on iOS 14. I think that is states that responsible for presenting those views not have time to update
You can checkout minimal gist reproduction or see it here
import SwiftUI
struct ContentView: View {
enum Sheet: String, Identifiable {
var id: String {
rawValue
}
case one, two
}
#State var isFullScreenPresented: Bool = false
#State var isSheetPresented: Bool = false
#State var isItemSheetPresented: Sheet?
var body: some View {
VStack {
HStack {
Button(action: {isFullScreenPresented.toggle()}, label: {
Text("fullScreen")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isSheetPresented.toggle()}, label: {
Text("sheet")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Button(action: {isItemSheetPresented = .one}, label: {
Text("one")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isItemSheetPresented = .two}, label: {
Text("two")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Text(isFullScreenPresented.description)
Text(isSheetPresented.description)
}
Text(isItemSheetPresented.debugDescription)
Spacer()
}
.sheet(item: $isItemSheetPresented, onDismiss: {isItemSheetPresented = nil}, content: {item in
Text(item.id)
})
.sheet(isPresented: $isSheetPresented, onDismiss: { isSheetPresented = false}, content: {
Text("sheet")
})
.fullScreenCover(isPresented: $isFullScreenPresented, onDismiss: {isFullScreenPresented = false }, content: {FullScreenContent()})
}
}
struct FullScreenContent: View {
#Environment(\.presentationMode) var dismiss
var body: some View {
VStack {
Button("close", action: {dismiss.wrappedValue.dismiss()})
Spacer()
}
}
}

How do I properly use NavigationView in a ZStack?

I am trying to add some filter options to sit at the top of my view, above the NavigationView. I wrote the following code that mostly does what I want, however it disabled the ability to click on the rows to get to the detailed view. I assume this is because my filter buttons are on top of the ZStack, but I'm not sure how else to get this to work.
Here is the code I wrote:
import SwiftUI
struct BonusList: View {
var bonuses = sampleBonusData
#State var showSettings = false
#State var showBonuses = false
#State var bonusEarned = true
#State var showStatePicker = false
#State var showCategoryPicker = false
var body: some View {
ZStack {
NavigationView {
List(bonuses) { item in
NavigationLink(destination: BonusDetail(bonusName: item.bonusName, bonusCode: item.bonusCode, city: item.city, sampleImage: item.sampleImage)) {
HStack(spacing: 12.0) {
Image(item.sampleImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 60)
.background(Color.white)
.cornerRadius(15)
VStack(alignment: .leading) {
HStack {
Text(item.bonusName)
.font(.headline)
Spacer()
Image(systemName: "checkmark.shield")
.opacity(self.bonusEarned ? 100 : 0)
}
Text("\(item.city), \(item.state)")
.font(.subheadline)
.frame(height: 25.0)
HStack {
Text(item.bonusCategory)
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.gray)
.padding(.top, 4)
Spacer()
Text(item.bonusCode)
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.gray)
.padding(.top, 4)
}
}
}
}
}
.navigationBarTitle(Text("Bonuses"))
// .navigationBarHidden(true)
}
.saturation(self.bonusEarned ? 0 : 1)
HStack {
FilterByCategory(showCategoryPicker: $showCategoryPicker)
Spacer()
FilterByState(showStatePicker: $showStatePicker)
}
StatePicker(showStatePicker: $showStatePicker)
CategoryPicker(showCategoryPicker: $showCategoryPicker)
}
}
}
This is what it looks like when I run it:
If I'm understanding correctly, you have a view or two which sit higher in the ZStack that are off canvas and come in when those buttons are tapped?
You could consider using a modal and setting the view you want to show for each button as the view for the modal. This will keep your views off screen and still allow interaction with your list. Here's what I've done...
On the main view
import SwiftUI
struct MainView: View {
#State private var isPresented = false
var body: some View {
NavigationView {
VStack {
//...
}
//Modal
.sheet(isPresented: $isPresented, content: {
AddItem(showModal: self.$isPresented)
})
}
}
}
The modal's view
import SwiftUI
struct AddItem: View {
#Binding var showModal: Bool
var body: some View {
VStack {
Button(action: {
self.showModal = false
}, label: {
Text("Cancel")
})
}
}
}

SwiftUI, setting title to child views of TabView inside of NavigationView does not work

Why I am putting TabView into a NavigationView is because I need to hide the bottom tab bar when user goes into 2nd level 'detail' views which have their own bottom action bar.
But doing this leads to another issue: all the 1st level 'list' views hosted by TabView no longer display their titles. Below is a sample code:
import SwiftUI
enum Gender: String {
case female, male
}
let members: [Gender: [String]] = [
Gender.female: ["Emma", "Olivia", "Ava"], Gender.male: ["Liam", "Noah", "William"]
]
struct TabItem: View {
let image: String
let label: String
var body: some View {
VStack {
Image(systemName: image).imageScale(.large)
Text(label)
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
ListView(gender: .female).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
ListView(gender: .male).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
}
}
}
struct ListView: View {
let gender: Gender
var body: some View {
let names = members[gender]!
return List {
ForEach(0..<names.count, id: \.self) { index in
NavigationLink(destination: DetailView(name: names[index])) {
Text(names[index])
}
}
}.navigationBarTitle(Text(gender.rawValue), displayMode: .inline)
}
}
struct DetailView: View {
let name: String
var body: some View {
ZStack {
VStack {
Text("profile views")
}
VStack {
Spacer()
HStack {
Spacer()
TabItem(image: "pencil.circle", label: "Edit")
Spacer()
TabItem(image: "minus.circle", label: "Delete")
Spacer()
}
}
}
.navigationBarTitle(Text(name), displayMode: .inline)
}
}
What I could do is to have a #State var title in the root view and pass the binding to all the list views, then have those list views to set their title back to root view on appear. But I just don't feel so right about it, is there any better way of doing this? Thanks for any help.
The idea is to join TabView selection with NavigationView content dynamically.
Demo:
Here is simplified code depicting approach (with using your views). The NavigationView and TabView just position independently in ZStack, but content of NavigationView depends on the selection of TabView (which content is just stub), thus they don't bother each other. Also in such case it becomes possible to hide/unhide TabView depending on some condition - in this case, for simplicity, presence of root list view.
struct TestTabsOverNavigation: View {
#State private var tabVisible = true
#State private var selectedTab: Int = 0
var body: some View {
ZStack(alignment: .bottom) {
contentView
tabBar
}
}
var contentView: some View {
NavigationView {
ListView(gender: selectedTab == 0 ? .female : .male)
.onAppear {
withAnimation {
self.tabVisible = true
}
}
.onDisappear {
withAnimation {
self.tabVisible = false
}
}
}
}
var tabBar: some View {
TabView(selection: $selectedTab) {
Rectangle().fill(Color.clear).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
Rectangle().fill(Color.clear).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
.frame(height: 50) // << !! might be platform dependent
.opacity(tabVisible ? 1.0 : 0.0)
}
}
This maybe a late answer, but the TabView items need to be assigned tag number else binding selection parameter won't happen. Here is how I do the same thing on my project:
#State private var selectedTab:Int = 0
private var pageTitles = ["Home", "Customers","Sales", "More"]
var body: some View {
NavigationView{
TabView(selection: $selectedTab, content:{
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text(pageTitles[0])
}.tag(0)
CustomerListView()
.tabItem {
Image(systemName: "rectangle.stack.person.crop.fill")
Text(pageTitles[1])
}.tag(1)
SaleView()
.tabItem {
Image(systemName: "tag.fill")
Text(pageTitles[2])
}.tag(2)
MoreView()
.tabItem {
Image(systemName: "ellipsis.circle.fill")
Text(pageTitles[3])
}.tag(3)
})
.navigationBarTitle(Text(pageTitles[selectedTab]),displayMode:.inline)
.font(.headline)
}
}