Pass an #State variable to ContentView - binding

Building my first SwiftUI app and I'm stuck on pass #State var to the ContentView. I have declared the #State variable in a struct, with an #Binding tag on the variable in the ContentView.
My intent is for multiple instances of NumberBlock to be called in ContentView, and be able to reset all of them to false (hide all of the images) with one button.
The new "App" struct that was added in Xcode 12 is giving an error for a missing parameter. I've tried everything I can think of to enter a parameter, but nothing seems to work. I was able to eliminate the error by using .constant(true), but that did not give me the functionality I need, which is to toggle the variable from the ContentView.
I appreciate any help eliminating the error or correcting my shallow understanding of #State and #Binding.
Here is where I create the #State reset_x var
import SwiftUI
struct NumberBlock: View {
#State var reset_x: Bool = true
#Binding var reset: Bool
var body: some View {
ZStack {
Text("test")
.onTapGesture(count: 1, perform: {
self.reset_x = false
})
Image("XMark")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50, alignment: .center)
.onTapGesture(count: 1, perform: {
self.reset_x = true
print("reset_x is \(self.reset_x)")
})
.isHidden(reset_x ? true : false)
.isHidden(reset ? true : false)
}
}
}
The error occurs in this view:
import SwiftUI
#main
struct Quixx2App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Here is where I want to use the #Binding
import SwiftUI
struct ContentView: View {
#State var reset: Bool = false
#Binding var reset_x: Bool
var body: some View {
VStack {
HStack {
NumberBlock(reset: self.$reset)
NumberBlock(reset: self.$reset)
}
Button("Reset Score"){
self.scoreKeeper.redScore = 0
self.reset_x = false //this line is not doing anything
print("reset_x is \(self.reset_x)")
}
}
}
}
And the .isHidden extension
import Foundation
import SwiftUI
extension View {
#ViewBuilder func isHidden(_ hidden: Bool, remove: Bool = false) -> some View {
if hidden {
if !remove {
self.hidden()
}
} else {
self
}
}
}

So let's start with your NumberBlock view. You need one state within the View, which preserves if the image is hidden or not. By tapping the Text View, the the value is toggled and the view is rendered accordingly.
struct NumberBlock: View {
#State private var imageIsHidden: Bool
var body: some View {
VStack {
Text("Show")
.onTapGesture(count: 1, perform: {
self.imageIsHidden = false
})
Text("Image")
.onTapGesture(count: 1, perform: {
self.imageIsHidden = true
})
.isHidden(imageIsHidden ? true : false)
}
}
}
Now, you want a second feature: The ContentView should control this state. So you have to extract this state from the child view (NumberBlock) to the parent view (ContentView). By changing the property from #State to #Binding you are basically telling the child view that this data is being passed from a parent view.
My working example:
import SwiftUI
import Foundation
extension View {
#ViewBuilder func isHidden(_ hidden: Bool, remove: Bool = false) -> some View {
if hidden {
if !remove {
self.hidden()
}
} else {
self
}
}
}
struct NumberBlock: View {
#Binding var imageIsHidden: Bool
var body: some View {
VStack {
Text("Show")
.onTapGesture(count: 1, perform: {
self.imageIsHidden = false
})
Text("Image")
.onTapGesture(count: 1, perform: {
self.imageIsHidden = true
})
.isHidden(imageIsHidden ? true : false)
}
}
}
struct ContentView: View {
#State var imageOfBlock1IsHidden: Bool = true
#State var imageOfBlock2IsHidden: Bool = true
var body: some View {
VStack {
HStack {
NumberBlock(imageIsHidden: self.$imageOfBlock1IsHidden)
NumberBlock(imageIsHidden: self.$imageOfBlock2IsHidden)
}
Button("Reset Score") {
self.imageOfBlock1IsHidden = true
self.imageOfBlock2IsHidden = true
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

Can't go back to InitialView with Modal transition

I'm a beginner of SwiftUI. (Sorry in advance if my English is hard to understand.)
I want to enable multiple screen transitions using Modal.
【Go back to the initial view when the view reached to the last View and pressed the button】
That's what I want to realize.
I thought my code would work perfectly but when I pressed the button of last view it stopped at the second view, not the initial view.
Can't figure out what's wrong and where to fix, any solutions?
Here's my code.
`
import SwiftUI
struct ContentView: View {
#State var isShowSecondView = false
var body: some View {
Button("To SecondView") {
isShowSecondView = true
}
.font(.largeTitle)
.fullScreenCover(isPresented: $isShowSecondView) {
SecondView(isShowSecondView: $isShowSecondView)
}
}
}
struct SecondView: View {
#Binding var isShowSecondView: Bool
#State var isShowThirdView = false
var body: some View {
Button("To ThirdView") {
isShowThirdView = true
}
.font(.largeTitle)
.fullScreenCover(isPresented: $isShowThirdView) {
ThirdView(isShowNextView: $isShowSecondView,
isShowThirdView: $isShowThirdView)
}
}
}
struct ThirdView: View {
#Binding var isShowNextView: Bool
#Binding var isShowThirdView: Bool
#State var isShowForthView = false
var body: some View {
Button("To ForthView") {
isShowForthView = true
}
.font(.largeTitle)
.fullScreenCover(isPresented: $isShowForthView) {
ForthView(isShowNextView: $isShowNextView,
isShowThirdView: $isShowThirdView, isShowForthView: $isShowForthView)
}
}
}
struct ForthView: View {
#Binding var isShowNextView: Bool
#Binding var isShowThirdView: Bool
#Binding var isShowForthView: Bool
var body: some View {
Button("Back to FirstView") {
isShowNextView = false
isShowThirdView = false
isShowForthView = false
}
.font(.largeTitle)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
`

#Environment(\.presentationMode) reloads body many times in SwiftUI

Set property wrapper to pop view "#Environment(.presentationMode) var presentationMode" but it is reloading view again and again. Why it is happening? any solution?
What i have observed except #Environment(.presentationMode) we should use bool value binding with controller, Please check code:
struct newView: View {
#State private var isActiveView: Bool = false
var body: some View {
VStack(alignment: .center) {
NavigationLink(destination: PushedView(isActiveView: $isActiveView), isActive: $isActiveView) {
Text("Create Account")
}
}
}
}
And on pushed screen check the code:
`struct PushedView: View {
#Binding var isActiveView: Bool
var body: some View {
NavigationView {
VStack {
Text(“Back”).onTapGesture {
isActiveView = false // to pop into previous view
}
}
}
}
}
`

In Swiftui How to automatically close modal with rotation to landscape

I currently use an landscape environmentobject based on this code - https://stackoverflow.com/a/58503841/412154
Within my view I have modals that appear and disappear appropriately using #State/#Binding depending on a "Done" Button press. My app does show a different view when rotated to landscape and I would like for the modal to dismiss automatically on the rotation, but couldn't figure out how to change the #binding var based on another #ennvironmentobject
Here is a simplified sample of my Modal View
struct StepsView: View {
#Binding var isPresented:Bool
#EnvironmentObject var orientation:Orientation
var body: some View {
VStack(alignment: .center) {
Text("Step")
}
.navigationBarItems(trailing: Button(action: {
//print("Dismissing steps view...")
self.isPresented = false
}) {
Text("Done").bold()
})
}
thanks for any help!
Appreciate #davidev's answer but I wanted each Modal to act a little differently so I did it this way
struct StepsView: View {
#Binding var isPresented:Bool
#EnvironmentObject var orientation:Orientation
private var PortraitView:some View {
VStack(alignment: .center) {
Text("Modal")
}
.navigationBarItems(trailing: Button(action: {
self.isPresented = false
}) {
Text("Done").bold()
})
}
var body: some View {
buildView(isLandscape: orientation.isLandScape, isShowing: &isPresented)
}
func buildView(isLandscape:Bool, isShowing:inout Bool) -> AnyView {
if !isLandscape {
return AnyView(PortraitView)
} else {
isShowing = false
return AnyView(EmptyView())
}
}
Here is a possible approach with extending the Device class with a variable which keeps track of the opened modal View.
import Combine
final class Orientation: ObservableObject {
#Published var isLandscape: Bool = false {
willSet {
objectWillChange.send()
if (newValue)
{
self.showModal = false
}
}
}
#Published var showModal : Bool = false
}
Whenever landscape changes, and the orientation is landscape, showModal will be set to false.
Here the ContentView..
struct ContentView6: View {
#EnvironmentObject var orientation:Orientation
// 1.
#State private var showModal = false
var body: some View {
Button("Show Modal") {
// 2.
self.orientation.isLandscape.toggle()
// 3.
}.sheet(isPresented: self.$orientation.isLandscape) {
ModalView(isPresented: self.$orientation.isLandscape).environmentObject(self.orientation)
}
}
}

How to change a #State variable from a different struct

I am new to swift UI and swift in general, and I was wondering how to change a variable from a different struct. In this case, I need to change the logged in boolean in a content view from this section in a different view. Basic explanations would be appreciated. Thanks!
Button(action: {
if (checkKey(testKey: self.key)) {
//HERE
}
}) {
Text("Submit")
.padding()
.background(Color.init(.sRGB, red: 0.01, green: 0.01, blue: 0.01, opacity: 0.05))
.cornerRadius(10)
}
And this is the Content View. I need to change the #State bool
struct ContentView: View {
#State public var loggedin: Bool = false
var body: some View {
NavigationView {
if (loggedin) {
MainView()
} else {
// Not Logged In
LoginScreen()
}
}
}
}
here is a full code you can pass your #State public var loggedin: Bool variable to another struct using #Binding var loggedin: Bool and chnage value
struct ContentView: View {
#State public var loggedin: Bool = false
var body: some View {
NavigationView {
if (loggedin) {
MainView(loggedin: $loggedin)
} else {
// Not Logged In
LoginScreen(loggedin: $loggedin)
}
}
}
}
struct MainView: View {
#Binding var loggedin: Bool
var body: some View {
VStack {
Text("")
Button(action: {
self.loggedin = false
}, label: {
Text("Chnage loggedin value")
})
}
}
}
#Binding var loggedin: Bool
in your login screen view. If you only use the login here its fine like this, if you are using it across the app you might want to look into #EnvironmentObject.

iOS SwiftUI: pop or dismiss view programmatically

I couldn't find any reference about any ways to make a pop or a dismiss programmatically of my presented view with SwiftUI.
Seems to me that the only way is to use the already integrated slide dow action for the modal(and what/how if I want to disable this feature?), and the back button for the navigation stack.
Does anyone know a solution?
Do you know if this is a bug or it will stays like this?
This example uses the new environment var documented in the Beta 5 Release Notes, which was using a value property. It was changed in a later beta to use a wrappedValue property. This example is now current for the GM version. This exact same concept works to dismiss Modal views presented with the .sheet modifier.
import SwiftUI
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(
"Here is Detail View. Tap to go back.",
action: { self.presentationMode.wrappedValue.dismiss() }
)
}
}
struct RootView: View {
var body: some View {
VStack {
NavigationLink(destination: DetailView())
{ Text("I am Root. Tap for Detail View.") }
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
RootView()
}
}
}
SwiftUI Xcode Beta 5
First, declare the #Environment which has a dismiss method which you can use anywhere to dismiss the view.
import SwiftUI
struct GameView: View {
#Environment(\.presentationMode) var presentation
var body: some View {
Button("Done") {
self.presentation.wrappedValue.dismiss()
}
}
}
iOS 15+
Starting from iOS 15 we can use a new #Environment(\.dismiss):
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
(There's no more need to use presentationMode.wrappedValue.dismiss().)
Useful links:
DismissAction
There is now a way to programmatically pop in a NavigationView, if you would like. This is in beta 5. Notice that you don't need the back button. You could programmatically trigger the showSelf property in the DetailView any way you like. And you don't have to display the "Push" text in the master. That could be an EmptyView(), thereby creating an invisible segue.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
MasterView()
}
}
}
struct MasterView: View {
#State private var showDetail = false
var body: some View {
VStack {
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
Text("Push")
}
}
}
}
struct DetailView: View {
#Binding var showSelf: Bool
var body: some View {
Button(action: {
self.showSelf = false
}) {
Text("Pop")
}
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
I recently created an open source project called swiftui-navigation-stack (https://github.com/biobeats/swiftui-navigation-stack) that contains the NavigationStackView, an alternative navigation stack for SwiftUI. It offers several features described in the readme of the repo. For example, you can easily push and pop views programmatically. I'll show you how to do that with a simple example:
First of all embed your hierarchy in a NavigationStackVew:
struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}
NavigationStackView gives your hierarchy access to a useful environment object called NavigationStack. You can use it to, for instance, pop views programmatically as asked in the question above:
struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()
PushView(destination: View2()) {
Text("PUSH TO VIEW 2")
}
}
}
}
}
struct View2: View {
#EnvironmentObject var navStack: NavigationStack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()
Button(action: {
self.navStack.pop()
}, label: {
Text("PROGRAMMATICALLY POP TO VIEW 1")
})
}
}
}
}
In this example I use the PushView to trigger the push navigation with a tap. Then, in the View2 I use the environment object to programmatically come back.
Here is the complete example:
import SwiftUI
import NavigationStack
struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}
struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()
PushView(destination: View2()) {
Text("PUSH TO VIEW 2")
}
}
}
}
}
struct View2: View {
#EnvironmentObject var navStack: NavigationStack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()
Button(action: {
self.navStack.pop()
}, label: {
Text("PROGRAMMATICALLY POP TO VIEW 1")
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
RootView()
}
}
the result is:
Alternatively, if you don't want to do it programatically from a button, you can emit from the view model whenever you need to pop.
Subscribe to a #Published that changes the value whenever the saving is done.
struct ContentView: View {
#ObservedObject var viewModel: ContentViewModel
#Environment(\.presentationMode) var presentationMode
init(viewModel: ContentViewModel) {
self.viewModel = viewModel
}
var body: some View {
Form {
TextField("Name", text: $viewModel.name)
.textContentType(.name)
}
.onAppear {
self.viewModel.cancellable = self.viewModel
.$saved
.sink(receiveValue: { saved in
guard saved else { return }
self.presentationMode.wrappedValue.dismiss()
}
)
}
}
}
class ContentViewModel: ObservableObject {
#Published var saved = false // This can store any value.
#Published var name = ""
var cancellable: AnyCancellable? // You can use a cancellable set if you have multiple observers.
func onSave() {
// Do the save.
// Emit the new value.
saved = true
}
}
Please check Following Code it's so simple.
FirstView
struct StartUpVC: View {
#State var selection: Int? = nil
var body: some View {
NavigationView{
NavigationLink(destination: LoginView().hiddenNavigationBarStyle(), tag: 1, selection: $selection) {
Button(action: {
print("Signup tapped")
self.selection = 1
}) {
HStack {
Spacer()
Text("Sign up")
Spacer()
}
}
}
}
}
SecondView
struct LoginView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView{
Button(action: {
print("Login tapped")
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("Back")
.resizable()
.frame(width: 20, height: 20)
.padding(.leading, 20)
}
}
}
}
}
You can try using a custom view and a Transition.
Here's a custom modal.
struct ModalView<Content>: View where Content: View {
#Binding var isShowing: Bool
var content: () -> Content
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
if (!self.isShowing) {
self.content()
}
if (self.isShowing) {
self.content()
.disabled(true)
.blur(radius: 3)
VStack {
Text("Modal")
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.transition(.moveAndFade) // associated transition to the modal view
}
}
}
}
}
I reused the Transition.moveAndFade from the Animation Views and Transition tutorial.
It is defined like this:
extension AnyTransition {
static var moveAndFade: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale()
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
You can test it - in the simulator, not in the preview - like this:
struct ContentView: View {
#State var isShowingModal: Bool = false
func toggleModal() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
self.isShowingModal = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
self.isShowingModal = false
}
}
}
}
var body: some View {
ModalView(isShowing: $isShowingModal) {
NavigationView {
List(["1", "2", "3", "4", "5"].identified(by: \.self)) { row in
Text(row)
}.navigationBarTitle(Text("A List"), displayMode: .large)
}.onAppear { self.toggleModal() }
}
}
}
Thanks to that transition, you will see the modal sliding in from the trailing edge, and the it will zoom and fade out when it is dismissed.
The core concept of SwiftUI is to watch over the data flow.
You have to use a #State variable and mutate the value of this variable to control popping and dismissal.
struct MyView: View {
#State
var showsUp = false
var body: some View {
Button(action: { self.showsUp.toggle() }) {
Text("Pop")
}
.presentation(
showsUp ? Modal(
Button(action: { self.showsUp.toggle() }) {
Text("Dismiss")
}
) : nil
)
}
}
I experienced a compiler issue trying to call value on the presentationMode binding. Changing the property to wrappedValue fixed the issue for me. I'm assuming value -> wrappedValue is a language update. I think this note would be more appropriate as a comment on Chuck H's answer but don't have enough rep points to comment, I also suggested this change as and edit but my edit was rejected as being more appropriate as a comment or answer.
This will also dismiss the view
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.rootViewController?.dismiss(animated: true, completion: {
print("dismissed")
})

Resources