Is there any way to invoke subview's function in SwiftUI? - swiftui

Here is an example:
struct ContentView: View {
var body: some View {
VStack {
SubView()
Button("Invoke"){
...// invoke subview's function
}
}
}
}
struct SubView: View{
var body: some View{
Text("SubView")
}
func changeSubView() {
//...
}
}
This is a simplified example. I don't want to use #State to control or pass function as a parameter.
Is there any other way?

Related

navigate with NavigationStack swiftUI

I am new to SwiftUI framework I am trying to implement NavigationStack. I want to navigate on button action instead of using NavigationLink. The reason behind that is, I need to navigate once a particular function get performed on button action.
struct AView: View {
#State private var actionss = [Int]()
var body: some View {
NavigationStack(path:$actionss) {
VStack{
Button("test") {
actionss.append(0)
}
}
.navigationDestination(for: Int.self) { data in
BView()
}
}
}
}
Above code of "AView" is working fine to navigate "BView". The only thing is I am not able to navigate on "CView" from "BView" without using NavigationLink.
I need to perform particular function before navigate from "BView" to "CView" as well.
Please help me in this.
Thank you in advance.
Assuming that the work is done on BView you can use .navigationDestination as well:
struct AView: View {
#State private var actionss = [Int]()
var body: some View {
NavigationStack(path:$actionss) {
VStack{
Button("show BView") {
actionss.append(0)
}
}
.navigationDestination(for: Int.self) { data in
BView()
}
.navigationTitle("AView")
}
}
}
struct BView: View {
#State var show: Bool = false
var body: some View {
VStack {
Button("show CView") {
show = true
}
}
.navigationDestination(isPresented: $show) {
CView()
}
.navigationTitle("BView")
}
}
struct CView: View {
var body: some View {
Text("Hello")
.navigationTitle("CView")
}
}

SwiftUI, How to Use Programmatic Navigation Using Value, with different NavigationDestinations

I am struggling to get my head around how to use programmatic navigation with multiple destination views which take the same type of value. In the following code I can successfully navigate from ContentView to View2, but would like to navigate from View2 to View3 by adding a value to the path.
The navigationDestination in ContentView has View2 specified. How/where do I add a second navigationDestination to View3? If I add a navigationDestination in View2 pointing to View3 then it doesn't work, as it uses the View1's navigationDestination as it is closer to root. I would appreciate some guidance on how to approach this problem. Many thanks in advance.
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
NavigationLink(value: "view2") {
Text("Go to View2")
}
.navigationDestination(for: String.self) { destination in
View2(someParameterA: destination)
}
.navigationTitle("ContentView")
}
}
}
struct View2: View {
#State var someParameterA: String
var body: some View {
VStack {
Text(someParameterA)
NavigationLink(value: "view3") {
Text("Go to View3")
}
}
.navigationTitle("View 2")
}
}
struct View3: View {
#State var someParameterB: String
var body: some View {
Text(someParameterB)
.navigationTitle("View 3")
}
}
I've managed to hack the following solution together which works but is there a better approach?
enum DestinationView {
case view2
case view3
}
struct NavStruct: Equatable, Hashable {
var destinationView: DestinationView
var param: String
}
class ViewSelector {
#ViewBuilder
static func viewForDestination(_ destination: DestinationView, _ param: String) -> some View {
switch destination {
case .view2:
View2(someParameterA: param)
case .view3:
View3(someParameterB: param)
}
}
}
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
NavigationLink(value: NavStruct(destinationView: .view2, param: "view2")) {
Text("Go to View2")
}
.navigationDestination(for: NavStruct.self) { destination in
ViewSelector.viewForDestination(destination.destinationView, destination.param)
}
.navigationTitle("ContentView")
}
}
}
struct View2: View {
#State var someParameterA: String
var body: some View {
VStack {
Text(someParameterA)
NavigationLink(value: NavStruct(destinationView: .view3, param: "view3")) {
Text("Go to View3")
}
}
.navigationTitle("View 2")
}
}
struct View3: View {
#State var someParameterB: String
var body: some View {
VStack {
Text(someParameterB)
}
.navigationTitle("View 3")
}
}

SwiftUI: Must an ObservableObject be passed into a View as an EnvironmentObject?

If I create an ObservableObject with a #Published property and inject it into a SwifUI view with .environmentObject(), the view responds to changes in the ObservableObject as expected.
class CounterStore: ObservableObject {
#Published private(set) var counter = 0
func increment() {
counter += 1
}
}
struct ContentView: View {
#EnvironmentObject var store: CounterStore
var body: some View {
VStack {
Text("Count: \(store.counter)")
Button(action: { store.increment() }) {
Text("Increment")
}
}
}
}
Tapping on "Increment" will increase the count.
However, if I don't use the EnvironmentObject and instead pass the store instance into the view, the compiler does not complain, the store method increment() is called when the button is tapped, but the count in the View does not update.
struct ContentViewWithStoreAsParameter: View {
var store: CounterStore
var body: some View {
VStack {
Text("Count: \(store.counter) (DOES NOT UPDATE)")
Button(action: { store.increment() }) {
Text("Increment")
}
}
}
}
Here's how I'm calling both Views:
#main
struct testApp: App {
var store = CounterStore()
var body: some Scene {
WindowGroup {
VStack {
ContentView().environmentObject(store) // works
ContentViewWithStoreAsParameter(store: store) // broken
}
}
}
}
Is there a way to pass an ObservableObject into a View as a parameter? (Or what magic is .environmentalObject() doing behind the scenes?)
It should be observed somehow, so next works
struct ContentViewWithStoreAsParameter: View {
#ObservedObject var store: CounterStore
//...
You can pass down your store easily as #StateObject:
#main
struct testApp: App {
#StateObject var store = CounterStore()
var body: some Scene {
WindowGroup {
VStack {
ContentView().environmentObject(store) // works
ContentViewWithStoreAsParameter(store: store) // also works
}
}
}
}
struct ContentViewWithStoreAsParameter: View {
#StateObject var store: CounterStore
var body: some View {
VStack {
Text("Count: \(store.counter)") // now it does update
Button(action: { store.increment() }) {
Text("Increment")
}
}
}
}
However, the store should normally only be available for the views that need it, why this solution would make the most sense in this context:
struct ContentView: View {
#StateObject var store = CounterStore()
var body: some View {
VStack {
Text("Count: \(store.counter)")
Button(action: { store.increment() }) {
Text("Increment")
}
}
}
}

Missing argument for parameter 'View Call' in call

I am struggle with understanding about why i have to give Popup view dependency named vm while calling this view since it is observable
struct ContentView: View {
#State private var showPopup1 = false
var body: some View {
VStack {
Button(action: { withAnimation { self.showPopup1.toggle()}}){
Text("showPopup1") }
Text("title")
DetailView() /// this line shows error
}
}
}
struct DetailView:View {
#ObservedObject var vm:ViewModel
var body : some View {
Text("value from VM")
}
}
class ViewModel: ObservableObject {
#Published var title:String = ""
}
You have to set your vm property when you init your View. Which is the usual way.
struct ContentView: View {
#State private var showPopup1 = false
var body: some View {
VStack {
Button(action: { withAnimation { self.showPopup1.toggle()}}){
Text("showPopup1") }
Text("title")
DetailView(vm: ViewModel()) // Initiate your ViewModel() and pass it as DetailView() parameter
}
}
}
struct DetailView:View {
var vm: ViewModel
var body : some View {
Text("value from VM")
}
}
class ViewModel: ObservableObject {
#Published var title:String = ""
}
Or you could use #EnvironmentObject. You have to pass an .environmentObject(yourObject) to the view where you want to use yourObject, but again you'll have to initialize it before passing it.
I'm not sure it's the good way to do it btw, as an environmentObject can be accessible to all childs view of the view you declared the .environmentObject on, and you usually need one ViewModel for only one View.
struct ContentView: View {
#State private var showPopup1 = false
var body: some View {
VStack {
Button(action: { withAnimation { self.showPopup1.toggle()}}){
Text("showPopup1") }
Text("title")
DetailView().environmentObject(ViewModel()) // Pass your ViewModel() as an environmentObject
}
}
}
struct DetailView:View {
#EnvironmentObject var vm: ViewModel // you can now use your vm, and access it the same say in all childs view of DetailView
var body : some View {
Text("value from VM")
}
}
class ViewModel: ObservableObject {
#Published var title:String = ""
}

How to update a view depending on button click from other view in SwiftUI

I often face this situation but so far I could not find a good solution. Thing is when my SwiftUI View gets bigger I refactor the code by making another struct and call the struct in the respective view. Say I got a struct A and I refactor some code in struct B, but how can I update the view or call a function in struct A depending on button click on struct B ? The below code might help to understand the situation:
import SwiftUI
struct ContentView: View {
#State var myText: String = "Hello World"
#State var isActive: Bool = false
var body: some View {
VStack {
Text(self.myText)
AnotherStruct(isActive: $isActive)
}
.onAppear {
if self.isActive == true {
self.getApi()
}
}
}
func getApi() {
print("getApi called")
self.myText = "Hello Universe"
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct AnotherStruct: View {
#Binding var isActive: Bool
var body: some View {
VStack {
Button( action: {
self.isActive.toggle()
}) {
Text("Button Tapped")
}
}
}
}
Here is a demo of possible approach to solve such cases - with separated responsibility and delegated activity.
struct ContentView: View {
#State var myText: String = "Hello World"
var body: some View {
VStack {
Text(self.myText)
AnotherStruct(onActivate: getApi)
}
}
func getApi() {
print("getApi called")
self.myText = "Hello Universe"
}
}
struct AnotherStruct: View {
let onActivate: () -> ()
// #AppStorage("isActive") var isActive // << possible store in defaults
var body: some View {
VStack {
Button( action: {
// self.isActive = true
self.onActivate()
}) {
Text("Button Tapped")
}
}
// .onAppear {
// if isActive {
// self.onActivate()
// }
// }
}
}