SwiftUI sharing timer through views in navigation view - swiftui

I'm pretty new at SwiftUI.
I would like to make an app that navigates through different views in a Navigation View and running a timer in background presenting the time on each of the views.
The problem is when I navigate to a second level of navigation. When the timer is on, the app returns automatically to the previous navigation view.
Here's is a screenshot video of what I mean:
https://youtu.be/eXbK9jpluvk
Here is my code:
import SwiftUI
struct ContentView: View {
#State var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var seconds : Int = 0
#State var paused : Bool = true
var body: some View {
VStack {
Text("\(seconds)")
Button(action: {
paused.toggle()
}) {
Image(systemName: paused ? "play.fill" : "stop.fill")
}
NavigationLink(destination: FirstView( timerSeconds: $seconds, timerPaused: $paused)) {
Text("Navigate 1")
}.padding()
}
.onReceive(self.timer) { currentTime in
if !paused {
seconds = seconds + 1
print(currentTime)
}
}
.navigationViewStyle(.stack)
}
}
struct FirstView: View {
#Binding var timerSeconds : Int
#Binding var timerPaused : Bool
var body: some View {
VStack {
Text("First View")
Text("\(timerSeconds)")
Button(action: {
timerPaused.toggle()
}) {
Image(systemName: timerPaused ? "play.fill" : "stop.fill")
}
NavigationLink(destination: SecondView(seconds: $timerSeconds)) {
Text("Navigate 2")
}
}
}
}
struct SecondView: View {
#Binding var seconds : Int
var body: some View {
Text("\(seconds)")
}
}
Any help will be welcome!!
Thanks a lot

NavigationView can only push on one detail NavigationLink so to have more levels you need to set .isDetailLink(false) on the link. Alternatively, if you don't expect to run in landscape split view, you could set .navigationViewStyle(.stack) on the navigation.

Related

How to link my Login screen to my Home screen SwiftUI

I am stuck on linking my login screen to my Main screen. Both have been created separately and I have used the Button function to create the login button and it goes to another screen with the email I have logged in with but that's not what I want as I want my home screen to open up instead.
Button(action: model.login) {
Text("LOGIN")
.fontWeight(.bold)
.foregroundColor(Color("Color1"))
.padding(.vertical)
.frame(width: UIScreen.main.bounds.width - 30)
.background(Color.white)
.clipShape(Capsule())
}
.padding(.top, 22)
My MainView code starting
struct MainView: View {
#State private var isShowing = false
var body: some View {
NavigationView {
ZStack {
In your App struct add a State var isLoggedin passing it to the loginView and toggling it from there.
This is how I implemented.
struct TestApp: App {
#State var isLoggedin: Bool = false
var body: some Scene {
WindowGroup {
if isLoggedin {
ContentView()
} else {
LoginView(isLoggedin: $isLoggedin)
}
}
}
}
struct LoginView: View {
#Binding var isLoggedin: Bool
var body: some View {
Button(action: {
isLoggedin = true
}, label: {
Text("Login")
})
}
}
This way you can pick which view to show. ContentView to LoginView.

NavigationView usage in swiftUI

Coming from Android and working on a very complex application , i would like to use NavigationView as much as possible. Having one view and make all elements appear and disappear on this view seems impossible to handle for me .
I was using navigationView to navigate bewteen views with navigationBar hidden .
This way navigating or making view appear is transparent for the user
After some tests , i encounter limitations : at the 13th or 14 th level of navigation everything disappear and app basically crashes .
Once more , this is a direct navigation between 2 content views , no HOMESCREEN
import SwiftUI
struct test4: View {
#State private var intent3: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : test3() , isActive : $intent3) { }
Text("ver 4")
.onTapGesture {
intent3 = true }
Spacer()
}
}
.navigationBarHidden(true)
}
}
import SwiftUI
struct test3: View {
#State private var intent4: Bool = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : test4() , isActive : $intent4) { }
Text("ver 3")
.onTapGesture {
intent4 = true }
Spacer()
}
}.navigationBarHidden(true) }
}
Here a basic example of navigation directly between 2 contents views . Crashes after 14/15 clicks. I encounter the same issue with about any navigation link.
Update:
With your added code, I can see the initial crash was a result of adding a new NavigationView each time. This solves it:
struct ContentView: View {
var body: some View {
NavigationView {
Test3()
}
}
}
struct Test4: View {
#State private var intent3: Bool = false
var body: some View {
VStack{
NavigationLink(destination : Test3() , isActive : $intent3) { }
Text("ver 4")
.onTapGesture {
intent3 = true
}
Spacer()
}
.navigationBarHidden(true)
}
}
struct Test3: View {
#State private var intent4: Bool = false
var body: some View {
VStack{
NavigationLink(destination : Test4() , isActive : $intent4) { }
Text("ver 3")
.onTapGesture {
intent4 = true }
Spacer()
}
.navigationBarHidden(true)
}
}
Original answer:
However, there are solutions to pop to the top of a navigation hierarchy.
One way is to use isActive to manage whether or not a given NavigationLink is presenting its view. That might look like this:
class NavigationReset : ObservableObject {
#Published var rootIsActive = false
func popToTop() {
rootIsActive = false
}
}
struct ContentView: View {
#StateObject private var navReset = NavigationReset()
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "First"), isActive: $navReset.rootIsActive) {
Text("Root nav")
}
}.environmentObject(navReset)
}
}
struct DetailView : View {
var title : String
#EnvironmentObject private var navReset : NavigationReset
var body: some View {
VStack {
NavigationLink(destination: DetailView(title: "\(Date())")) {
Text("Navigate (\(title))")
}
Button("Reset nav") {
navReset.popToTop()
}
}
}
}
Another trick you could use is changing an id on a NavigationLink -- as soon as that happens, it re-renders and becomes inactive.
class NavigationReset : ObservableObject {
#Published var id = UUID()
func popToTop() {
id = UUID()
}
}
struct ContentView: View {
#StateObject private var navReset = NavigationReset()
var body: some View {
NavigationView {
NavigationLink(destination: DetailView(title: "First")) {
Text("Root nav")
}
.id(navReset.id)
}.environmentObject(navReset)
}
}
struct DetailView : View {
var title : String
#EnvironmentObject private var navReset : NavigationReset
var body: some View {
VStack {
NavigationLink(destination: DetailView(title: "\(Date())")) {
Text("Navigate (\(title))")
}
Button("Reset nav") {
navReset.popToTop()
}
}
}
}
It works by marking the first NavigationLink (ie the one on the Home Screen) with an id. As soon as that id is changed, the NavigationLink is recreated, popping all of the views off of the stack.

Why is SwiftUI presented view displayed incorrectly?

I've created a SwiftUI "multiplatform" (iOS and macOS) app from the Xcode 12 beta 6 (12A8189n) app template.
My issue is that one of my views, AnotherView, is displaying incorrectly. Here's a gif showing the problem. Notice that AnotherView displays with the navigation stack already pushed to a non-existent view. Tapping the back button reveals the expected screen, however it is displayed only partially filling the expected area.
Here's the code:
TestNavigationApp.swift
import SwiftUI
#main
struct TestNavigationApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var presentingFirstView = false
var body: some View {
Button(action: { self.presentingFirstView = true }) {
Text("Present First View")
}
.sheet(isPresented: $presentingFirstView) {
FirstView(isPresented: $presentingFirstView)
}
}
}
FirstView.swift
import SwiftUI
struct FirstView: View {
#Binding var isPresented: Bool
var body: some View {
NavigationView {
EmbeddedView()
.navigationBarTitle("First View", displayMode: .large)
}
}
}
EmbeddedView.swift
import SwiftUI
struct EmbeddedView: View {
#State private var presentingAnotherView = false
var body: some View {
VStack {
Text("Embedded View")
Button(action: { self.presentingAnotherView = true }) {
Text("Present Another View")
}
}
.sheet(isPresented: $presentingAnotherView) {
AnotherView(isPresented: $presentingAnotherView)
}
}
}
AnotherView.swift
import SwiftUI
struct AnotherView: View {
#Binding var isPresented: Bool
var body: some View {
NavigationView {
Text("Another View")
.navigationBarTitle("Another View", displayMode: .large)
}
}
}
Anyway, not really sure what's happening here. Any suggestions appreciated.
Try to use navigation view style explicitly
var body: some View {
NavigationView {
Text("Another View")
.navigationBarTitle("Another View", displayMode: .large)
}.navigationViewStyle(StackNavigationViewStyle())
}

How to go to another view with button click

I have a button in my code and I have a file called LogindView.swift
I cannot get the code to open another view file when clicking on the button.
Can anybody give me an example on how to do it.
In my button action I have tried to write LogindView() but i just gives me a warning.
"Result of 'LogindView' initializer is unused"
Button(action: {
// Do action
LogindView()
}, label: {
//** Label text
Text("Logind")
.font(.headline)
.padding(.all)
.foregroundColor(Color.white)
})
.background(Color.blue)
You essentially have 3 options to transition between views depending on your needs.
First, you can use a NavigationView. This will provide a back button and will allow the user to go back. Note that there are some bugs currently when you don't put the NavigationLink inside of a List as per https://stackoverflow.com/a/57122621/3179416
import SwiftUI
struct MasterView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: LoginView()) {
Text("Login")
}
}
.navigationBarTitle(Text("Master"))
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
Second, you can present a modal using .sheet. This will present a modal that appears on top of the current view but it can be dismissed by the user by dragging it down.
import SwiftUI
struct MasterView: View {
#State var isModal: Bool = false
var body: some View {
Button("Login") {
self.isModal = true
}.sheet(isPresented: $isModal, content: {
LoginView()
})
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
Third, you can just use an if statement to change the current view to your Login View like so
import SwiftUI
struct MasterView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
LoginView()
} else {
Button("Login") {
self.showLoginView = true
}
}
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
If you would like to animate this, so that the transition doesn't appear so abruptly, you can also do this:
import SwiftUI
struct MasterView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
LoginView()
.animation(.spring())
.transition(.slide)
} else {
Button("Login") {
withAnimation {
self.showLoginView = true
}
}.animation(.none)
}
}
}
}
struct LoginView: View {
var body: some View {
Text("Login View")
}
}
You can use navigation link instead button
var body: some View {
VStack {
Text("Title")
.font(.headline)
Image("myimage").clipShape(Circle())
Text("mytext").font(.title)
NavigationLink(destination: AnotherView()) {
Image(systemName: "person.circle").imageScale(.large)
}
}
}

Show a new View from Button press Swift UI

I would like to be able to show a new view when a button is pressed on one of my views.
From the tutorials I have looked at and other answered questions here it seems like everyone is using navigation button within a navigation view, unless im mistaken navigation view is the one that gives me a menu bar right arrows the top of my app so I don't want that. when I put the navigation button in my view that wasn't a child of NavigationView it was just disabled on the UI and I couldn't click it, so I guess I cant use that.
The other examples I have seen seem to use presentation links / buttons which seem to show a sort of pop over view.
Im just looking for how to click a regular button and show another a view full screen just like performing a segue used to in the old way of doing things.
Possible solutions
1.if you want to present on top of current view(ex: presentation style in UIKit)
struct ContentView: View {
#State var showingDetail = false
var body: some View {
Button(action: {
self.showingDetail.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $showingDetail) {
DetailView()
}
}
}
2.if you want to reset current window scene stack(ex:after login show home screen)
Button(action: goHome) {
HStack(alignment: .center) {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
func goHome() {
if let window = UIApplication.shared.windows.first {
window.rootViewController = UIHostingController(rootView: HomeScreen())
window.makeKeyAndVisible()
}
}
3.push new view (ex: list->detail, navigation controller of UIKit)
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Show Detail View")
}.navigationBarTitle("Navigation")
}
}
}
}
4.update the current view based on #state property, (ex:show error message on login failure)
struct ContentView: View {
#State var error = true
var body: some View {
...
... //login email
.. //login password
if error {
Text("Failed to login")
}
}
}
For simple example you can use something like below
import SwiftUI
struct ExampleFlag : View {
#State var flag = true
var body: some View {
ZStack {
if flag {
ExampleView().tapAction {
self.flag.toggle()
}
} else {
OtherExampleView().tapAction {
self.flag.toggle()
}
}
}
}
}
struct ExampleView: View {
var body: some View {
Text("some text")
}
}
struct OtherExampleView: View {
var body: some View {
Text("other text")
}
}
but if you want to present more view this way looks nasty
You can use stack to control view state without NavigationView
For Example:
class NavigationStack: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
var list: [AuthState] = []
public func push(state: AuthState) {
list.append(state)
didChange.send()
}
public func pop() {
list.removeLast()
didChange.send()
}
}
enum AuthState {
case mainScreenState
case userNameScreen
case logginScreen
case emailScreen
case passwordScreen
}
struct NavigationRoot : View {
#EnvironmentObject var state: NavigationStack
#State private var aligment = Alignment.leading
fileprivate func CurrentView() -> some View {
switch state.list.last {
case .mainScreenState:
return AnyView(GalleryState())
case .none:
return AnyView(LoginScreen().environmentObject(state))
default:
return AnyView(AuthenticationView().environmentObject(state))
}
}
var body: some View {
GeometryReader { geometry in
self.CurrentView()
.background(Image("background")
.animation(.fluidSpring())
.edgesIgnoringSafeArea(.all)
.frame(width: geometry.size.width, height: geometry.size.height,
alignment: self.aligment))
.edgesIgnoringSafeArea(.all)
.onAppear {
withAnimation() {
switch self.state.list.last {
case .none:
self.aligment = Alignment.leading
case .passwordScreen:
self.aligment = Alignment.trailing
default:
self.aligment = Alignment.center
}
}
}
}
.background(Color.black)
}
}
struct ExampleOfAddingNewView: View {
#EnvironmentObject var state: NavigationStack
var body: some View {
VStack {
Button(action:{ self.state.push(state: .emailScreen) }){
Text("Tap me")
}
}
}
}
struct ExampleOfRemovingView: View {
#EnvironmentObject var state: NavigationStack
var body: some View {
VStack {
Button(action:{ self.state.pop() }){
Text("Tap me")
}
}
}
}
In my opinion this bad way, but navigation in SwiftUI much worse