SwiftUI View doesn't change when using button and #State / #Binding - swiftui

Long story short this is an onboarding view after a user goes through auth. My main navigation of the app uses navigationview but I can't use that for onboarding. I've put a fullscreencover over the main screen for this onboarding stuff.
However, when trying to simply navigate between the views from different files between the onboarding screens, the button doesn't show the other view. I've tried everything under the son from #Appstorage and #Environment stuff but can't get it to work.
Also please note I cut off the bottom of the rest of the file as that had nothing to do with this logic.
import SwiftUI
struct OnboardingTestView: View {
#State var shouldShowOnboarding = true
var body: some View {
if shouldShowOnboarding {
OffsetChoicesView(shouldShowOnboarding: $shouldShowOnboarding)
}
if shouldShowOnboarding == false {
PersonalInfoView()
}
}
}
struct OnboardingTestView_Previews: PreviewProvider {
static var previews: some View {
OnboardingTestView()
}
}
//*********************************************************************
//OffsetChoicesView
struct OffsetChoicesView: View {
#Binding var shouldShowOnboarding: Bool
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.9803921569, green: 0.9568627451, blue: 0.9568627451, alpha: 1)).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
Progress1View()
.padding(.bottom, 40)
.padding(.top)
Spacer()
Button(action: {
shouldShowOnboarding = false
}) {
NextButtonView()
.padding(.top)
.padding(.bottom)
}
}

You can try something like this - utilizes #AppStorage
I was not quite sure what the OffsetChoiceView() parameters are supposed to be.. so I just removed them in the example. This should be whatever your onboarding view is called.
import SwiftUI
struct OnboardingTestView: View {
//#State var shouldShowOnboarding = true
#AppStorage("showOnboarding") var showOnboarding: Bool = true
var body: some View {
if (showOnboarding == true) {
OffsetChoicesView()
} else if (showOnboarding == false) {
PersonalInfoView()
}
}
}
struct OnboardingTestView_Previews: PreviewProvider {
static var previews: some View {
OnboardingTestView()
}
}
//*********************************************************************
//OffsetChoicesView
struct OffsetChoicesView: View {
//#Binding var shouldShowOnboarding: Bool
#AppStorage("showOnboarding") var showOnboarding: Bool = false
var body: some View {
ZStack {
Color(#colorLiteral(red: 0.9803921569, green: 0.9568627451, blue: 0.9568627451, alpha: 1)).edgesIgnoringSafeArea(/*#START_MENU_TOKEN#*/.all/*#END_MENU_TOKEN#*/)
VStack {
Progress1View()
.padding(.bottom, 40)
.padding(.top)
Spacer()
Button(action: {
showOnboarding = false
}) {
NextButtonView()
.padding(.top)
.padding(.bottom)
}
}

Related

Two views within a view, top with button and the lower not showing the firebase data

Simple project to be able to understand the concept.
I have two views UpperView and LowerView. The UpperView has a button, when clicked the button calls a ViewModel that fetches data from firebase. My problem is displaying the fetched data in the LowerView. I initialize the ViewModel in the LowerView so that I can access the fetched data through a #Published property but it doesn't work. It's a pretty simple case that I have built in order to understand the concept. Here is the code for UpperView, LowerView and the ViewModel. HomeView is the combination of the UpperView and the LowerView. It feels as if the data is loaded after the LowerView is displayed. All help will be appreciated!!
import Foundation
class MergeViewModel: ObservableObject {
#Published var clients: [Client] = [Client]()
func fetchAllClients() {
COLLECTION_CLIENTS.getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let documents = querySnapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: Client.self)})
print(self.clients.count)
}
}
}
import SwiftUI
struct UpperView: View {
#ObservedObject var viewModel = MergeViewModel()
#State var numberOfClients: Int = 0
#State var buttonPressed: Int = 0
#State var clients: [Client] = [Client]()
var body: some View {
ZStack {
Color(.red)
VStack{
Text("This is UPPER VIEW ")
.foregroundColor(.white)
Text("We have \(numberOfClients) of clients!")
Text("Button pressed \(buttonPressed)")
Button(action: {
viewModel.fetchAllClients()
numberOfClients = viewModel.clients.count
buttonPressed += 1
}, label: {
Text("Press")
.frame(width: 100, height: 50)
.background(Color.white.opacity(0.50))
.cornerRadius(10)
})
}
}.ignoresSafeArea()
}
}
struct UpperView_Previews: PreviewProvider {
static var previews: some View {
UpperView()
}
}
import SwiftUI
struct LowerView: View {
#ObservedObject var viewModel = MergeViewModel()
var body: some View {
VStack {
Text("This is LOWER VIEW")
.foregroundColor(.black)
Text("\(viewModel.clients.count)")
.foregroundColor(.black)
List(viewModel.clients) { client in
Text(client.clientName)
.foregroundColor(.black)
}
}
}
}
struct LowerView_Previews: PreviewProvider {
static var previews: some View {
LowerView()
}
}
import SwiftUI
struct HomeView: View {
var body: some View {
NavigationView {
VStack {
UpperView()
LowerView()
Spacer()
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
HStack {
Image("logo_silueta")
.resizable()
.scaledToFit()
.frame(width: 30)
Text("TheJump")
.font(.subheadline)
.foregroundColor(.gray.opacity(0.8))
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
AuthViewModel.shared.signOut()
}, label: {
Text("logout")
})
}
}
}
}
}
Thanks for your input!
Here is how it works correctly:
ViewModel
import Foundation
class MergeViewModel: ObservableObject {
#Published var clients: [Client] = [Client]()
init(){
fetchAllClients()
}
func fetchAllClients() {
COLLECTION_CLIENTS.getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let documents = querySnapshot?.documents else { return }
self.clients = documents.compactMap({ try? $0.data(as: Client.self)})
print(self.clients.count)
}
}
}
UpperView
import SwiftUI
struct UpperView: View {
#ObservedObject var viewModel: MergeViewModel
#State var numberOfClients: Int = 0
#State var buttonPressed: Int = 0
#State var clients: [Client] = [Client]()
var body: some View {
ZStack {
Color(.red)
VStack{
Text("This is UPPER VIEW ")
.foregroundColor(.white)
Text("We have \(numberOfClients) of clients!")
Text("Button pressed \(buttonPressed)")
Button(action: {
viewModel.fetchAllClients()
numberOfClients = viewModel.clients.count
buttonPressed += 1
}, label: {
Text("Press")
.frame(width: 100, height: 50)
.background(Color.white.opacity(0.50))
.cornerRadius(10)
})
}
}.ignoresSafeArea()
}
}
struct UpperView_Previews: PreviewProvider {
static var previews: some View {
UpperView(viewModel: MergeViewModel())
}
}
LowerView
struct LowerView: View {
#ObservedObject var viewModel: MergeViewModel
var body: some View {
VStack {
Text("This is LOWER VIEW")
.foregroundColor(.black)
Text("\(viewModel.clients.count)")
.foregroundColor(.black)
List(viewModel.clients) { client in
Text(client.clientName)
.foregroundColor(.black)
}
}
}
}
struct LowerView_Previews: PreviewProvider {
static var previews: some View {
LowerView(viewModel: MergeViewModel())
}
}

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.

Fade-in/out animation with a boolean flag

I am trying to implement a simple "tap to toggle the visibility of the UI" in SwiftUI with fade in/out animation. The following code animates the fade-in effect of the Text element as I expected, but it immediately hides the Text element when isVisible become false.
I'd like to understand why this code does not work, and how to fix it in the most natural way.
import SwiftUI
struct ContentView: View {
#State var isVisible = true
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.blue)
.gesture(TapGesture(count: 1).onEnded {
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
}
})
if isVisible {
Text("Tap me!")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm using Xcode 12.5 on Big Sur, and my iPhone is running iOS 14.5.1.
Thanks to Erik Philips, here is the answer.
import SwiftUI
struct ContentView: View {
#State var isVisible = true
var body: some View {
ZStack {
Rectangle()
.zIndex(1)
.foregroundColor(.blue)
.gesture(TapGesture(count: 1).onEnded {
withAnimation(.easeInOut(duration: 1.0)) {
isVisible.toggle()
}
})
if isVisible {
Text("Tap me!")
.zIndex(2)
}
}
}
}
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()
}
}

SwiftUI - Animation in view stops when scrolling the list

Considering the following code, why the animations in the views that are initialized without the n property stops when you scroll the list?
Tested on Xcode 11.3 (11C29) with a new default project on device and simulator.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
List(1...50, id: \.self) { n in
HStack {
KeepRolling()
Spacer()
KeepRolling(n: n)
}
}
}
}
}
struct KeepRolling: View {
#State var isAnimating = false
var n: Int? = nil
var body: some View {
Rectangle()
.frame(width: 50, height: 50)
.rotationEffect(Angle(degrees: self.isAnimating ? 360 : 0))
.onAppear {
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
self.isAnimating = true
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
IMO it is due to caching/reuse in List. For List all the values of KeepRolling() is the same, so .onAppear is not always re-called.
If to make every such view unique, say using .id as below, all works (tested with Xcode 11.2)
KeepRolling().id(UUID().uuidString)