SwiftUI view parameter does not update as expected - ios

I am curious why this .fullScreenCover display of a view does not update properly with a passed-in parameter unless the parameter is using the #Binding property wrapper. Is this a bug or intended behavior? Is this the fact that the view shown by the fullScreenCover is not lazily generated?
import SwiftUI
struct ContentView: View {
#State private var showFullScreen = false
#State private var message = "Initial Message"
var body: some View {
VStack {
Button {
self.message = "new message"
showFullScreen = true
} label: {
Text("Show Full Screen")
}
}.fullScreenCover(isPresented: $showFullScreen) {
TestView(text: message)
}
}
}
struct TestView: View {
var text: String
var body: some View {
Text(text)
}
}

There is a different fullScreenCover for passing in dynamic data, e.g.
import SwiftUI
struct CoverData: Identifiable {
var id: String {
return message
}
let message: String
}
struct FullScreenCoverTestView: View {
#State private var coverData: CoverData?
var body: some View {
VStack {
Button {
coverData = CoverData(message: "new message")
} label: {
Text("Show Full Screen")
}
}
.fullScreenCover(item: $coverData, onDismiss: didDismiss) { item in
TestView(text: item.message)
.onTapGesture {
coverData = nil
}
}
}
func didDismiss() {
// Handle the dismissing action.
}
}
struct TestView: View {
let text: String
var body: some View {
Text(text)
}
}
More info and an example in the docs:
https://developer.apple.com/documentation/SwiftUI/AnyView/fullScreenCover(item:onDismiss:content:)

Related

Supply any string value to swiftUI form in different for textfield

I have two view file. I have textfield. I want to supply string value to the textfield from another view
File 1 :- Place where form is created
struct ContentView: View {
#State var subjectLine: String = ""
var body: some View {
form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine
}
}
}
}
File 2 :- Place where I want to provide value to the string and that will show in the textfield UI
struct CalenderView: View {
#Binding var subjectLine: String
var body : some View {
Button(action: {
subjectLine = "Assigned default value"
}, label: {
Text("Fill textfield")
}
}
})
}
}
This is not working. Any other way we can supply value to the textfield in other view file.
As i can understand you have binding in CalenderView
that means you want to navigate there when you navigate update there.
struct ContentView: View {
#State private var subjectLine: String = ""
#State private var showingSheet: Bool = false
var body: some View {
NavigationView {
Form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine)
}
}
.navigationBarItems(trailing: nextButton)
.sheet(isPresented: $showingSheet) {
CalenderView(subjectLine: $subjectLine)
}
}
}
var nextButton: some View {
Button("Next") {
showingSheet.toggle()
}
}
}
CalendarView
struct CalenderView: View {
#Binding var subjectLine: String
#Environment(\.dismiss) private var dismiss
var body: some View {
Button {
subjectLine = "Assigned default value"
dismiss()
} label: {
Text("Fill textfield")
}
}
}

How to pass value form view to view model in SwiftUI?

I am having one email filed in view, that value I want to pass to viewModel. But I am not sure how.
Email View
import SwiftUI
struct EmailView: View {
#State private var email: String = ""
init(viewModel:EmailViewModel) {
self.viewModel = viewModel
}
TextField("", text: $email)
func sendButtonAction() {
viewModel.updateDataToServer()
}
}
Email Viewmodel
import Foundation
import SwiftUI
class EmailViewModel: NSObject, ObservableObject {
var emailText = ""
convenience init(emailText: String) {
self.init()
self.emailText = emailText
}
func updateDataToServer() {
print("Show email text" + emailText). // not getting email value here???
}
}
I am coming to email screen from other screen. How should I pass email value form here?
NavigationLink(destination: EmailView(viewModel: EmailViewModel(emailText: "")), isActive: $pushToMail) {
EmptyView()
}.hidden()
Try this type of approach as shown in the example code:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#StateObject var emailModel = EmailViewModel()
var body: some View {
NavigationView {
VStack (spacing: 55) {
NavigationLink(destination: EmailView(viewModel: emailModel)) {
Text("go to EmailView")
}
Text("email was: \(emailModel.emailText)")
}
}.navigationViewStyle(.stack)
}
}
class EmailViewModel: ObservableObject {
#Published var emailText = ""
func updateDataToServer() {
print("----> Show email text " + emailText)
}
}
struct EmailView: View {
#ObservedObject var viewModel: EmailViewModel
var body: some View {
TextField("Enter your email", text: $viewModel.emailText).border(.green)
.onSubmit {
viewModel.updateDataToServer()
}
}
}

Removing multiple sheets after swiping down the last one swiftui

I currently have two sheets in a row and I want them to be dismissed to the view that called the first sheet once the last sheet is dismissed by the user. I am open to not pulling up views as sheets its just the way I learned how to easily pull up new views.
BookView is what I want to be returned after the PickDefinition sheet view has been dismissed.
BookView pulls up AddWord as a sheet.
AddWord is pulled up as a sheet and then in it PickDefinition is pulled up as a sheet.
After PickDefinition is dismissed I would like for it to go back to the BookView
struct BookView: View {
#ObservedObject var book: Book
#State var addingWord = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
List(Array(zip(book.words, book.definitions)), id: \.self.0) { (word, definition) in
Text("\(word) - \(definition)")
}
}
.onAppear(perform: {
DB_Manager().openBook(name: book.name, book: self.book)
})
.navigationBarTitle("\(book.name)")
.navigationBarItems(trailing: Button(action: {
self.addingWord = true
}) {
Image(systemName: "plus")
}
).sheet(isPresented: $addingWord) {
AddWord(book: self.book)
}
}
}
struct AddWord: View {
#ObservedObject var book: Book
#StateObject var currentArray = SaveArray()
#State var addingDefinition = false
#State var word = ""
#State var definition = ""
#State var allDefinitions: [String] = []
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Form {
TextField("Word: ", text: $word)
}
.navigationBarTitle("Add word")
.navigationBarItems(trailing: Button("Add") {
if self.word != "" {
book.words.append(self.word)
getDef(self.word, book, currentArray)
addingDefinition = true
}
}).sheet(isPresented: $addingDefinition) {
PickDefinition(definitions: currentArray, book: book, word: self.word)
}
}
}
struct PickDefinition: View {
#ObservedObject var definitions: SaveArray
var book: Book
var word: String
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
VStack {
List {
ForEach(0 ..< definitions.currentArray.count) { index in
Button("\(self.definitions.currentArray[index])", action: {
print("hello")
DB_Manager().addWords(name: self.book.name, word: self.word, definition: self.definitions.currentArray[index])
book.definitions.append(self.definitions.currentArray[index])
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
.navigationTitle("Choose")
}
}
}
You need to pass addingWord into the AddWord view, and then into the PickDefinition view, using Binding. When PickDefinition disappears, set the passed property to false to hide the AddWord sheet.
struct BookView: View {
var body: some View {
// ...
.sheet(isPresented: $addingWord) {
AddWord(book: self.book, presentAddingWord: $addingWord)
}
}
}
struct AddWord: View {
#Binding var presentAddingWord: Bool
// ...
var body: some View {
// ...
.sheet(isPresented: $addingDefinition) {
PickDefinition(/* ... */, presentAddingWord: $presentAddingWord)
}
}
}
struct PickDefinition: View {
#Binding var presentAddingWord: Bool
// ...
var body: some View {
// ...
.onDisappear {
presentAddingWord = false
}
}
}

When a property value is changed in a class via #Published, some of the observedObjects wont get updated?

These are all the codes.
the SurveyQuestion class
class SurveyQuestion: ObservableObject {
#Published var text: String
init() {
print("Initialising now ...")
self.text = "HELLO"
changeText()
}
func changeText() {
print("Changing Text Now from \(text).. ")
if self.text == "HELLO"{
self.text = "BYE"
}
else{
self.text = "HELLO"
}
print("to \(self.text) \n")
}
}
SubView.swift
struct SubView: View {
#ObservedObject var someOtherClass = SurveyQuestion()
var body: some View {
Text("Text now is \(someOtherClass.text)")
}
}
ContentView.swift
struct ContentView: View {
#ObservedObject var someClass = SurveyQuestion()
var body: some View {
VStack{
Button(action: {
print("Changing Text Now !")
self.someClass.changeText()
}) {
Text("Change Text ")
}
Text("Text now is \(someClass.text)")
SubView()
}
}
}
Whenever I click 'changeText' button, it changes the text in Text("Text now is (someClass.text)") but not Subview(). They should all be updated with the same text change.
Any idea what went wrong here?
To make it work you should use same instance of ObservableObject, like below
struct SubView: View {
#ObservedObject var someOtherClass: SurveyQuestion // to be injected
var body: some View {
Text("Text now is \(someOtherClass.text)")
}
}
struct ContentView: View {
#ObservedObject var someClass = SurveyQuestion() // created
var body: some View {
VStack{
Button(action: {
print("Changing Text Now !")
self.someClass.changeText()
}) {
Text("Change Text ")
}
Text("Text now is \(someClass.text)")
SubView(someOtherClass: someClass) // << injected
}
}
}
Your someClass and someOtherClass are two completely different objects. Changing one has no effect on the other. If this was supposed to be a singleton observable that could affect different views simultaneously, you wanted an environment object.

SwiftUI #State not triggering update when value changed by separate method

I have this (simpilied) section of code for a SwiftUI display:
struct ContentView: View {
private var errorMessage: String?
#State private var showErrors: Bool = false
var errorAlert: Alert {
Alert(title: Text("Error!"),
message: Text(errorMessage ?? "oops!"),
dismissButton: .default(Text("Ok")))
}
init() {}
var body: some View {
VStack {
Text("Hello, World!")
Button(action: {
self.showErrors.toggle()
}) {
Text("Do it!")
}
}
.alert(isPresented: $showErrors) { errorAlert }
}
mutating func display(errors: [String]) {
errorMessage = errors.joined(separator: "\n")
showErrors.toggle()
}
}
When the view is displayed and I tape the "Do it!" button then the alert is displayed as expected.
However if I call the display(errors:...) function the error message is set, but the display does not put up an alert.
I'm guessing this is something to do with the button being inside the view and the function being outside, but I'm at a loss as to how to fix it. It should be easy considering the amount of functionality that any app would have that needs to update a display like this.
Ok, some more reading and a refactor switched to using an observable view model like this:
class ContentViewModel: ObservableObject {
var message: String? = nil {
didSet {
displayMessage = message != nil
}
}
#Published var displayMessage: Bool = false
}
struct ContentView: View {
#ObservedObject private var viewModel: ContentViewModel
var errorAlert: Alert {
Alert(title: Text("Error!"), message: Text(viewModel.message ?? "oops!"), dismissButton: .default(Text("Ok")))
}
init(viewModel: ContentViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack {
Button(action: {
self.viewModel.displayMessage.toggle()
}) {
Text("Do it!")
}
}
.alert(isPresented: $viewModel.displayMessage) { errorAlert }
}
}
Which is now working as expected. So the takeaway from this is that using observable view models is more useful even in simpler code like this.

Resources