I am trying to pass data (incomeAmount) from the First View to the Second View in my SwiftUI app, but I need to declare the BindingString. What does it mean that I need to declare the BindingString?
View 1
struct ContentView: View {
#State var incomeAmount = ""
var body: some View {
NavigationView {
VStack {
TextField("Your income amount", text: $incomeAmount)
.frame(width: 300)
.padding(.bottom, 30)
NavigationLink(destination: NewView()){
Text("Continue")
.frame(width: 300, height: 50, alignment: .center)
.background(Color.black)
.cornerRadius(130)
.foregroundColor(.white)
.padding(.bottom, 30)
}
Text("$\(incomeAmount)")
}.navigationTitle("First View")
}
}
}
View 2
struct NewView: View {
#Binding var incomeAmount: String
var body: some View {
NavigationView {
VStack {
Text("$\(incomeAmount)")
}
}.navigationTitle("Second View")
}
}
struct NewView_Previews: PreviewProvider {
static var previews: some View {
NewView(incomeAmount: <#Binding<String>#>)
}
}
To pass the incomeAmount to the second view, you have to do this:
NavigationLink(destination: NewView(incomeAmount: $incomeAmount)) { ... }
Also, you need to provide it to:
struct NewView_Previews: PreviewProvider {
static var previews: some View {
NewView(incomeAmount: .constant("something here"))
}
}
Because in PreviewProvider, NewView expect to have a Binding<String> passed in. So you need to provide it.
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")
}
I would like to open new View after clicking on a image button in SwiftUI, any idea?
Button(action: {}) {
Image("gift")
Text("Send")
.padding(.horizontal)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
If you want it in a NavigationView, you can use a NavigationLink instead of a Button:
struct ContentView : View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView()) {
Image("gift")
Text("Send")
.padding(.horizontal)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
}
}
}
}
struct DetailView : View {
var body: some View {
Text("Detail")
}
}
If you want a sheet presented, you can use the sheet modifier and a #State variable:
struct ContentView : View {
#State private var sheetPresented = false
var body: some View {
Button(action: {
sheetPresented = true
}) {
Image("gift")
Text("Send")
.padding(.horizontal)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
.sheet(isPresented: $sheetPresented) {
DetailView()
}
}
}
struct DetailView : View {
var body: some View {
Text("Detail")
}
}
How can I preview this button who needs a PresentationMode to get constructed?
The button works well the main view that contains it creates it with an environment PresentationMode object declared as:
#Environment(\.presentationMode) var presentationMode:Binding<PresentationMode>
struct BackButton: View {
#Binding var presentationMode: PresentationMode
var color: Color
var body: some View {
Button(action: {
self.$presentationMode.wrappedValue.dismiss()
}, label: { Image(systemName: "chevron.left")
.scaleEffect(1.3)
.foregroundColor(color)
.offset(x: -17)
.frame(width: 43, height: 43)
}
)
}
}
struct BackButton_Previews: PreviewProvider {
static var previews: some View {
let pres = PresentationMode()
return BackButton(presentationMode: pres, color: .black) // Compiler Error: PresentationMode cannot be constructed because it has no accessible initializers
}
}
I think PresentationMode should be declared as Environment Variable.
So declare it like this..
#Environment(\.presentationMode) var presentationMode
and then change it on action like that, as it is not a Binding anymore.
Button(action: {
self.presentationMode.wrappedValue.dismiss()
Edit:
Here is a working example/ with preview for BackButton View and how to use PresentationMode.
struct MainView: View {
var body: some View {
NavigationView
{
VStack()
{
Text("Hello World")
NavigationLink("Go to Detail View", destination: BackButton(color: .black))
}.navigationBarTitle(Text("Main View"))
}
}
}
struct BackButton : View
{
//Environment variable here
#Environment(\.presentationMode) var presentationMode
var color: Color
var body: some View
{
Button(action: {
//Dismiss the View
self.presentationMode.wrappedValue.dismiss()
}, label: { Image(systemName: "chevron.left")
.scaleEffect(1.3)
.foregroundColor(color)
.offset(x: -17)
.frame(width: 43, height: 43)
})
}
}
struct BackButton_Previews: PreviewProvider {
static var previews: some View {
//Preview here is working, no need to pass environment variable
//Going back from this view in Preview won't work
BackButton(color: .black)
}
}
I have a view that is triggered by a button touch. It appears nicely, all good. Now I want the View to disappear automatically again after a few seconds.
The view should disappear automatically without having to hit the button again.
Below my test project
import SwiftUI
struct ContentView: View {
#State private var presentClipboardView = false
#State private var scale: CGFloat = 1.0
var body: some View {
VStack{
Button(action: {
let pasteboard = UIPasteboard()
pasteboard.string = "http://I_AM_A_URL.com"
withAnimation(.easeInOut(duration: 2)) {
self.presentClipboardView.toggle()
}
}, label: {
HStack {
Image(systemName: "list.dash")
.padding(.trailing)
VStack(alignment: .leading) {
Text("Open URL")
.font(.headline)
}
Spacer()
}
}
)
if(self.presentClipboardView){
LabelView()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct LabelView: View {
var body: some View {
Text("URL copied to clipboard!")
.padding(10)
.font(.title)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 8).fill(Color.green).shadow(color: .gray, radius: 3))
}
}
Try this on LabelView()
LabelView().onAppear {
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { timer in
withAnimation(.easeInOut(duration: 2)) {
self.presentClipboardView.toggle()
}
}
}
lets try
import SwiftUI
struct ContentView: View {
#State var flag = false
let time = 3.0
var body: some View {
VStack {
if flag {
DetailView(flag: $flag, showTime: time)
}
Button(action: {
self.flag.toggle()
}) {
Text("show for \(time.description) seconds")
}.disabled(flag)
}
}
}
struct DetailView: View {
#Binding var flag: Bool
let showTime: Double
var body: some View {
Text("Welcome").font(.largeTitle).foregroundColor(Color.orange)
.onAppear {
let _delay = RunLoop.SchedulerTimeType(.init(timeIntervalSinceNow: self.showTime))
RunLoop.main.schedule(after: _delay) {
self.flag.toggle()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I code a List in a ScrollView, when I selected a List cell to translate to another view and return back, the cell selected indicator did not disappear after selecting.
I hope after selected the list cell, the selected indicator should be disappear.
I debugged, I found that the ScrollView has some problems when it worked with List.If no ScrollView, the list selection behavior is all right, if plus the ScrollView outside the list, the problem become.
The other problem is How to remove the List Separator.
Thank you for your help!!!
#State var valueData: [String] = ["Apple", "Pear", "Orange", "Cake"]
var body: some View {
NavigationView {
ScrollView(.vertical) {
VStack(spacing: 10) {
DietListView(valueData: self.$valueData)
DietListView(valueData: self.$valueData)
.padding()
}
}
.frame(width: 352)
}
}
struct DietListView: View {
#Binding var valueData: [String]
var body: some View {
VStack {
List {
ForEach(self.valueData, id: \.self) { item in
NavigationLink(destination: DietItemDetailView()) {
HStack {
Text(item)
Spacer()
Text("100")
}
}
}
.onDelete { index in
self.valueData.remove(at: index.first!)
}
}
.frame(height: 300)
}
.frame(width: 352, height: 350)
.background(Color.white)
.cornerRadius(16)
.shadow(radius: 10)
}
}
the problem just like this:
At the moment (SwiftUI Beta 5) you can't customise List very much changing, for example, the divider style. What you can do, depending on your needs, is to use a ScrollView with ForEach and give the cell the style you want. For example:
struct ContentView: View {
#State var valueData: [String] = ["Apple", "Pear", "Orange", "Cake"]
var body: some View {
NavigationView {
ScrollView(.vertical) {
VStack(spacing: 10) {
DietListView(valueData: self.$valueData)
DietListView(valueData: self.$valueData)
.padding()
}
}
.frame(width: 352)
}
}
}
struct DietListView: View {
#Binding var valueData: [String]
var body: some View {
VStack {
ScrollView {
ForEach(self.valueData, id: \.self) { item in
NavigationLink(destination: Text("ciao")) {
HStack {
Text(item)
Spacer()
Text("100")
}
.foregroundColor(.primary)
.padding(10)
}
}
}
.frame(height: 300)
}
.frame(width: 352, height: 350)
.background(Color.white)
.cornerRadius(16)
.shadow(radius: 10)
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
Also, take a look at this: https://stackoverflow.com/a/56498261/1291872
EDIT: you can use onDelete, onMove and onInsert only within a List. If you want to let users delete a row in a ScrollView you must implement something yourself. Take a look at the code below for a simple (pretty ugly) example:
struct ContentView: View {
#State var valueData: [String] = ["Apple", "Pear", "Orange", "Cake"]
var body: some View {
NavigationView {
ScrollView(.vertical) {
VStack(spacing: 10) {
DietListView(valueData: self.$valueData)
DietListView(valueData: self.$valueData)
.padding()
}
}
.frame(width: 352)
}
}
}
struct DietListView: View {
#Binding var valueData: [String]
var body: some View {
VStack {
ScrollView {
ForEach(self.valueData.indices, id: \.self) { idx in
NavigationLink(destination: Text("ciao")) {
HStack {
Text(self.valueData[idx])
Spacer()
Text("100")
.padding(.trailing, 20)
Button(action: {
self.valueData.remove(at: idx)
}) {
Image(systemName: "xmark.circle")
.resizable()
.frame(width: 25, height: 25)
.foregroundColor(Color.red)
}
}
.foregroundColor(.primary)
.padding([.leading, .trailing], 20)
.padding([.top, .bottom], 10)
}
}
}
.frame(height: 300)
}
.frame(width: 352, height: 350)
.background(Color.white)
.cornerRadius(16)
.shadow(radius: 10)
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif