I'm trying to but a NavigationLink in a LazyVGrid and I'm having a bit of trouble with the navigation. I would like that when the color is clicked that I could segue to the next page. Here is some code:
Here is how the start page looks here
Here is how the image card is set up:
ZStack{
Color(colorData.image)
.cornerRadius(15)
.aspectRatio(contentMode: .fit)
.padding(8)
.matchedGeometryEffect(id: colorData.image, in: animation)
}
Here is how the LazyVgrid is set up:
ScrollView(.vertical, showsIndicators: false, content: {
VStack{
LazyVGrid(columns: Array(repeating: GridItem(.flexible(),spacing: 15), count: 2),spacing: 15){
ForEach(bags){color in
ColorView(colorData: color,animation: animation)
.onTapGesture {
withAnimation(.easeIn){
print("pressed")
selectedColor = color
}
}
}
}
.padding()
.padding(.top,10)
}
})
}
Here is how I navigate:
if selectedColor != nil {
NavigationLink(destination: DetailView()) {}
}
You can do as mentioned in #workingdog's answer, or slightly modify the code like so:
struct ContentView: View {
#State var isActive: Bool = false
#State var selectedColor: Color?
var body: some View {
NavigationView {
NavigationLink(destination: DetailsVieww(selectedColor: selectedColor), isActive: $isActive) {
EmptyView()
}
VStack {
ColorView(colorData: color, animation: animation)
.onTapGesture {
selectedColor = color
isActive = true
}
}
}
}
}
typically with navigation you would do something like this:
struct ContentView: View {
#State var selection: Int?
#State var selectedColor: Color?
var body: some View {
NavigationView { // <--- required somewhere in the hierarchy of views
// ....
NavigationLink(destination: DetailsVieww(selectedColor: selectedColor),
tag: 1,
selection: $selection) {
ColorView(colorData: color, animation: animation)
.onTapGesture {
withAnimation(.easeIn) {
selectedColor = color
selection = 1 // <--- will trigger just the tag=1 NavigationLink
}
}
}
// ....
}
}
}
struct DetailsVieww: View {
#State var selectedColor: Color?
var body: some View {
Text("selected color view \(selectedColor?.description ?? "")")
}
}
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 want to change the background color of top safe area from green to gray. I have looked everywhere but could not find any solution. The screen in preview looks like this.
My codes:
struct ContentView: View {
#State var name = ""
init() {
//Use this if NavigationBarTitle is with Large Font
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.red]
UINavigationBar.appearance().backgroundColor = .gray
}
var body: some View {
NavigationView {
ZStack{
VStack{
TextField("Name", text: $name)
.frame(height:200)
.padding()
.background(backgrounImage())
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray,lineWidth: 4))
.padding()
Spacer()
}.navigationTitle("Tanvir")
.background(Color.green.edgesIgnoringSafeArea(.all))
}
}
}
}
You can add another view on top of the ZStack:
var body: some View {
NavigationView {
ZStack(alignment: .top) { // <- Don't forget this
,,,
GeometryReader { reader in
Color.yellow
.frame(height: reader.safeAreaInsets.top, alignment: .top)
.ignoresSafeArea()
}
}
}
}
Don't forget the stack alignment!
Consistant Bar for the entire App
If you need it to be on all of your views, try putting the code somewhere more consistent like where you are providing the contentView:
#main
struct SwiftUIAppPlaygroundApp: App {
var body: some Scene {
WindowGroup {
ZStack {
ContentView()
GeometryReader { reader in
Color.yellow
.frame(height: reader.safeAreaInsets.top, alignment: .top)
.ignoresSafeArea()
}
}
}
}
}
Use this UIApplication extension to chagne your status bar color
extension UIApplication {
/**
Get status bar view
*/
var statusBarUIView: UIView? {
let tag = 13101996
if let statusBar = self.windows.first?.viewWithTag(tag) {
self.windows.first?.bringSubviewToFront(statusBar)
return statusBar
} else {
let statusBarView = UIView(frame: UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame ?? .zero)
statusBarView.tag = tag
self.windows.first?.addSubview(statusBarView)
return statusBarView
}
}
}
Usage
struct ContentViewStatusBar: View {
#State var name = ""
init() {
//Use this if NavigationBarTitle is with Large Font
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.red]
UINavigationBar.appearance().backgroundColor = .gray
}
var body: some View {
NavigationView {
ZStack{
VStack{
TextField("Name", text: $name)
.frame(height:200)
.padding()
.background(backgrounImage())
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray,lineWidth: 4))
.padding()
Spacer()
}.navigationTitle("Tanvir")
.background(Color.green.edgesIgnoringSafeArea(.all))
}
}.onAppear {
UIApplication.shared.statusBarUIView?.backgroundColor = .gray //<<=== Here
}
}
}
I want to let my cell looks not fill in list's column. I have already clear the list background color and
separatorStyle set .none. I also set my cellView's listRowBackground been gray, but it doesn't work well.The background color is still white in my cell. How do I clear my list's column background color? Please help. Thank you.
struct TeamListView: View {
#EnvironmentObject var userToken : UserToken
#State var teamResults : [TeamResult] = []
var body: some View {
NavigationView {
ZStack{
Color.gray.edgesIgnoringSafeArea(.all)
VStack {
List(teamResults) { team in
TeamListCellView(teamResult: team)
}.navigationBarTitle(Text("My team"),displayMode: .inline)
}
}
.onAppear(perform: {
self.getTeamData()
UITableView.appearance().backgroundColor = .gray
UITableView.appearance().separatorStyle = .none
})
.onDisappear(perform: {
UITableView.appearance().backgroundColor = .white
UITableView.appearance().separatorStyle = .singleLine
})
}
Below is my cellView, I set the .listRowBackground(Color.gray) in here.
struct TeamListCellView: View {
// #ObservedObject var teamResult: TeamResult
var teamResult: TeamResult
var body: some View {
NavigationLink(destination: TeamDetail(teamResult1: teamResult)) {
Image(uiImage: teamResult.teamImage)
.resizable()
.aspectRatio(contentMode: ContentMode.fill)
.frame(width:70, height: 70)
.cornerRadius(35)
VStack(alignment: .leading) {
Text(teamResult.groupName)
Text(teamResult.groupIntro)
.font(.subheadline)
.foregroundColor(Color.gray)
}
} .frame(width:200,height: 100)
.background(Color.green)
.cornerRadius(10)
.listRowBackground(Color.gray)
}
}
You can create a Background<Content: View> and use it to set the background colour of your view. To do it you can embed your views inside your Background View
For example:
struct ContentView: View {
#EnvironmentObject var userToken : UserToken
#State var teamResults : [TeamResult] = []
var body: some View {
Background{
NavigationView {
ZStack{
Color.gray.edgesIgnoringSafeArea(.all)
VStack {
List(teamResults) { team in
TeamListCellView(teamResult: team)
}
.navigationBarTitle(Text("My team"),displayMode: .inline)
}
}
.onAppear(perform: {
self.getTeamData()
UITableView.appearance().backgroundColor = .gray
UITableView.appearance().separatorStyle = .none
})
.onDisappear(perform: {
UITableView.appearance().backgroundColor = .white
UITableView.appearance().separatorStyle = .singleLine
})
}
}
}
}
struct Background<Content: View>: View {
private var content: Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content()
}
var body: some View {
Color.gray
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.overlay(content)
}
}
I create a List to show my player information. but my List column didn't fit my cell view. The textfield that disappear in some column. This is strange because most of they has "-1" number. Is somebody know any reason may cause it happen? Demo video: https://youtu.be/vUo9zZZ5olo
struct SelectPlayerCellView: View {
#ObservedObject var player: PlayerGameData
#State var newuniformNumber: Int = 0
var body: some View {
HStack{
Button(action: {
self.player.isPlayer.toggle()
}){
if self.player.isPlayer{
Image(systemName: "checkmark.rectangle")
}
else{
Image(systemName: "rectangle")
}
}
TextField("\(player.uniformNumber)", value: $newuniformNumber, formatter: NumberFormatter())
.font(.system(size: 40))
.frame(width:55)
.padding(5)
Text(player.name)
}
.padding(.horizontal,8)
.cornerRadius(20)
}
}
I put my cell view in the list
struct SelectPlayerView: View {
#EnvironmentObject var teamResult : TeamResult
#ObservedObject var allPlayerList : PlayersGameData = PlayersGameData()
#State var goToNextPage : Bool = false
var selectedPlayerList : PlayersGameData = PlayersGameData()
var body: some View {
VStack {
List{
ForEach(allPlayerList.playersGameDataArray, id: \.userId) { (player) in
SelectPlayerCellView(player: player)
}.onMove(perform: move)
}
NavigationLink(destination: SelectStartingLineupView(selectedPlayerList: self.selectedPlayerList).environmentObject(teamResult),isActive: $goToNextPage){
EmptyView()
}
VStack (spacing: 10){
Button(action: {
self.createPlayersList()
self.goToNextPage.toggle()
}){
Image(systemName: "arrowshape.turn.up.right.circle")
}
}
}.onAppear(perform: getTeamMemberResults)
.onDisappear(perform: clearTeamMemberResults)
.navigationBarItems(trailing: EditButton())
}
Why I am putting TabView into a NavigationView is because I need to hide the bottom tab bar when user goes into 2nd level 'detail' views which have their own bottom action bar.
But doing this leads to another issue: all the 1st level 'list' views hosted by TabView no longer display their titles. Below is a sample code:
import SwiftUI
enum Gender: String {
case female, male
}
let members: [Gender: [String]] = [
Gender.female: ["Emma", "Olivia", "Ava"], Gender.male: ["Liam", "Noah", "William"]
]
struct TabItem: View {
let image: String
let label: String
var body: some View {
VStack {
Image(systemName: image).imageScale(.large)
Text(label)
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
ListView(gender: .female).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
ListView(gender: .male).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
}
}
}
struct ListView: View {
let gender: Gender
var body: some View {
let names = members[gender]!
return List {
ForEach(0..<names.count, id: \.self) { index in
NavigationLink(destination: DetailView(name: names[index])) {
Text(names[index])
}
}
}.navigationBarTitle(Text(gender.rawValue), displayMode: .inline)
}
}
struct DetailView: View {
let name: String
var body: some View {
ZStack {
VStack {
Text("profile views")
}
VStack {
Spacer()
HStack {
Spacer()
TabItem(image: "pencil.circle", label: "Edit")
Spacer()
TabItem(image: "minus.circle", label: "Delete")
Spacer()
}
}
}
.navigationBarTitle(Text(name), displayMode: .inline)
}
}
What I could do is to have a #State var title in the root view and pass the binding to all the list views, then have those list views to set their title back to root view on appear. But I just don't feel so right about it, is there any better way of doing this? Thanks for any help.
The idea is to join TabView selection with NavigationView content dynamically.
Demo:
Here is simplified code depicting approach (with using your views). The NavigationView and TabView just position independently in ZStack, but content of NavigationView depends on the selection of TabView (which content is just stub), thus they don't bother each other. Also in such case it becomes possible to hide/unhide TabView depending on some condition - in this case, for simplicity, presence of root list view.
struct TestTabsOverNavigation: View {
#State private var tabVisible = true
#State private var selectedTab: Int = 0
var body: some View {
ZStack(alignment: .bottom) {
contentView
tabBar
}
}
var contentView: some View {
NavigationView {
ListView(gender: selectedTab == 0 ? .female : .male)
.onAppear {
withAnimation {
self.tabVisible = true
}
}
.onDisappear {
withAnimation {
self.tabVisible = false
}
}
}
}
var tabBar: some View {
TabView(selection: $selectedTab) {
Rectangle().fill(Color.clear).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
Rectangle().fill(Color.clear).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
.frame(height: 50) // << !! might be platform dependent
.opacity(tabVisible ? 1.0 : 0.0)
}
}
This maybe a late answer, but the TabView items need to be assigned tag number else binding selection parameter won't happen. Here is how I do the same thing on my project:
#State private var selectedTab:Int = 0
private var pageTitles = ["Home", "Customers","Sales", "More"]
var body: some View {
NavigationView{
TabView(selection: $selectedTab, content:{
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text(pageTitles[0])
}.tag(0)
CustomerListView()
.tabItem {
Image(systemName: "rectangle.stack.person.crop.fill")
Text(pageTitles[1])
}.tag(1)
SaleView()
.tabItem {
Image(systemName: "tag.fill")
Text(pageTitles[2])
}.tag(2)
MoreView()
.tabItem {
Image(systemName: "ellipsis.circle.fill")
Text(pageTitles[3])
}.tag(3)
})
.navigationBarTitle(Text(pageTitles[selectedTab]),displayMode:.inline)
.font(.headline)
}
}