How to present modal in SwiftUI automatically without button - swift

I am thinking of using a switch case to present different views.
struct searchview : View {
var body: some View {
VStack {
VStack {
if self.speechRecognition.isPlaying == true {
VStack {
Text(self.speechRecognition.recognizedText).bold()
.foregroundColor(.white)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.font(.system(size: 50))
.sheet(isPresented: $showSheet) {
self.sheetView
}
}.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.showSheet = true
}
}
}
}.onAppear(perform: getViews)
}
var currentSheetView: String {
var isProductDictEmpty = Global.productDict.isEmpty
var wasTextRecognizedEmpty = self.speechRecognition.recognizedText.isEmpty
var checkTextandDict = (wasTextRecognizedEmpty,isProductDictEmpty)
switch checkTextandDict {
case (true,true):
print("Product dict and Text rec are empty")
return "error"
case (false,false):
print("Yes we are in business")
return "product"
case (true,false):
print("OOPS we didnt catch that")
return "error"
case (false,true):
print("OOPS we didnt catch that")
return "zero match"
}
}
#ViewBuilder
var sheetView: some View {
if currentSheetView == "product" {
ProductSearchView(model: self.searchModel)
}
else if currentSheetView == "zero match" {
zeroResult()
}
else if currentSheetView == "error" {
SearchErrorView()
}
}
}
}
I know how to use .sheet modifier to present modal when a button is pressed but how could I the present the respective modals in swift cases automatically with button?
UPDATE
these are the views I am trying to implement
struct SearchErrorView: View {
var body: some View {
VStack {
Text("Error!")
Text("Oops we didn't catch that")
}
}
}
struct zeroResult: View {
var body: some View {
Text("Sorry we did not find any result for this item")
}
}
I'm not sure what I am doing wrongly. I tried to implement the solution below but still not able to call the views with the switch cases.

The solution is to programmatically set a variable controlling displaying of your sheet.
You can try the following to present your sheet in onAppear:
struct ContentView: View {
#State var showSheet = false
var body: some View {
Text("Main view")
.sheet(isPresented: $showSheet) {
self.sheetView
}
.onAppear {
self.showSheet = true
}
}
var currentSheetView: String {
"view1" // or any other...
}
#ViewBuilder
var sheetView: some View {
if currentSheetView == "view1" {
Text("View 1")
} else {
Text("View 2")
}
}
}
You may delay it as well:
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.showSheet = true
}
}
Just setting self.showSheet = true will present the sheet. It's up to you how you want to trigger it.

Related

Open sheet and overwrite current sheet or provide internal navigationstack inside sheet

So I have a button as shown below:
private var getStartedButton: some View {
Button {
showGetStartedSheet.toggle()
} label: {
Text("Get Started")
}
.sheet(isPresented: $showGetStartedSheet) {
LoginUserSheetView()
.presentationDetents([.fraction(0.70), .large])
}
}
Which opens the LoginUserSheetView() view and has the following function inside:
private var userCreateAccount: some View {
VStack(alignment: .center) {
Text("New MapGliders? User register")
.sheet(isPresented: $showUserRegisterSheet) {
RegisterUserSheetView()
.presentationDetents([.fraction(0.70), .large])
}
}
}
The above code, then opens another sheet which presents the following code:
private var appleButton: some View {
Button {
// Hello
} label: {
HStack(alignment: .firstTextBaseline) {
Image(systemName: "applelogo")
Text("Hello")
}
}
}
The above code (lots has been removed) produces the following output:
https://im4.ezgif.com/tmp/ezgif-4-4ecfdb6d55.gif
As you can see the video above, the second sheet opens on top of the old sheet, I would like the sheet to be overwritten or create a navigation on a single sheet.
Does anyone know how I can close LoginUserSheetView() when RegisterUserSheetView() is opened? or how could I make the sheet be overwritten or even use a navigation to navigate to RegisterUserSheetView() when on the LoginUserSheetView() is opened.
The first option is to use sheet(item:). But this does dismiss the sheet and they makes it reappear with the new value
struct DynamicOverlay: View {
#State var selectedOverlay: OverlayViews? = nil
var body: some View {
VStack{
Text("hello there")
Button("first", action: {
selectedOverlay = .first
})
}
.sheet(item: $selectedOverlay){ passed in
passed.view($selectedOverlay)
}
}
enum OverlayViews: String, Identifiable{
var id: String{
rawValue
}
case first
case second
#ViewBuilder func view(_ selectedView: Binding<OverlayViews?>) -> some View{
switch self{
case .first:
ZStack {
Color.blue
.opacity(0.5)
Button {
selectedView.wrappedValue = .second
} label: {
Text("Next")
}
}
case .second:
ZStack {
Color.red
.opacity(0.5)
Button("home") {
selectedView.wrappedValue = nil
}
}
}
}
}
}
The second option doesn't have the animation behavior but required a small "middle-man" View
import SwiftUI
struct DynamicOverlay: View {
#State var selectedOverlay: OverlayViews = .none
#State var presentSheet: Bool = false
var body: some View {
VStack{
Text("hello there")
Button("first", action: {
selectedOverlay = .first
presentSheet = true
})
}
.sheet(isPresented: $presentSheet){
//Middle-main
UpdatePlaceHolder(selectedOverlay: $selectedOverlay)
}
}
enum OverlayViews{
case first
case second
case none
#ViewBuilder func view(_ selectedView: Binding<OverlayViews>) -> some View{
switch self{
case .first:
ZStack {
Color.blue
.opacity(0.5)
Button {
selectedView.wrappedValue = .second
} label: {
Text("Next")
}
}
case .second:
ZStack {
Color.red
.opacity(0.5)
Button("home") {
selectedView.wrappedValue = .none
}
}
case .none:
EmptyView()
}
}
}
//Needed to reload/update the selected item on initial presentation
struct UpdatePlaceHolder: View {
#Binding var selectedOverlay: OverlayViews
var body: some View{
selectedOverlay.view($selectedOverlay)
}
}
}
You can read a little more on why you need that intermediate view here SwiftUI: Understanding .sheet / .fullScreenCover lifecycle when using constant vs #Binding initializers

SwiftUI - confirmationDialog has abnormal behavior when inside a LazyVStack

I have a ScrollView with a LazyVStack which holds n subviews.
Each subview has a button which will present a confirmation dialog, the confirmation dialog is created inside the child.
the confirmation dialog for some reason doesn't work after seeing 3 (more or less) subviews, you could press the button many times but won't immediately show the dialog, if you wait around while scrolling, suddenly every dialog will popup one after another.
video testing
Code for testing:
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 50) {
ForEach(0...100, id: \.self) { _ in
SubView()
}
}
}
.padding()
}
}
struct SubView: View {
#State var flag = false
var body: some View {
ZStack(alignment: .bottom) {
RoundedRectangle(cornerRadius: 30)
.frame(height: 500)
.foregroundColor(.gray)
.overlay {
Button("Press me") {
flag.toggle()
}
.confirmationDialog("", isPresented: $flag, actions: {
Button(role: .none) {
print("option 1")
} label: {
Text("option 1")
}
Button(role: .cancel) {
flag = false
} label: {
Text("cancel")
}
})
}
}
}
}
Approach
Move the confirmationDialog outside the LazyVStack
Code
struct ContentView: View {
#State private var flag = false
var body: some View {
ScrollView {
LazyVStack(spacing: 50) {
ForEach(0...100, id: \.self) { _ in
SubView(flag: $flag)
}
}
.confirmationDialog("", isPresented: $flag) {
Button(role: .none) {
print("option 1")
} label: {
Text("option 1")
}
Button(role: .cancel) {
flag = false
} label: {
Text("cancel")
}
}
}
.padding()
}
}
struct SubView: View {
#Binding var flag: Bool
var body: some View {
ZStack(alignment: .bottom) {
RoundedRectangle(cornerRadius: 30)
.frame(height: 500)
.foregroundColor(.gray)
.overlay {
Button("Press me") {
flag.toggle()
}
}
}
}
}

Button with NavigationLink and function call SwiftUI

I have an issue with NavigationLinks with conditions. It doesn't react what I'm expected. When a user click on the button the function "test" must be called and give a return value. If the return value is true the "SheetView" must be openend directly without clicking on the NavigationLink text. Please could someone give me a help on this one. Thanks in advance
I made a small (sample) program for showing the issue.
import SwiftUI
struct LoginView: View {
#State var check = false
#State var answer = false
var body: some View {
NavigationView {
VStack{
Text("it doesn't work")
Button(action: {
answer = test(value: 2)
if answer {
//if the return value is true then directly navigate to the sheetview
NavigationLink(
destination: SheetView(),
label: {
Text("Navigate")
})
}
}, label: {
Text("Calculate")
})
}
}
}
func test(value: Int) -> Bool {
if value == 1 {
check = false
} else {
print("Succes")
check = true
}
return check
}
}
struct SheetView: View {
var body: some View {
NavigationView {
VStack{
Text("Test")
.font(.title)
}
}
}
}
The answer from Yodagama works if you were trying to present a sheet (because you called your navigation destination SheetView), but if you were trying to navigate to SheetView instead of present a sheet, the following code would do that.
struct LoginView: View {
#State var check = false
#State var answer = false
var body: some View {
NavigationView {
VStack{
Text("it doesn't work")
NavigationLink(destination: SheetView(), isActive: $answer, label: {
Button(action: {
answer = test(value: 2)
}, label: {
Text("Calculate")
})
})
}
}
}
func test(value: Int) -> Bool {
if value == 1 {
check = false
} else {
print("Succes")
check = true
}
return check
}
}
struct SheetView: View {
var body: some View {
NavigationView {
VStack{
Text("Test")
.font(.title)
}
}
}
}
struct LoginView: View {
#State var check = false
#State var answer = false
var body: some View {
NavigationView {
VStack{
Text("it doesn't work")
Button(action: {
answer = test(value: 2)
//<= here
}, label: {
Text("Calculate")
})
}
.sheet(isPresented: $answer, content: { //<= here
SheetView()
})
}
}
...

Where should ı assign variable in View - SwiftUI

I want to assign my storeId, which I am getting from my API. I want to assign it to a global variable. I did this with using onAppear and it works, but it causes lag when the screen opens.
Im looking for better solution for this. Where should I assign the storeId to my global variable?
This is my code:
struct ContentView: View {
var body: some View {
ZStack {
NavigationView {
ScrollView {
LazyVStack {
ForEach(storeArray,id:\.id) { item in
if item.type == StoreItemType.store_Index.rawValue {
NavigationImageView(item: item, destinationView: ShowCaseView()
.navigationBarTitle("", displayMode: .inline)
.onAppear{
Config.storeId = item.data?.storeId
})
} else if item.type == StoreItemType.store_link.rawValue {
if item.data?.type == StoreDataType.html_Content.rawValue {
NavigationImageView(item: item, destinationView: WebView())
} else if item.data?.type == StoreDataType.product_List.rawValue {
NavigationImageView(item: item, destinationView: ProductListView())
} else if item.data?.type == StoreDataType.product_Detail.rawValue {
NavigationImageView(item: item, destinationView: ProductDetailView())
}
} else {
fatalError()
}
}
}
}
.navigationBarTitle("United Apps")
}
.onAppear {
if isOpened != true {
getStoreResponse()
}
}
ActivityIndicator(isAnimating: $isAnimating)
}
}
func getStoreResponse() {
DispatchQueue.main.async {
store.storeResponse.sink { (storeResponse) in
isAnimating = false
storeArray.append(contentsOf: storeResponse.items!)
isOpened = true
}.store(in: &cancellable)
store.getStoreResponse()
}
}
}
struct NavigationImageView <DestinationType : View> : View {
var item : Store
var destinationView: DestinationType
var body: some View {
NavigationLink(destination:destinationView ) {
Image(uiImage: (item.banner?.url)!.load())
.resizable()
.aspectRatio(CGFloat((item.banner?.ratio)!), contentMode: .fit)
.cornerRadius(12)
.shadow(radius: 4)
.frame(width: GFloat(UIScreen.main.bounds.width * 0.9),
height: CGFloat((UIScreen.main.bounds.width / CGFloat((item.banner?.ratio) ?? 1))))
}
}
}

why is SwiftUI modal view updating parent variable here (code attached)

In the code below if I change the value of the TextField and then click "Cancel" (i.e. will not then do a coredata save), after this modal view is hidden the value is changed in the parents UI list?
Is this line effectively passing my ref? If yes how to change to be effectively by value?
UPDATE: Actually it appears the code in the Save button is getting call directly after the Code in the cancel button, that is in the case I'm clickon on Cancel. Not sure why this would be occurring?
Code:
import SwiftUI
struct GCListsViewEdit: View {
#Environment (\.presentationMode) var presentationMode
#State var titleStr : String = ""
var gcItem : GCList?
var body: some View {
NavigationView {
Form {
Section(header: Text("Enter Details")) {
TextField("List Title", text: self.$titleStr)
.onAppear {
self.titleStr = self.gcItem?.title ?? "" // ** HERE **
}
}
HStack {
Button("Cancel") {
self.presentationMode.wrappedValue.dismiss()
}
Spacer()
Button("Save") {
guard !self.titleStr.isEmpty else {
return
}
guard let item = self.gcItem else {
return
}
item.title = self.titleStr
GCCoreData.save()
self.presentationMode.wrappedValue.dismiss()
}
}
}
.navigationBarTitle("Edit List")
}
}
}
PARENT - just the body part
var body : some View {
NavigationView {
VStack {
// -- Main List --
List() {
ForEach(gcLists) { gcList in
HStack {
if self.editMode {
Button(action: {}) {
Text("\(gcList.title)")
}
.onTapGesture {
self.selectedListViewItem = gcList
self.newListItemTitle = gcList.title
self.showEditView.toggle()
}
.sheet(isPresented: self.$showEditView, content: {
GCListsViewEdit(gcItem: self.selectedListViewItem!)
})
} else {
NavigationLink(destination: GCTasksView(withGcList: gcList)) {
Text("\(gcList.title)")
}
}
}
}
.onDelete(perform: self.deleteList)
.onMove(perform: self.move)
}
.environment(\.editMode, editMode ? .constant(.active) : .constant(.inactive))
.alert(isPresented: $showingAlert) {
Alert(
title: Text(verbatim: "Important Message"),
message: Text(self.alertString),
dismissButton: Alert.Button.default(Text(verbatim: "Cancel"))
)
}
.navigationBarTitle( Text("Todo Lists") )
.navigationBarItems(
trailing: Button(action: {
print("Edit" as Any)
self.editMode = !self.editMode
} ) {
Text(editMode ? "Done" : "Edit")
}
)
// -- Add List Item ---selectedListViewItem
Button("Add List") {
self.newListItemTitle = ""
self.showAddView.toggle()
}
.sheet(isPresented: $showAddView, content: { GCListsViewAdd() } )
}
}
}
Form is kind of List and List has specific handling of standard buttons in a row - it makes active entire row, so when row tapped in any place a button (or buttons) got activated.
In your example even if you tap in between Cancel and Save, both action are executed.
There are several possible solutions:
1) use tap gestures (keeping buttons or replacing them with other views, Text, Image, etc.), like
HStack {
Button("Cancel") {}
.onTapGesture {
print(">> do cancel")
}
Spacer()
Button("Save") {}
.onTapGesture {
print(">> do save")
}
}
2) use custom button style, because List intercepts only DefaultButtonStyle buttons
struct CustomButton: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? Color.gray : Color.blue)
}
}
HStack {
Button("Cancel") {
print(">> do cancel")
}.buttonStyle(CustomButton())
Spacer()
Button("Save") {
print(">> do save")
}.buttonStyle(CustomButton())
}
3) move buttons out of Form for this screen (eg. in NavigationBar)