Button not changing view in SwiftUI - swiftui

So in my ContentView.swift, I have this code:
import SwiftUI
extension UIScreen{
static let screenWidth = UIScreen.main.bounds.size.width
static let screenHeight = UIScreen.main.bounds.size.height
static let screenSize = UIScreen.main.bounds.size
}
struct ContentView: View {
#State var Direction: Int = 0
#State var ButtonsShowing: Bool = true
#State var View: Int = 0
func MoveLeft() {
if ButtonsShowing == true {
ButtonsShowing = false
}
}
func MoveRight() {
if ButtonsShowing == true {
ButtonsShowing = false
}
Direction = 1
}
var body: some View {
ZStack {
Image("Space").resizable()
.frame(width: UIScreen.screenWidth, height: UIScreen.screenHeight + 50)
.edgesIgnoringSafeArea(.all)
VStack {
HStack {
Button(action: MoveLeft) {
if ButtonsShowing == true {
NavigationLink(destination: Playing()) {
Image("LeftClick")
.frame(width: UIScreen.screenWidth / 2, height: UIScreen.screenHeight)
}
}
}
Spacer()
Button(action: MoveRight) {
if ButtonsShowing == true {
Image("RightClick")
.frame(width: UIScreen.screenWidth / 2, height: UIScreen.screenHeight)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I get a preview of this:
Preview of button with NavigationLink
If you look towards my LeftClick button, I want it to take me to a different view, but don't know how. I have a NavigationLink in it, with my destination being the different view (Playing.swift, if needed to know that, I call it using Playing())
Obviously I'm doing something wrong, and I [[hopefully]] know for sure that it's from the button, not from the other view:
NavigationLink(destination: Playing()) {
Image("LeftClick")
.frame(width: UIScreen.screenWidth / 2, height: UIScreen.screenHeight)
}
Thanks in advance.

Add a NavigationView before your ZStack, NavigationLink won't work without a NavigationView.
var body: some View {
NavigationView {
ZStack {
...
}
}
}

Related

Why is my custom view not printing something inside this onTapGesture?

HomeView
import SwiftUI
struct HomeView: View {
#State private var mapState = MapViewState.noInput
#EnvironmentObject var locationViewModel: LocationSearchViewModel
#EnvironmentObject var launchScreenManager : LaunchScreenManager
var body: some View {
ZStack(alignment: .bottom) {
ZStack(alignment: .top) {
UberMapViewRepresentable(mapState: $mapState )
.ignoresSafeArea()
if mapState == .searchingForLocation{
LocationSearchView(mapState: $mapState)
} else if mapState == .noInput {
LocationSearchActivationView()
.padding(.top, 72)
.onTapGesture{
withAnimation(.spring()) {
mapState = .searchingForLocation
}
}
}
MapViewActionButton(mapState: $mapState)
.padding(.leading)
.padding(.top, 4)
.onTapGesture{
print("Why isn't this printing?")
}
}
if mapState == .locationSelected || mapState == .polylineAdded{
RideRequestView()
.transition(.move(edge: .bottom))
}
}
.edgesIgnoringSafeArea(.bottom)
.onReceive(LocationManager.shared.$userLocation) { location in
if let location = location {
locationViewModel.userLocation = location
}
}
.onAppear{
DispatchQueue
.main
.asyncAfter(deadline: .now() + 2) {
launchScreenManager.dismiss()
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
MapViewActionButton
import SwiftUI
struct MapViewActionButton: View {
#Binding var mapState: MapViewState
#EnvironmentObject var viewModel : LocationSearchViewModel
var body: some View {
Button {
withAnimation(.spring()) {
actionForState(mapState)
}
} label: {
Image(systemName: imageNameForState(mapState))
.font(.title2)
.foregroundColor(.black)
.padding()
.background(.white)
.clipShape(Circle())
.shadow(color: .black, radius: 6)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
func actionForState(_ state: MapViewState) {
switch state {
case .noInput:
print("no input")
case .searchingForLocation:
mapState = .noInput
case .locationSelected, .polylineAdded:
mapState = .noInput
viewModel.selectedUberLocation = nil
}
}
func imageNameForState(_ state: MapViewState) -> String {
switch state {
case .noInput:
return "line.3.horizontal"
case .searchingForLocation, .locationSelected, .polylineAdded:
return "arrow.left"
}
}
}
struct MapViewActionButton_Previews: PreviewProvider {
static var previews: some View {
MapViewActionButton(mapState: .constant(.noInput))
}
}
I understand that maybe not all this code is relevant but basically when I am adding an onTapGesture to MapViewActionButton and printing something, it doesn't print? Does anybody know why this can be happening?
enter image description here
If I add a background(.red) modifier to my MapViewActionButton it seems it's not going over just the button(presumably because I am setting the frame width to infinity but how come it's not when I tap the image itself it's not detecting the tap gesture?
I tried playing around with this a lot but I couldn't really figure out why this was happening.
By using this you can use print anywhere in code. If there is no other issue.
let _ = print("Why isn't this printing?")
You can try something like this
Button("Button") {
print("tapped")
}
.onTapGesture{
print("Why isn't this printing?")
}
}

LazyGridView how to detect and act on item overflowing screen?

I have a grid of items. Each item can expand height. I want to autoscroll when the item is expanded so it doesn't overflow the screen.
I was successful with the following code but I had to revert to a hack.
The idea was to detect when the item is overflowing using a Geometry reader on the item's background. Works wonders.
The issue is that when the view is expanded , the geo reader will update after the condition to check if autoscroll should execute is ran by the dispatcher. Hence my ugly hack.
Wonder what is the proper way ?
import SwiftUI
struct BlocksGridView: View {
private var gridItemLayout = [GridItem(.adaptive(minimum: 300, maximum: .infinity), spacing: 20)]
var body: some View {
ZStack{
ScrollView {
ScrollViewReader { value in
LazyVGrid(columns: gridItemLayout, spacing: 20) {
ForEach((0..<20), id: \.self) {
BlockView(cardID: $0,scrollReader: value).id($0)
}
}
}
.padding(20)
}
}
}
}
struct BlockView : View {
var cardID : Int
var scrollReader : ScrollViewProxy
#State private var isOverflowingScreen = false
#State private var expand = false
var body: some View {
ZStack{
Rectangle()
.foregroundColor(isOverflowingScreen ? Color.blue : Color.green)
.frame(height: expand ? 300 : 135)
.clipShape(Rectangle()).cornerRadius(14)
.overlay(Text(cardID.description))
.background(GeometryReader { geo -> Color in
DispatchQueue.main.async {
if geo.frame(in: .global).maxY > UIScreen.main.bounds.maxY {
isOverflowingScreen = true
} else {
isOverflowingScreen = false
}
}
return Color.clear
})
.onTapGesture {
expand.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { // <-- Hack :(
if isOverflowingScreen {
withAnimation{
scrollReader.scrollTo(cardID)
}
}
}
}
}
}
}
struct BlocksGridView_Previews: PreviewProvider {
static var previews: some View {
BlocksGridView()
}
}
Blue items are overflowing ...

how can i make a conditional navigation in swiftui [duplicate]

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")
}

SwiftUI Binding Test for Button And Picker

I would like to make a test with SwiftUI binding.
Aim was changing Picker selection with a button and with an other Picker.
In following code ContentView2's Picker selection will be changed with button in ContentView and Picker in ContentView1.
Button can change ContentView2 picker selection but ContentView1 Picker does not.
I can not find the reason.
You can copy paste code to test.
import SwiftUI
struct ContentView: View {
#State private var index1 = 0
#State private var index2 = 1
var body: some View {
GeometryReader { mainView in
HStack {
Button(action: {
if index2 == 0 {
index2 = 1
} else {
index2 = 0
}
}) {
Text("Button")
}
ContentView1(pickerIndex: $index1)
ContentView2(pickerIndex: $index2)
}
}
}
}
struct ContentView1: View {
#State var pickerData = ["Data1", "Data2"]
#Binding var pickerIndex: Int
#State var pickerIndex2 = 0
var customLabel0: some View{
HStack {
VStack(spacing: 10) {
Text("Picker One")
.foregroundColor(.black)
Text(pickerData[pickerIndex])
.multilineTextAlignment(.center)
}
.foregroundColor(.white)
}
.frame(width: (UIScreen.main.bounds.width - (2.5 * 8 )) * 0.6 * 0.5, height: 100)
.background(Color.gray)
.cornerRadius(5)
}
#State private var testIndex = 1
var body: some View {
Menu {
Picker("", selection: self.$pickerIndex) {
ForEach(0..<pickerData.count, id: \.self) {index in
Text(pickerData[index])
}
}
} label: {
customLabel0
}
.onChange(of: pickerIndex, perform: {_ in
ContentView2(pickerIndex: $pickerIndex)
})
}
}
struct ContentView2: View {
#State var pickerData = ["Data1", "Data2"]
#Binding var pickerIndex: Int
var customLabel0: some View{
HStack {
VStack(spacing: 10) {
Text("Picker Two")
.foregroundColor(.black)
Text(pickerData[pickerIndex])
.multilineTextAlignment(.center)
}
.foregroundColor(.white)
}
.frame(width: (UIScreen.main.bounds.width - (2.5 * 8 )) * 0.6 * 0.5, height: 100)
.background(Color.gray)
.cornerRadius(5)
}
var body: some View {
Menu {
Picker("", selection: $pickerIndex) {
ForEach(0..<pickerData.count, id: \.self) {index in
Text(pickerData[index])
}
}
} label: {
customLabel0
}
.onChange(of: pickerIndex, perform: {_ in
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI Let View disappear automatically

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()
}
}