I have a button that displays a PopUp when pressed, and on the PopUp is a button that is supposed to dismiss the PopUp itself.
I am unsure as to how to use #Binding variable here (if I am correct in assuming that's what I'm supposed to use to communicate between different structs)
struct TESTSTSTSTS: View {
#State var showPopUp = false
var body: some View {
VStack {
Button(action: {
self.showPopUp = true
}) {
Text("Show PopUp Button")
}
Spacer()
if self.showPopUp == true {
PopUp()
}
}
}
}
struct PopUp: View {
var body: some View {
ZStack {
Color.orange
Button(action: {
//Unsure what code to use here.
}) {
Text("Hide PopUp Button")
}
}.frame(width: 300, height: 500, alignment: .center)
}
}
#Binding is indeed a possibility to solve this.
It works like this:
struct ContentView : View {
#State var showPopUp = false
var body: some View {
VStack {
Button(action: {
self.showPopUp = true
}) {
Text("Show PopUp Button")
}
Spacer()
if self.showPopUp == true {
PopUp(showPopUp: $showPopUp)
}
}
}
}
struct PopUp: View {
#Binding var showPopUp: Bool
var body: some View {
ZStack {
Color.orange
Button(action: {
self.showPopUp.toggle()
}) {
Text("Hide PopUp Button")
}
}.frame(width: 300, height: 500, alignment: .center)
}
}
Related
I am trying to push from login view to detail view but not able to make it.even navigation bar is not showing in login view. How to push on button click in SwiftUI? How to use NavigationLink on button click?
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Let's get you signed in.")
.bold()
.font(.system(size: 40))
.multilineTextAlignment(.leading)
.frame(width: 300, height: 100, alignment: .topLeading)
.padding(Edge.Set.bottom, 50)
Text("Email address:")
.font(.headline)
TextField("Email", text: $email)
.frame(height:44)
.accentColor(Color.white)
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
Text("Password:")
.font(.headline)
SecureField("Password", text: $password)
.frame(height:44)
.accentColor(Color.white)
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
Button(action: {
print("login tapped")
}) {
HStack {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
.accentColor(Color.black)
.padding()
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
.padding(Edge.Set.vertical, 20)
}
.padding(.horizontal,30)
}
.navigationBarTitle(Text("Login"))
}
To fix your issue you need to bind and manage tag with NavigationLink, So create one state inside you view as follow, just add above body.
#State var selection: Int? = nil
Then update your button code as follow to add NavigationLink
NavigationLink(destination: Text("Test"), tag: 1, selection: $selection) {
Button(action: {
print("login tapped")
self.selection = 1
}) {
HStack {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
.accentColor(Color.black)
.padding()
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
.padding(Edge.Set.vertical, 20)
}
Meaning is, when selection and NavigationLink tag value will match then navigation will be occurs.
I hope this will help you.
iOS 16+
Note: Below is a simplified example of how to present a new view. For a more advanced generic example please see this answer.
In iOS 16 we can access the NavigationStack and NavigationPath.
Usage #1
A new view is activated by a simple NavigationLink:
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink(value: "NewView") {
Text("Show NewView")
}
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
}
}
Usage #2
A new view is activated by a standard Button:
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button {
path.append("NewView")
} label: {
Text("Show NewView")
}
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
}
}
Usage #3
A new view is activated programmatically:
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Text("Content View")
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
.onAppear {
path.append("NewView")
}
}
}
iOS 13+
The accepted answer uses NavigationLink(destination:tag:selection:) which is correct.
However, for a simple view with just one NavigationLink you can use a simpler variant: NavigationLink(destination:isActive:)
Usage #1
NavigationLink is activated by a standard Button:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
Button(action: {
self.isLinkActive = true
}) {
Text("Login")
}
}
}
.navigationBarTitle(Text("Login"))
}
}
}
Usage #2
NavigationLink is hidden and activated by a standard Button:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
Button(action: {
self.isLinkActive = true
}) {
Text("Login")
}
}
.navigationBarTitle(Text("Login"))
.background(
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
}
}
Usage #3
NavigationLink is hidden and activated programmatically:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
}
.navigationBarTitle(Text("Login"))
.background(
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
.onAppear {
self.isLinkActive = true
}
}
}
Here is a GitHub repository with different SwiftUI extensions that makes navigation easier.
Another approach:
SceneDelegate
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: BaseView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
BaseView
import SwiftUI
struct BaseView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "view1" {
FirstView()
} else if viewRouter.currentPage == "view2" {
SecondView()
.transition(.scale)
}
}
}
}
#if DEBUG
struct MotherView_Previews : PreviewProvider {
static var previews: some View {
BaseView().environmentObject(ViewRouter())
}
}
#endif
ViewRouter
import Foundation
import Combine
import SwiftUI
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter,Never>()
var currentPage: String = "view1" {
didSet {
withAnimation() {
objectWillChange.send(self)
}
}
}
}
FirstView
import SwiftUI
struct FirstView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Button(action: {self.viewRouter.currentPage = "view2"}) {
NextButtonContent()
}
}
}
}
#if DEBUG
struct FirstView_Previews : PreviewProvider {
static var previews: some View {
FirstView().environmentObject(ViewRouter())
}
}
#endif
struct NextButtonContent : View {
var body: some View {
return Text("Next")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.blue)
.cornerRadius(15)
.padding(.top, 50)
}
}
SecondView
import SwiftUI
struct SecondView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Spacer(minLength: 50.0)
Button(action: {self.viewRouter.currentPage = "view1"}) {
BackButtonContent()
}
}
}
}
#if DEBUG
struct SecondView_Previews : PreviewProvider {
static var previews: some View {
SecondView().environmentObject(ViewRouter())
}
}
#endif
struct BackButtonContent : View {
var body: some View {
return Text("Back")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.blue)
.cornerRadius(15)
.padding(.top, 50)
}
}
Hope this helps!
Simplest and most effective solution is :
NavigationLink(destination:ScoresTableView()) {
Text("Scores")
}.navigationBarHidden(true)
.frame(width: 90, height: 45, alignment: .center)
.foregroundColor(.white)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(10)
.contentShape(Rectangle())
.padding(EdgeInsets(top: 16, leading: UIScreen.main.bounds.size.width - 110 , bottom: 16, trailing: 20))
ScoresTableView is the destination view.
In my opinion a cleaner way for iOS 16+ is using a state bool to present the view.
struct ButtonNavigationView: View {
#State private var isShowingSecondView : Bool = false
var body: some View {
NavigationStack {
VStack{
Button(action:{isShowingSecondView = true} ){
Text("Show second view")
}
}.navigationDestination(isPresented: $isShowingSecondView) {
Text("SecondView")
}
}
}
}
I think above answers are nice, but simpler way should be:
NavigationLink {
TargetView()
} label: {
Text("Click to go")
}
Is it possible to overlay something on top of an inline nav bar? Here's an example with a popup where you can display and alert and then tap outside the alert to dismiss it.
I'd like the dark background overlay to also cover the nav bar. This works fine for the default large text style nav bar, but when I change it to an inline nav bar, the dark background no longer covers the nav. Is there a workaround for this?
import SwiftUI
struct ContentView: View {
#State private var isPresented = false
var body: some View {
NavigationView {
ZStack {
Button(action: {
isPresented = true
}) {
Text("Show popup")
}
if isPresented {
ZStack {
Rectangle()
.foregroundColor(Color.black.opacity(0.5))
.edgesIgnoringSafeArea(.all)
.onTapGesture {
isPresented = false
}
Rectangle()
.foregroundColor(Color.red)
.frame(width: 300, height: 100)
.onTapGesture {
isPresented = true
}
Text("Alert!")
}
}
}
.navigationBarTitle("Hello", displayMode: .inline)
}
}
}
Wrapped NavigationView inside the ZStack.
struct ContentView: View {
#State private var isPresented = false
var body: some View {
ZStack { // < -- Here
NavigationView {
ZStack {
Button(action: {
isPresented = true
}) {
Text("Show popup")
}
}
.navigationBarTitle("Hello", displayMode: .inline)
}
if isPresented {
ZStack {
Rectangle()
.foregroundColor(Color.black.opacity(0.5))
.edgesIgnoringSafeArea(.all)
.onTapGesture {
isPresented = false
}
Rectangle()
.foregroundColor(Color.red)
.frame(width: 300, height: 100)
.onTapGesture {
isPresented = true
}
Text("Alert!")
}
}
}
}
}
Another way to use overlay.
struct ContentView: View {
#State private var isPresented = false
var body: some View {
NavigationView {
ZStack {
Button(action: {
isPresented = true
}) {
Text("Show popup")
}
}
.navigationBarTitle("Hello", displayMode: .inline)
}.overlay( //<--- Here
alertView
)
}
#ViewBuilder
private var alertView: some View {
if isPresented {
ZStack {
Rectangle()
.foregroundColor(Color.black.opacity(0.5))
.edgesIgnoringSafeArea(.all)
.onTapGesture {
isPresented = false
}
Rectangle()
.foregroundColor(Color.red)
.frame(width: 300, height: 100)
.onTapGesture {
isPresented = true
}
Text("Alert!")
}
}
}
}
Hello, I want to navigate between windows using a button but not use a NavigationLink. It looks ugly.
this is my code
import SwiftUI
struct ContentView: View {
var body: some View {
Button(action: action()){
Text("Hola")
.font(.largeTitle)
.frame(width: 100, height: 100)
.background(Color.red)
}
}
}
You can use an empty NavigationLink and bind your navigation flag or destination to your Button.
struct FirstView: View {
#State var navigationFlag = false
var body: some View {
NavigationView {
VStack {
Text("First View")
Button(action: {
self.navigationFlag = true
}, label: {
Text("navigate")
})
NavigationLink(destination: SecondView(),
isActive: self.$navigationFlag,
label: {
EmptyView()
})
}
}
}
}
struct SecondView: View {
var body: some View {
Text("Second View")
}
}
I made a SearchBarView view to use in various other views (for clarity, I removed all the layout modifiers, such as color and padding):
struct SearchBarView: View {
#Binding var text: String
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search…", text: $text, onCommit: didPressReturn)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
}
}
}
)
}
func didPressReturn() {
print("did press return")
}
}
It looks and works great to filter data in a List.
But now I'd like to use the SearchBarView to search an external database.
struct SearchDatabaseView: View {
#Binding var isPresented: Bool
#State var searchText: String = ""
var body: some View {
NavigationView {
VStack {
SearchBarView(text: $searchText)
// need something here to respond to onCommit and initiate a network call.
}
.navigationBarTitle("Search...")
.navigationBarItems(trailing:
Button(action: { self.isPresented = false }) {
Text("Done")
})
}
}
}
For this, I only want to start the network access when the user hits return. So I added the onCommit part to SearchBarView, and the didPressReturn() function is indeed only called when tapping return. So far, so good.
What I don't understand is how SearchDatabaseView that contains the SearchBarView can respond to onCommit and initiate the database searh - how do I do that?
Here is possible approach
struct SearchBarView: View {
#Binding var text: String
var onCommit: () -> () = {} // inject callback
#State private var isEditing = false
var body: some View {
HStack {
TextField("Search…", text: $text, onCommit: didPressReturn)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
}
}
}
)
}
func didPressReturn() {
print("did press return")
// do internal things...
self.onCommit() // << external callback
}
}
so now in SearchDatabaseView you can
VStack {
SearchBarView(text: $searchText) {
// do needed things here ...
}
}
NavigationBarItem can't Click after dismiss view!
XCode11 beta3,
MacOS Catalina 10.15 Beta(19A501i)
When click DetailView button to dismiss by #Binding,
ContentView's navigationBarItem will disabled(Can't Click)!
But scroll down to dismiss will be fine(can click and will be print "Clicked!" in Debug Preview Mode)
struct DetailView: View {
#Binding var isPresented: Bool
var body: some View {
Group {
Text("Detail")
Button(action: {
self.isPresented.toggle()
}) {
Text("Dismiss")
}
}
}
}
struct ContentView : View {
#State var isPresented = false
var body: some View {
NavigationView{
Button(action: {self.isPresented.toggle()}){
Text("Show")
}
.presentation(!isPresented ? nil :
Modal(DetailView(isPresented: $isPresented)) {
print("dismissed")
}
)
.navigationBarTitle(Text("Test"))
.navigationBarItems(trailing:
Button(action: {print("Clicked!")} ) {
Image(systemName: "plus")
.frame(width: 44, height: 44)
.foregroundColor(.black)
.cornerRadius(22)
}
.padding(.trailing)
)
}
}
}
I'm inclined to think that there is a bug with modals. The onDismiss is never called when the modal goes away. However, I did found a workaround. Instead of dismissing by setting the isPresented variable from inside the modal view, I use the rootViewController from the main window, to call the UIKit dismiss method.
By dismissing the modal this way, the onDismiss closure is called properly, and it is there where I set isPresented = false, so the modal can be presented again.
The following code works, at least until a new version fixes the problem:
import SwiftUI
struct DetailView: View {
var body: some View {
Group {
Text("Detail")
Button(action: {
UIApplication.shared.windows[0].rootViewController?.dismiss(animated: true, completion: { })
}) {
Text("Dismiss")
}
}
}
}
struct ContentView : View {
#State var isPresented = false
var body: some View {
NavigationView{
Button(action: {self.isPresented.toggle()}){
Text("Show")
}
.presentation(!isPresented ? nil :
Modal(DetailView()) {
self.isPresented = false
print("dismissed")
}
)
.navigationBarTitle(Text("Test"))
.navigationBarItems(trailing:
Button(action: {print("Clicked!")} ) {
Image(systemName: "plus")
.frame(width: 44, height: 44)
.foregroundColor(.black)
.cornerRadius(22)
}
.padding(.trailing)
)
}
}
}