presentationMode.wrappedValue.dismiss() not working properly - swiftui

I'm using the sheet method to display a simple form and I pass into it a couple of varsiables. The problem is that if I click the button which performs the .dismiss() method after changing the variables passed in it doesn't work. Instead if I directly click the button it works normally.
Here's the code:
struct EditProductForm: View {
var listIndex : Int
var product : Product
#State var quantity: Int
#State var productName : String
#EnvironmentObject var data : Data
#Environment(\.presentationMode) var presentationModeEdit
func editProduct(){
self.data.editProduct(listIndex: self.listIndex, product: self.product, productName: self.productName, quantity: self.quantity)
}
var body: some View {
VStack{
Spacer()
VStack(spacing: 64){
Text("Edit Product")
TextField("Edit the name", text: $productName)
Picker(selection: $quantity, label: Text("Quantity")){
Text("OK").tag(Constants.Status.OK)
Text("Almost finished").tag(Constants.Status.ALMOST_NONE)
Text("Finished").tag(Constants.Status.NONE)
}.pickerStyle(SegmentedPickerStyle())
Button(action: {
self.editProduct()
self.presentationModeEdit.wrappedValue.dismiss()
}){
Text("Save")
}
}
Spacer()
}.padding(.horizontal)
}
}
I also checked if the isPresented variable changes value and it's actually toggled when i click the button but the sheet stays there.
Here's the code where I use the form:
ForEach(self.list.content, id: \.self) { item in
Button(action: {
self.show_modal_edit[self.list.content.firstIndex(of: item)!] = true
}){
ProductCell(item: item)
}.sheet(isPresented: self.$show_modal_edit[self.list.content.firstIndex(of: item)!]){
EditProductForm(
listIndex: self.listIndex,
product: item,
quantity: item.quantity,
productName: item.productName
).environmentObject(self.data)
}
}
show_modal_edit is a list of Bool, I checked the values and apparently the correct one is passed to the isPresented field of .sheet().

I've setup the following test version of your code and all is working well for me on ios 13.4 and macos catalyst after renaming Data to Datax.
This points to the function in editProduct()
self.data.editProduct(listIndex: self.listIndex, product: self.product, productName: self.productName, quantity: self.quantity)
as the possible source of your problem. Specifically, using Data as the name for your type. It seems to clash with the system struct Data type. Try renaming your ObservableObject class to something else (Datax in my test).
import SwiftUI
class Datax: ObservableObject {
#Published var xxx = "xxx"
func editProduct(listIndex: Int, product: String, productName: String, quantity: Int) {
print("---> editProduct")
}
}
struct ContentView: View {
var data = Datax()
#State var showEditProductForm = false
var body: some View {
VStack {
Text("main view")
Button("EditProductForm") {
self.showEditProductForm.toggle()
}
}.sheet(isPresented: $showEditProductForm) {
EditProductForm(listIndex: 2, product: "product", quantity: 1, productName: "productName")
.environmentObject(self.data)
}
}
}
struct EditProductForm: View {
#EnvironmentObject var data: Datax
#Environment(\.presentationMode) var presentationModeEdit: Binding<PresentationMode>
var listIndex: Int
var product: String
#State var quantity: Int
#State var productName: String
func editProduct() {
self.data.editProduct(listIndex: self.listIndex, product: self.product, productName: self.productName, quantity: self.quantity)
}
var body: some View {
VStack{
Spacer()
VStack(spacing: 64){
Text("Edit Product")
TextField("Edit the name", text: $productName)
Picker(selection: $quantity, label: Text("Quantity")){
Text("OK").tag(0)
Text("Almost finished").tag(1)
Text("Finished").tag(2)
}.pickerStyle(SegmentedPickerStyle())
Button(action: {
self.editProduct()
self.presentationModeEdit.wrappedValue.dismiss()
}){
Text("Save")
}
}
Spacer()
}.padding(.horizontal)
}
}
Hope this helps track down your issue.

Related

Touch events seemingly not registering at top of screen

I'm seeing very strange behavior within a view. Here's my layout:
struct EventDetailViewContainer: View {
let eventID: EventRecord.ID
#State var event: EventRecord = EventRecord(keyResults: [], text: "", achievesKR: false)
#State var editing: Bool = true
var body: some View {
if #available(iOS 15.0, *) {
VStack {
HStack {
Spacer()
Toggle("Editing", isOn: $editing)
.padding()
}
EventDetailView(event: $event, editing: $editing)
}
} else {
// Fallback on earlier versions
}
}
}
#available(iOS 15.0, *)
struct EventDetailView: View {
#Binding var event: EventRecord
#Binding var editing: Bool
#FocusState var textIsFocused: Bool
var body: some View {
VStack {
TextField(
"Event text",
text: $event.text
)
.focused($textIsFocused)
.disabled(!editing)
.padding()
DatePicker("Event Date:", selection: $event.date)
.disabled(!editing)
.padding()
Toggle("Goal is Reached?", isOn: $event.achievesKR)
.disabled(!editing)
.padding()
HStack {
Text("Notes:")
Spacer()
}
.padding()
TextEditor(text: $event.notes)
.disabled(!editing)
.padding()
Spacer()
}
}
}
struct EventRecord: Identifiable, Equatable {
typealias ID = Identifier
struct Identifier: Identifiable, Equatable, Hashable {
typealias ID = UUID
let id: UUID = UUID()
}
let id: ID
var keyResults: [KeyResult.ID]
var date: Date
var text: String
var notes: String
var achievesKR: Bool
init(
id: ID = ID(),
keyResults: [KeyResult.ID],
date: Date = Date(),
text: String,
notes: String = "",
achievesKR: Bool
) {
self.id = id
self.keyResults = keyResults
self.date = date
self.text = text
self.notes = notes
self.achievesKR = achievesKR
}
}
So this works perfectly when I run it as an iPad app, but when I run it on the simulator, the the top toggle doesn't respond to text input.
The strange thing is, when I simply duplicate the toggle, the top one doesn't work and the bottom one works perfectly:
struct EventDetailViewContainer: View {
let eventID: EventRecord.ID
#State var event: EventRecord = EventRecord(keyResults: [], text: "", achievesKR: false)
#State var editing: Bool = true
var body: some View {
if #available(iOS 15.0, *) {
VStack {
HStack {
Spacer()
Toggle("Editing", isOn: $editing)
.padding()
}
HStack {
Spacer()
Toggle("Editing", isOn: $editing)
.padding()
}
EventDetailView(event: $event, editing: $editing)
}
} else {
// Fallback on earlier versions
}
}
}
It seems like this should be totally unrelated to the touch behavior of the other views.
Btw this is being displayed in the context of a navigation view.
Is there anything that can explain this? And how can I get it working without adding this extra view on top?
edit: Here's a gif of this behavior being demonstrated. The two controls are exactly the same, but the lower one responds to touch and the upper one does not.

Can't transfer variable from one watchos view to another view, Using swiftui

I am trying to get data from one view to another.
I can not figure out how to get values from the fourth view array into the Third view.
I am not using storyboards. I tried using #EnvironmentObject but can not make it work. New to coding. In xcode I am using watchos without app.
I tried to strip out most of the code and leave just the important stuff that can be tested. I used NavigationLink(destination: )to transfer between views.
enter code here
class viewFromEveryWhere: ObservableObject {
#Published var testname2: String = "testTTname"
}
struct secondView: View {
var body: some View {
Text("second view")
List(1..<7) {
Text("\($0)")
}
}
}
struct thirdView: View {
#EnvironmentObject var testname2: viewFromEveryWhere
#EnvironmentObject var testSixTestArray: viewFromEveryWhere
#State var sixTestArray:[String] = ["ww","GS","DW","CD","TS","JW",]
var body: some View {
List(sixTestArray, id:\.self) {
Text($0)
}
}
}
struct fourthView: View {
#StateObject var testname2 = viewFromEveryWhere()
#State private var name: String = ""
#State var testSixTestArray:[String] = []
func collectName () {
print("collectName triggered")
if testSixTestArray.count < 5 {
// testSixTestArray.append(name)
print(name)
print(testSixTestArray)
}
// .enviromentObject(testSixTestArray)
}
var body: some View {
VStack(alignment: . leading) {
Text("Type a name")
TextField("Enter your name", text: $name)
Text("Click to add, \(name)!")
// Button("click this if \(name) is correct") {}
Button(action:{
print("Button Tapped")
collectName()
print(testSixTestArray.count)
name = ""
}) {
Text("Add \(name) to list")
}
// .buttonStyle(GrowingButton1())
}
Text("forth view")
// testSixTestArray.append(name)
.environmentObject(testname2)
}
}
/*func presentTextInputControllerWithSuggestions(forLanguage suggestionsHandler:
((String)-> [Any]?)?,
allowedInputMode inputMode:
WKTextInputMode,
completion: #escaping ([Any]?) -> Void) {}
*/
struct ContentView: View {
#State var sixNameArray:[String] = ["","","","","","",]
#State var messageTextBox: String = "Start"
#State var button1: String = "Button 1"
#State var button2: String = "Button 2"
#State var button3: String = "Button 3"
var body: some View {
NavigationView {
VStack{
Text(messageTextBox)
.frame(width: 120, height: 15, alignment: .center)
.truncationMode(.tail)
.padding()
NavigationLink(destination: secondView(),
label:{
Text(button1)
})
.navigationBarTitle("Main Page")
NavigationLink(destination: thirdView(),
label:{
Text(button2)
})
NavigationLink(destination: fourthView(),
label:{
Text(button3)
})
}
}
}
}
enter code here

SwiftUI NavigationView Unexpected Pop to Root

I've created some SwiftUI code that use an EnvironmentObject to store boolean values for popping back to the root view and another EnvironmentObject to store a score variable that is intended to be used across multiple views.
In this example, I have two games, a Red Game and a Blue Game. I have booleans (.redStacked and .blueStacked) that are initially set to false. Their respective NavigationLinks set them to true. At the end of the game, a "Home" button sets it back to false, and this unwinds the navigation stack back to the root view.
The problem that I am running into is that any update to the score EnvironmentObject unexpectedly and prematurely pops the navigation stack back to the root view.
In the following example code, the only difference between games is that the Red Game button adds +1 to its score environmental variable. In this instance, the one point is added and the navigation link to the final page is executed, but then it immediately rubberbands back to the start. The Blue Game does not update the score environmental variable and transitions as expected.
I welcome any insights on why this occurs. Thanks in advance.
import SwiftUI
class Pop: ObservableObject {
#Published var redStack = false
#Published var blueStack = false
}
class Score: ObservableObject {
#Published var redTotal = 0
#Published var blueTotal = 0
}
struct GameSelection: View {
#ObservedObject var pop = Pop()
#ObservedObject var score = Score()
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: RedStart(), isActive: $pop.redStack) {
Text("Play Red Game") //Tapping this link sets redStacked to true
}.foregroundColor(.red)
Divider()
NavigationLink(destination: BlueStart(), isActive: $pop.blueStack) {
Text("Play Blue Game") //Tapping this link sets blueSteacked to true
}.foregroundColor(.blue)
}
}
.environmentObject(score)
.environmentObject(pop)
}
}
struct RedStart: View {
#State var goToNextView : Bool = false
#EnvironmentObject var score : Score
var body: some View {
VStack {
NavigationLink(destination: RedEnd(), isActive: $goToNextView) {}
Button(action: {
score.redTotal += 1
goToNextView = true
}, label: {
Text("Add 1 Point. Move to Next Screen")
.foregroundColor(.red)
})
}
}
}
struct BlueStart: View {
#State var goToNextView : Bool = false
#EnvironmentObject var score : Score
var body: some View {
VStack {
NavigationLink(destination: BlueEnd(), isActive: $goToNextView) {}
Button(action: {
// score.blueTotal += 1
goToNextView = true
}, label: {
Text("Do NOT add points. Move to Next Screen")
.foregroundColor(.blue)
})
}
}
}
struct RedEnd: View {
#EnvironmentObject var pop: Pop
#EnvironmentObject var score: Score
var body: some View {
Button(action: {
pop.redStack = false
}, label: {
VStack {
Text("Your Score: \(score.redTotal)")
Text("Game Over")
Image(systemName: "house.fill")
}
.foregroundColor(.red)
})
}
}
struct BlueEnd: View {
#EnvironmentObject var pop: Pop
#EnvironmentObject var score: Score
var body: some View {
Button(action: {
pop.blueStack = false
}, label: {
VStack {
Text("Your Score: \(score.blueTotal)")
Text("Game Over")
Image(systemName: "house.fill")
}.foregroundColor(.blue)
})
}
}
struct GameSelection_Previews: PreviewProvider {
static var previews: some View {
GameSelection()
}
}

SwiftUI - Picker value not changing when accessing data from UserDefaults

I am making an app where I am showing different views based of user's selection by a picker. The binding value of the picker is initially set by UserDefaults in a viewModel. The problem is when I choose a picker value in my app, The picker automatically go back to initial state, as if someone forcing the picker not the change the values.
Settings ViewModel :
import Foundation
class SettingsViewModel:ObservableObject{
#Published var showSettings = false
//Here is the problem
#Published var choosenUserType = UserDefaults.standard.string(forKey: "userType"){
didSet{
UserDefaults.standard.set(self.choosenUserType, forKey: "userType")
}
}
static var userTypes = ["Client", "Worker"]
}
Home View:
import SwiftUI
struct HomeView: View {
#StateObject var settingsVM = SettingsViewModel()
var body: some View {
VStack{
switch settingsVM.choosenUserType{
case "Client":
Text("This is client")
case "Worker":
Text("This is worker")
default:
Text("This is default")
}
}.navigationTitle("Tanvirgeek Co")
.navigationBarItems(trailing: Button(action: {
settingsVM.showSettings.toggle()
}, label: {
Text("Settings")
}))
.sheet(isPresented: $settingsVM.showSettings, content: {
SettingsView(dissmiss: $settingsVM.showSettings)
.environmentObject(settingsVM)
})
}
}
Settings View:
import SwiftUI
struct SettingsView: View {
#EnvironmentObject var settingVM:SettingsViewModel
#Binding var dissmiss:Bool
var body: some View {
VStack{
Picker(selection: $settingVM.choosenUserType, label: Text("Choose User Type"), content: {
ForEach(SettingsViewModel.userTypes, id: \.self) { userType in
Text("\(userType)")
}
})
Button(action: {
dissmiss.toggle()
}, label: {
Text("Dismiss")
})
}
}
}
What I am doing wrong? How to change the picker's binding variable value through the picked value here?
Your choosenUserType ends up with an inferred type of String? because that's what UserDefaults.string(forKey:) returns.
The Picker's selection type needs to match exactly with the tag type. The tags (which are inferred in this case as well) are of type String.
I've solved this by giving a default value to choosenUserType so that it can be a String (not String?):
class SettingsViewModel:ObservableObject{
#Published var showSettings = false
#Published var choosenUserType : String = UserDefaults.standard.string(forKey: "userType") ?? SettingsViewModel.userTypes[0] {
didSet{
UserDefaults.standard.set(self.choosenUserType, forKey: "userType")
}
}
static var userTypes = ["Client", "Worker"]
}
Also, in your SettingsView, you don't have to interpolate the userType in the Text -- you can just provide it directly:
struct SettingsView: View {
#EnvironmentObject var settingVM:SettingsViewModel
#Binding var dissmiss:Bool
var body: some View {
VStack{
Picker(selection: $settingVM.choosenUserType, label: Text("Choose User Type")) {
ForEach(SettingsViewModel.userTypes, id: \.self) { userType in
Text(userType)
}
}
Button(action: {
dissmiss.toggle()
}, label: {
Text("Dismiss")
})
}
}
}

SwiftUI: Picker doesn't update text in same view

I have this simple situation:
struct User: Hashable, Identifiable {
var id: Int
var name: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
let bob = User(id: 1, name: "Bob")
let john = User(id: 2, name: "John")
let users = [bob, john]
struct ParentView: View {
#State private var user: User?
var body: some View {
VStack(spacing: 0) {
// Other elements in view...
Divider()
ChildPickerView(user: $user)
Spacer()
}
}
}
struct ChildPickerView: View {
#Binding var user: User?
var body: some View {
HStack {
Text("Selected : \(author?.name ?? "--")")
Picker(selection: $user, label: Text("Select a user")) {
ForEach(0..<users.count) {
Text(users[$0].name)
}
}
}
.padding()
}
}
When I select another value in the picker, the name in the Text() above isn't updated.
However, when tapping the picker again, there is a tick near the selected user, which means the value has actually been selected. Why is the text not updated then?
Thank you for your help
Type of selection and picker content id or tag should be the same. Find below fixed variant.
Tested with Xcode 12.1 / iOS 14.1.
VStack {
Text("Selected : \(user?.name ?? "--")")
Picker(selection: $user, label: Text("Select a user")) {
ForEach(users) {
Text($0.name).tag(Optional($0))
}
}
}