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

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)

Related

SwiftUI - Not changing views

I have a form that I want to change views from if a Bool is false:
var body: some View {
NavigationView {
Form {
Section {
Toggle(isOn: $ClientAnswer) {
Text("Client answer")
}
if ClientAnswer {
Toggle(isOn: $submission.fieldOne ) {
Text("Field One")
}
Toggle(isOn: $submission.fieldTwo ) {
Text("Field Two")
}
}
}
Section {
Button(action: {
if self.ClientAnswer{
self.placeSubmission()
}
else {
ShowRS() //**change views here.**
print("test")
}
}){
Text("Submit")
}
}.disabled(!submission.isValid)
}
}
}
The code is being executed as print("test") works, but it doesn't change view it just stays on the same view?
The view I am trying to switch to is:
struct ShowRS: View {
var body: some View {
Image("testImage")
}
}
You have to include ShowRS in your view hierarchy -- right now, it's just in your Buttons action callback. There are multiple ways to achieve this, but here's one option:
struct ContentView : View {
#State private var moveToShowRSView = false
var body: some View {
NavigationView {
Button(action: {
if true {
moveToShowRSView = true
}
}) {
Text("Move")
}.overlay(NavigationLink(destination: ShowRS(), isActive: $moveToShowRSView, label: {
EmptyView()
}))
}
}
}
struct ShowRS: View {
var body: some View {
Image("testImage")
}
}
I've simplified this down from your example since I didn't have all of your code with your models, etc, but it demonstrates the concept. In the Button action, you test a boolean (in this case, it'll always return true) and then set the #State variable moveToShowRSView.
If moveToShowRSView is true, there's an overlay on the Button that has a NavigationLink (which is invisible because of the EmptyView) which will only be active if moveToShowRSView is true

How to deselect a selected Form (UITableView) cell - SwiftUI

Everything I've found is how we do this in UIKit. Here's a useful extension for no style at all:
extension UITableViewCell {
func noStyle() {
self.selectionStyle = .none
}
}
How can I select a cell in a SwiftUI Form and remove the selected (namely, still highlighted) cell, even after I push to a new view and return to the view with the selected cell?
To be clear, I am using a Form instead of a List.
Here's an example:
Form {
Section(header: Text("Header")) {
NavigationLink(
destination: DetailView(user: user)) {
HStack {
Text("Cell")
Spacer()
}
}
}
}
Ideally I would be able to do .selectedStyle(.none) on the penultimate brace, on the Section.
#State private var selected: User?
var body: some View {
Form {
Section(header: Text("Header")) {
NavigationLink(
destination: DetailView(user: user),
tag: user,
selection: $selected) {
HStack {
Text("Cell")
Spacer()
}
}
.onDisappear {
selected = nil
}
}
}
}

How to present modal in SwiftUI automatically without button

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.

Programmatically presenting SwiftUI view is not working

I have a SwiftUI view that I want to display programmatically. I currently present the screen with a button but I want to present it programmatically
This is the view:
struct SearchResultView: View {
#ObservedObject var model: SearchResultViewModel
var body: some View {
VStack {
VStack{
Text("Check Aisles ")
HStack{
ForEach(0 ..< model.aisleArry.count){aisleNum in
Text(String(self.model.aisleArry[aisleNum])).bold()
}
}
Text( "For Blue Coffee")
}
Spacer()
VStack {
ForEach(0 ..< Global.productArry.count) { value in
Text(Global.productArry[value].name)
}
}
Spacer()
}.onAppear { self.model.getValue() }
}
}
This is the button that presents it:
struct homeMainView: View {
var body: some View {
Button("Search") {
// show the search sheet
self.searchSheet.toggle()
}
.sheet(isPresented: $searchSheet) {
ProductSearchView(model: self.searchModel)
//
}
VStack {
if self.speechRecognition.isPlaying {
VStack {
Text(self.speechRecognition.recognizedText).bold()
}.onAppear{
//if text is recognize wait a few seconds and launch searchResult View
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// wait 1 second
// I WANT TO PRESENT IT PROGRAMATICALLY HERE
ProductSearchView(model:self.searchModel).presentationMode.wrappedValue.isPresented
self.sheet(isPresented: self.$searchSheet) {
ProductSearchView(model: self.searchModel)
}
}
}
}
}
}
I have tried many methods but could not get it to work. How can I present the SearchResultView programmatically?
Here is a possible solution
VStack {
if self.speechRecognition.isPlaying {
VStack {
Text(self.speechRecognition.recognizedText).bold()
}.onAppear{
//if text is recognize wait a few seconds and launch searchResult View
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// wait 1 second
self.searchSheet.toggle() // << activate here !!
}
}
}
}
.sheet(isPresented: $searchSheet) { // << attach here !!
ProductSearchView(model: self.searchModel)
}

Animate State change in SwiftUI

I‘m currently playing around with SwiftUI. In SwiftUI it‘s possible, to animate a State change for example like so:
struct Foo: View {
#State private var show = false
var body: some View {
VStack {
if show {
Text("Foo")
}
Button(action: {
withAnimation {
self.show.toggle()
}
}) {
Text(show ? "Hide" : "Show")
}
}
}
}
But if I have for example a TextField:
struct Foo: View {
#State private var text = ""
var body: some View {
VStack {
TextField($text, placeholder: Text("Foo")) {
print("editing ended")
}
if !text.isEmpty {
Button(action: {}) {
Text("Done")
}
}
}
}
}
I‘m not able to find a way to animate this change, because the State property is changed by the TextField without a call to withAnimation().
Is it possible to get this change animated?
Just add the animation modifier to wrap your button
var body: some View {
VStack {
TextField($text, placeholder: Text("Foo")) {
print("editing ended")
}
// if !text.isEmpty {
Button(action: {}) {
Text("Done")
}
.background(text.isEmpty ? Color.red : Color.yellow )
//.animation(.basic(duration: 1))
.animation(Animation.default.speed(1))
}
}
}
TextField("Placeholder", text:$text.animation())
Everything that uses the text will be animated when it is changed.