How can I preview a SwiftUI button that depends on PresentationMode? - swiftui

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

Related

Swiftui Button to behave like a checkbox doesn't preview but works

Here's the code for the Checkbox I created. It works great but in the Xcode preview canvas it doesn't work. As in, in the preview when you click it, it doesn't change. When I use the checkbox in my app and run it in the simulator it works great. Any ideas why this is?
//
// CheckboxToggle.swift
//
//
//
import SwiftUI
enum CheckboxShape : String {
case circle = "circle"
case square = "square"
}
struct Checkbox: View {
#Binding var isChecked:Bool
var style:CheckboxShape
#State var action:(Bool)->() = {_ in }
var body: some View {
Button {
isChecked.toggle()
action(isChecked)
} label: {
let shape = style.rawValue
Image(systemName: isChecked ? "checkmark.\(shape).fill" : "\(shape)")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(isChecked ? .green : .gray)
.font(.system(size: 20, weight: .regular, design: .default))
}
.buttonStyle(PlainButtonStyle())
}
}
struct Checkbox_Previews: PreviewProvider {
#State static var isChecked:Bool = false
// Not sure why this preview doens't work but the view works in other views fine.
static var previews: some View {
HStack {
Checkbox(isChecked: $isChecked, style: .circle)
}
}
}
SwiftUI previews don't work this way.
To make Checkbox interactive declare isChecked as #State
#State var isChecked = false
and replace the PreviewProvider with
struct Checkbox_Previews: PreviewProvider {
static var previews: some View {
Checkbox(style: .circle)
}
}
And if you want to see both states (and also the square appearance) and keep the #Binding add more Checkbox views and specify constants
struct Checkbox_Previews: PreviewProvider {
static var previews: some View {
HStack {
Checkbox(isChecked: .constant(true), style: .circle)
Checkbox(isChecked: .constant(false), style: .circle)
Checkbox(isChecked: .constant(true), style: .square)
Checkbox(isChecked: .constant(false), style: .square)
}
}
}
But this preview is not interactive.

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

SwiftUI dismiss all active presented view controls and pop to root view controller

I'm trying to dismiss all active presented view controllers and pushed view controllers, but I'm facing some issue and not getting expected output.
ContentView -> Pushed(LoginView) -> Presented(HomeView) -> Expected back to (ContentView) directly without any animation lags and without showing intermediate animations. It should directly show content view.
here is my code:
import SwiftUI
struct FirstView: View {
#State var showLoginView = false
var body: some View {
NavigationView {
VStack(spacing: 16) {
Text("FirstView View")
.font(.largeTitle)
.foregroundColor(.red)
NavigationLink( destination: SecondView(), isActive: $showLoginView, label: {
Text("Show SecondView")
})
}
}
}
}
struct SecondView: View {
#State var showHomeView = false
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack(spacing: 16) {
Text("Second View")
.font(.largeTitle)
.foregroundColor(Color(.systemPurple))
Button(action: { showHomeView = true }, label: {
Text("Show ThirdView")
.padding()
})
}
.sheet(isPresented: $showHomeView, onDismiss: { presentationMode.wrappedValue.dismiss() }) {
ThirdView()
}
}
}
struct ThirdView: View {
#Environment(\.presentationMode) var presentationMode
#State var showFormView = false
var body: some View {
VStack(spacing: 16) {
Text("Third View")
.font(.largeTitle)
.foregroundColor(Color(.systemTeal))
Button(action: {
showFormView = true
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Back to FirstView")
.padding()
})
}
}
}

Passing Data to Second View in SwiftUI

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.

Why won't my UI reflect a change in the #State property?

I'm trying to make a graph app, but animating it using a #State property does not help, for some reason.
struct GraphBars: View {
#State var percent: CGFloat
var body: some View {
Capsule()
.fill(Color.black)
.frame(width: 50, height: self.percent)
}
}
struct TEST3: View {
#State var bar1: CGFloat = 90.0
var body: some View {
GeometryReader { gg in
VStack {
Button(action: {
self.bar1 = 300.0
}) {
Text("Hello")
}
GraphBars(percent: bar1)
}
However, pressing the button does not increase the height of the bar as I thought it would. What am I doing wrong?
You need to use #Binding to transfer a variable back and forth between two Views otherwise the parent View doesn't get a notice of the variable change.
Do it like this:
struct GraphBars: View {
#Binding var percent: CGFloat
var body: some View {
Capsule()
.fill(Color.black)
.frame(width: 50, height: self.percent)
}
}
struct Test3: View {
#State var bar1: CGFloat = 90.0
var body: some View {
GeometryReader { gg in
VStack {
Button(action: {
self.bar1 = 300.0
}) {
Text("Hello")
}
GraphBars(percent: self.$bar1)
}
}
}
}