SwiftUI/Combine no updates to #ObservedObject from #Published - ios

I have a logon screen followed by an overview screen. When the user is successful at logon, the logon response sends back a list of items, which I want to display on the subsequent overview screen.
I can see the response being successfully mapped but the overview view is not receiving any update to the #ObservedObject. I could be missing something obvious but I've been through a bunch of articles and haven't managed to get anything working. Any help appreciated!
Logon view
import SwiftUI
struct LogonView: View {
#State private var username: String = ""
#State private var password: String = ""
#State private var inputError: Bool = false
#State private var errorMessage: String = ""
#State private var loading: Bool? = false
#State private var helpShown: Bool = false
#State private var successful: Bool = false
//MARK:- UIView
var body: some View {
NavigationView {
VStack {
VStack {
TextField("Username", text: $username)
.padding(.horizontal)
.disabled(loading! ? true : false)
Rectangle()
.frame(height: 2.0)
.padding(.horizontal)
.foregroundColor(!inputError ? Color("SharesaveLightGrey") : Color("SharesaveError"))
.animation(.easeInOut)
}.padding(.top, 80)
VStack {
SecureField("Password", text: $password)
.padding(.horizontal)
.disabled(loading! ? true : false)
Rectangle()
.frame(height: 2.0)
.padding(.horizontal)
.foregroundColor(!inputError ? Color("SharesaveLightGrey") : Color("SharesaveError"))
.animation(.easeInOut)
}.padding(.top, 40)
if (inputError) {
HStack {
Text(errorMessage)
.padding(.top)
.padding(.horizontal)
.foregroundColor(Color("SharesaveError"))
.animation(.easeInOut)
.lineLimit(nil)
.font(.footnote)
Spacer()
}
}
SharesaveButton(action: {self.submit(user: self.username, pass: self.password)},
label: "Log on",
loading: $loading,
style: .primary)
.padding(.top, 40)
.animation(.interactiveSpring())
NavigationLink(destination: OverviewView(), isActive: $successful) {
Text("")
}
Spacer()
}
.navigationBarTitle("Hello.")
.navigationBarItems(
trailing: Button(action: { self.helpShown = true }) {
Text("Need help?").foregroundColor(.gray)
})
.sheet(isPresented: $helpShown) {
SafariView( url: URL(string: "http://google.com")! )
}
}
}
//MARK:- Functions
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = ResultsViewModel()
resultsVM.getGrants(user: user, pass: pass,
successful: { response in
self.loading = false
if ((response) != nil) { self.successful = true }
},
error: { error in
self.inputError = true
self.loading = false
self.successful = false
switch error {
case 401:
self.errorMessage = "Your credentials were incorrect"
default:
self.errorMessage = "Something went wrong, please try again"
}
},
failure: { fail in
self.inputError = true
self.loading = false
self.successful = false
self.errorMessage = "Check your internet connection"
})
}
}
Results View Model
import Foundation
import Moya
import Combine
import SwiftUI
class ResultsViewModel: ObservableObject {
#Published var results: Results = Results()
func getGrants(
user: String,
pass: String,
successful successCallback: #escaping (Results?) -> Void,
error errorCallback: #escaping (Int) -> Void,
failure failureCallback: #escaping (MoyaError?) -> Void
)
{
let provider = MoyaProvider<sharesaveAPI>()
provider.request(.getSharesave(username: user, password: pass)) { response in
switch response.result {
case .success(let value):
do {
let data = try JSONDecoder().decode(Results.self, from: value.data)
self.results = data
successCallback(data)
} catch {
let errorCode = value.statusCode
errorCallback(errorCode)
}
case .failure(let error):
failureCallback(error)
}
}
}
}
Overview View
import SwiftUI
import Combine
struct OverviewView: View {
#ObservedObject var viewModel: ResultsViewModel = ResultsViewModel()
var body: some View {
let text = "\(self.viewModel.results.market?.sharePrice ?? 0.00)"
return List {
Text(text)
}
}
}

You submitted request to one instance of ResultsViewModel
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = ResultsViewModel() // << here
by try to read data from another instance of ResultsViewModel
struct OverviewView: View {
#ObservedObject var viewModel: ResultsViewModel = ResultsViewModel() // << here
but it must be the one instance, so modify as follows
1) In OverviewView
struct OverviewView: View {
#ObservedObject var viewModel: ResultsViewModel // expects injection
2) In LogonView
struct LogonView: View {
#ObservedObject var resultsViewModel = ResultsViewModel() // created once
and inject same instance for OverviewView
NavigationLink(destination: OverviewView(viewModel: self.resultsViewModel), isActive: $successful) {
Text("")
}
and in submit
private func submit(user: String, pass: String) {
loading = true
inputError = false
let resultsVM = self.resultsViewModel // use created

Please try after change in initialise OverviewView like below in NavigationLink
NavigationLink(destination: OverviewView(viewModel: self.resultsVM),
isActive: $successful) {
Text("")
}
OR
pass results in OverviewView as argument like below
NavigationLink(destination: OverviewView(results: self.resultsVM.results),
isActive: $successful) {
Text("")
}
.....
struct OverviewView: View {
let results: Results
var body: some View {
let text = "\(self.results.market?.sharePrice ?? 0.00)"
return List {
Text(text)
}
}
}

Related

Creating an iOS passcode view with SwiftUI, how to hide a TextView?

I am trying to imitate a lock screen of iOS in my own way with some basic code. However I do not understand how to properly hide an input textview. Now I am using an opacity modifier, but it does not seem to be the right solution. Could you please recommend me better options?
import SwiftUI
public struct PasscodeView: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var viewModel: ContentView.ViewModel
private let maxDigits: Int = 4
private let userPasscode = "1234"
#State var enteredPasscode: String = ""
#FocusState var keyboardFocused: Bool
#State private var showAlert = false
#State private var alertMessage = "Passcode is wrong, try again!"
public var body: some View {
VStack {
HStack {
ForEach(0 ..< maxDigits) {
($0 + 1) > enteredPasscode.count ?
Image(systemName: "circle") :
Image(systemName: "circle.fill")
}
}
.alert("Wrong Passcode", isPresented: $showAlert) {
Button("OK", role: .cancel) { }
}
TextField("Enter your passcode", text: $enteredPasscode)
.opacity(0)
.keyboardType(.decimalPad)
.focused($keyboardFocused)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
keyboardFocused = true
}
}
}
.padding()
.onChange(of: enteredPasscode) { _ in
guard enteredPasscode.count == maxDigits else { return }
passcodeValidation()
}
}
func passcodeValidation() {
if enteredPasscode == userPasscode {
viewModel.isUnlocked = true
dismiss()
} else {
enteredPasscode = ""
showAlert = true
}
}
}

index out of bound from TabViewStyle of SwiftUI

I am trying to implement a tabView that takes a list of items into pages that I could swipe back and forth. However, it keeps bugging out with an "index out of bound" error. It's confusing to me because I never set index at all, and I don't know how to force an index either...
Below are my code. Apologize for any naive code, I am new to SwiftUI. Any helps are appreciated, thank you!
import SwiftUI
//#Published private var list = QuestionList.self
struct QuestionList: Codable {
var list:[QuestionItem]
}
class QuestionItem: Codable, Identifiable {
var id: Int
var text: String
var type: Int
var answer: String
}
struct ContentView: View {
#State private var qlist = [QuestionItem]()
#State private var isShowForm = false
#State private var q1 = true
#State private var answer = ""
#State private var isOn = [Bool]()
#State private var selectedTab = 0
func showForm() {
isShowForm = !isShowForm
let url = URL(string: "http://127.0.0.1:3000/question")!
let task = URLSession.shared.dataTask(with: url) {
data, response, error in
if let error = error {
print(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print(response)
return
}
guard let data = data else {
return
}
do {
let list = try JSONDecoder().decode([QuestionItem].self, from:data)
qlist = list
print(qlist[1])
for i in 0..<qlist.count {
isOn.append(qlist[i].type == 0)
}
print(isOn)
// print(isOn)
print(type(of: qlist[1]))
} catch {
print("error: ", error)
}
}
task.resume()
}
var body: some View {
Text("Hello, world!")
.padding()
Button("Open Form") {
self.showForm()
}
if (isShowForm) {
TabView(selection: $selectedTab) {
ForEach(qlist.indices, id: \.self) { index in
if qlist[index].type == 0 {
HStack {
Text("\(self.qlist[index].text)")
// Toggle("", isOn: $isOn[index])
Toggle("", isOn: $q1)
}
} else {
VStack {
Text("\(self.qlist[index].text)")
// .lineLimit(2)
// .multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
// .frame(width: 300)
TextField("Enter your answer here:", text: $qlist[index].answer) {
}
}
}
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
.indexViewStyle(.page(backgroundDisplayMode: .always))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It is not recommended to use indices in ForEach loops, instead use something like this updated code
that allows you to use your TextField :
ForEach($qlist) { $qItem in // <-- here $
if qItem.type == 0 {
HStack {
Text(qItem.text)
// Toggle("", isOn: $isOn[index])
Toggle("", isOn: $q1)
}
} else {
VStack {
Text(qItem.text)
// .lineLimit(2)
// .multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
// .frame(width: 300)
TextField("Enter your answer here:", text: $qItem.answer) {
}
}
}
}
and as I mentioned in my comment, remove the print(qlist[1]) and print(type(of: qlist[1])) in showForm,
because if qlist is empty or only has one element, you will get the index out of bound error .
Remember one element is qlist[0].
EDIT-1: full test code:
this is the code I used in my test. It does not give any index errors.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var qlist = [QuestionItem]()
#State private var isShowForm = false
#State private var q1 = true
#State private var answer = ""
#State private var isOn = [Bool]()
#State private var selectedTab = 0
func showForm() {
let url = URL(string: "http://127.0.0.1:3000/question")!
let task = URLSession.shared.dataTask(with: url) {
data, response, error in
if let error = error {
print(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
// print(response)
return
}
guard let data = data else {
return
}
do {
let list = try JSONDecoder().decode([QuestionItem].self, from:data)
qlist = list
// print(qlist[1])
for i in 0..<qlist.count {
isOn.append(qlist[i].type == 0)
}
print(isOn)
// print(isOn)
// print(type(of: qlist[1]))
isShowForm.toggle() // <--- here important
} catch {
print("error: ", error)
}
}
task.resume()
}
var body: some View {
Text("Hello, world!")
.padding()
Button("Open Form") {
// self.showForm()
// simulate showForm()
qlist = [
QuestionItem(id: 0, text: "text0", type: 0, answer: "0"),
QuestionItem(id: 1, text: "text1", type: 1, answer: "1"),
QuestionItem(id: 2, text: "text2", type: 2, answer: "2"),
QuestionItem(id: 3, text: "text3", type: 3, answer: "3")
]
isShowForm.toggle() // <--- here after qlist is set
}
if (isShowForm) {
TabView(selection: $selectedTab) {
ForEach($qlist) { $qItem in
if qItem.type == 0 {
HStack {
Text(qItem.text)
// Toggle("", isOn: $isOn[index])
Toggle("", isOn: $q1)
}
} else {
VStack {
Text(qItem.text)
// .lineLimit(2)
// .multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
// .frame(width: 300)
TextField("Enter your answer here:", text: $qItem.answer)
.border(.red)
}
}
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
.indexViewStyle(.page(backgroundDisplayMode: .always))
}
}
}
struct QuestionItem: Codable, Identifiable { // <-- here note the struct
var id: Int
var text: String
var type: Int
var answer: String
}

State is nil when showing sheet

For some reason, my selectedTask State is Empty when presenting the Sheet,
even if I set it on the onTapGesture.
What I'm I missing?
struct TasksTabView: View {
#State private var showComputedTaskSheet: Bool = false
#State var selectedTask: OrderTaskCheck?
var body: some View {
VStack(alignment: .leading) {
List {
ForEach(Array(tasks.enumerated()), id:\.1.title) { (index, task) in
VStack(alignment: .leading, spacing: 40) {
HStack(spacing: 20) {
PillForRow(index: index, task: task)
}.padding(.bottom, 30)
}.onTapGesture {
// Where I'm setting selectedTask
self.selectedTask = task
self.showComputedTaskSheet.toggle()
}
}
}
}.listStyle(SidebarListStyle())
}
.sheet(isPresented: $showComputedTaskSheet) {
// self.selectedTask is returns nil
showScreen(task: self.selectedTask!)
}
.onAppear {
UITableView.appearance().backgroundColor = .white
}
}
Since I have no access to your full project this example can help you to get the idea, you can use .sheet() with item initializer like aheze said.
The advantage is here you pass optional to input item and you receive unwrapped safe value to work!
struct ContentView: View {
#State private var customValue: CustomValue?
var body: some View {
Button("Show the Sheet View") { customValue = CustomValue(description: "Hello, World!") }
.sheet(item: $customValue){ item in sheetView(item: item) }
}
func sheetView(item: CustomValue) -> some View {
return VStack {
Text(item.description)
Button("Close the Sheet View") { customValue = nil }.padding()
}
}
}
struct CustomValue: Identifiable {
let id: UUID = UUID()
var description: String
}

How to Stop a user from passing the login page if their info is not correct IOS

In my IOS app, I'd like to send a message to a user who tries to login with bad credentials and notify them of this with a pop up. Currently, My database can recognize a login error but my swift code doesn't see the error condition until after it dismisses the login page and enters the app.
The flask/python code that accesses the database looks like this:
#app.route('/login', methods=['GET', 'POST'])
def login():
mydb = mysql.connector.connect(host="localhost", user="root", passwd="Pass", database = "events")
if request.method == 'POST':
mycursor = mydb.cursor()
username = request.form['username']
password = request.form['password']
mycursor.execute('SELECT* FROM accounts WHERE username = %s AND password = %s', (username, password,))
account = mycursor.fetchone()
if account:
try:
mydb.commit()
mydb.close()
except e:
# Rollback in case there is any error
print("Error: ", e)
mydb.rollback()
return make_response("Success!", 200)
else:
return make_response("username/password combination dne", 500)
The swiftui code that contacts the data base inside my app looks like this:
struct LogInView: View {
#State var username: String = ""
#State var password: String = ""
#State var email: String = "test#gmail.com"
#Binding var didLogin: Bool
#Binding var needsAccount: Bool
#State var errorString: String = ""
func send(_ sender: Any, completion: #escaping (String) -> Void) {
let request = NSMutableURLRequest(url: NSURL(string: "http://localhost/login")! as URL)
request.httpMethod = "POST"
self.username = "\(self.username)"
self.password = "\(self.password)"
self.email = "\(self.email)"
let postString = "username=\(self.username)&password=\(self.password)&c=\(self.email)"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
print("error=\(String(describing: error))")
//put variable that triggers error try again view here
self.didLogin = false
self.errorString = String(describing: error)
completion(self.errorString)
return
}else{
self.didLogin = true
completion(String(describing: error))
}
print("response = \(String(describing: response))")
let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("responseString = \(String(describing: responseString))")
if let httpResponse = response as? HTTPURLResponse {
self.errorString = String(httpResponse.statusCode)
}
}
task.resume()
}
var body: some View {
VStack{
Spacer()
WelcomeText()
UserImage()
TextField("Username", text: $username)
.padding()
.background(Color(.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
SecureField("Password", text: $password)
.padding()
.background(Color(.lightGray))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: {
self.send((Any).self){ array in
self.errorString = array
}/*
if self.errorString == "500"{
self.didLogin = false
}
else{
self.didLogin = true
}
}*/
},
label: {Text("LOGIN")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.orange)
.cornerRadius(15.0)})
.shadow(radius: 5)
.padding(.bottom, 10)
Button(action: {
self.needsAccount = true
}, label: {Text("Not a member yet? Sign up here")})
Spacer()
}.padding().background(Color.white).edgesIgnoringSafeArea(.all)
}
}
ContentView:
import SwiftUI
import Mapbox
import CoreLocation
struct ContentView: View {
#ObservedObject var annotationsVM: AnnotationsVM //= AnnotationsVM()
#ObservedObject var VModel: ViewModel //= ViewModel()
#ObservedObject var locationManager: LocationManager //= LocationManager()
#ObservedObject var data: DataFetcher
// #ObservedObject var mapViewCoordinator = MapViewCoordinator()
init() {
let vm = ViewModel()
VModel = vm
annotationsVM = AnnotationsVM(VModel: vm)
locationManager = LocationManager()
data = DataFetcher()
}
var userLatitude: CLLocationDegrees {
return (locationManager.lastLocation?.latitude ?? 0)
}
var userLongitude: CLLocationDegrees {
return (locationManager.lastLocation?.longitude ?? 0)
}
var lat: Double {
return (VModel.lat ?? 0)
}
var long: Double {
return (VModel.lon ?? 0)
}
var Userlat: Double {
return (VModel.userLatitude)
}
var Userlon: Double {
return (VModel.userLongitude)
}
//#State var searchedLocation: String = ""
#State private var annotationSelected: Bool = false
#State private var renderingMap: Bool = true
#State private var searchedText: String = ""
#State private var showResults: Bool = false
#State private var events: [eventdata] = []
#State private var showMoreDetails: Bool = false
#State private var didLogin: Bool = false
#State private var needsAccount: Bool = false
#State private var selectedAnnotation: MGLAnnotation? = nil
var body: some View {
VStack{
ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)){
MapView(annotationSelected: $annotationSelected, renderingMap: $renderingMap, visited: $annotationsVM.visited, showMoreDetails: $showMoreDetails, selectedAnnotation: $selectedAnnotation, VModel: VModel, locationManager: locationManager, aVM: annotationsVM, data: data, annos: $annotationsVM.annos)
.edgesIgnoringSafeArea(.all)
if showResults == true && searchedText.count >= 1 {
Text("").frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(Color.white).edgesIgnoringSafeArea(.all)
//this is pretty ghetto but whatever
}
VStack{
HStack(alignment: .top){
if showResults == false {
SettingsButton()
}
Spacer()
SearchBar(annotation: annotationsVM, VModel: VModel, searchedText: $searchedText, showResults: $showResults, showMoreDetails: $showMoreDetails)
// SearchBar(annotation: annotationsVM) { sender in
// self.searchedLocation = sender.searchText.text
// }
Spacer()
if showResults == false {
MessageButton()
}
}.padding()
//Update Annotation Button
// Button (action: {
// let delayInSeconds = 1.5
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
// self.annotationsVM.addNextAnnotation(address: "22 Sunset Ave, East Quogue, NY")
//
// print("\(self.annotationsVM.annos)")
// print("User Coords: \(self.VModel.userLatitude), \(self.VModel.userLongitude)")
// }
// }, label: {Text("Press to update annotation")})
if showResults == true && searchedText.count >= 1 {
SearchResults(VModel: VModel, annotation: annotationsVM, showResults: $showResults, searchedText: $searchedText)
}
Spacer()
HStack(alignment: .bottom) {
if renderingMap {
Text("The Map is Rendering...")
}
}
//Side Note: If the Create Event Button is pressed, the currently selected annotation is unselected, so that'll need to be fixed
HStack(alignment: .bottom) {
if annotationSelected {
CreateEventButton(annotation: annotationsVM, annotationSelected: $annotationSelected)
}
}.padding()
}
VStack {
Spacer()
HStack {
Spacer()
if annotationsVM.annotationPlacementFailed == true {
AnnotationPlacementErrorView(annotationPlacementFailed: $annotationsVM.annotationPlacementFailed, annotation: annotationsVM, searchedText: $searchedText)
}
Spacer()
}
Spacer()
}
VStack {
Spacer()
HStack{
Spacer()
if self.showMoreDetails == true {
MoreDetailsView(searchedText: $searchedText, showMoreDetails: $showMoreDetails, selectedAnnotation: $selectedAnnotation)
//Instead of passing in searchedText, we need to pass in the mapView...idk how though
}
Spacer()
}
Spacer()
}
if self.didLogin == false {
LogInView(didLogin: $didLogin, needsAccount: $needsAccount)
}
if self.needsAccount == true {
SignUpView(didLogin: $didLogin, needsAccount: $needsAccount)
}
}
}
}
}
I'm not sure if this is a database//server issue or swiftui/httpresponse issue. Any insight is greatly appreciated
how does it dismiss the login page? assuming you use didLogin to check, you should default didLogin to false. so the user is not logged in until didLogin = true, meaning it will wait till you get a response from your http request. something like this:
#State private var didLogin: Bool = false
if didLogin {
ShowSomeViewAfterLogin()
} else {
ShowLogin()
}

Async Next Screen Presentation in SwiftUI

i want to signup users when they click the signup button in my app. When the signup is complete and successfully created a user on the server side I want to present the next screen and only then.
In normal way I have a PresentationButton and set the destination and when somebody clicked the button the next screen is presented directly, but now it's async.
How to handle that?
Currently I have this PresentationButton:
PresentationButton(
Text(isSignIn ? "SignIn" : "Next").font(.headline).bold()
.frame(width: 100)
.padding(10)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(20)
, destination: HomeScreen()
)
That's with the suggestion by Uro Arangino:
struct PasswordView : View {
#State private var password = ""
#State private var showNextScreen = false
var loginMode: LoginType
#ObjectBinding var signupManager = SignUpManager()
var body: some View {
VStack {
// if self.signupManager.showModal { self.showNextScreen.toggle() } <- Can't do this
TwitterNavigationView(backBtnOn: false)
middleView()
Spacer()
bottomView()
}
}
func bottomView() -> some View {
return VStack(alignment: .trailing) {
Divider()
BindedPresentationButton(
showModal: $showNextScreen,
label: getCustomText((Text("Registrieren"))),
destination: HomeTabView(),
onTrigger: {
let user = User(name: "rezo", username: "ja lol ey", profileDescription: "YAS", email: "dbjdb#dedde.de", telephoneNumber: nil, profileImage: UIImage(named: "twitter-logo")!, bannerImage: UIImage(systemName: "star")!)
self.signupManager.signIn(forUser: user, password: "ultraSecure", loginMode: .email)
// UserDefaults.standard.set(true, forKey: "loggedIn")
})
}.padding([.leading, .trailing]).padding(.top, 5).padding(.bottom, 10)
}
}
My bindable object class
final class SignUpManager: BindableObject {
let didChange = PassthroughSubject<SignUpManager, Never>()
var showModal: Bool = false {
didSet {
didChange.send(self)
}
}
func signIn(forUser user: User, password: String, loginMode: LoginType) {
if loginMode == .email {
LoginService.instance.signupWithEmail(user: user, andPassword: password, completion: handleCompletion)
} else {
LoginService.instance.login(withPhoneNumber: user.telephoneNumber!, completion: handleCompletion)
}
}
private func handleCompletion(_ status: Bool) {
if status {
showModal = true
}
}
}
You can use a presentation button with a binding.
See: https://stackoverflow.com/a/56547016/3716612
struct ContentView: View {
#State var showModal = false
var body: some View {
BindedPresentationButton(
showModal: $isSignIn,
label: Text(isSignIn ? "SignIn" : "Next")
.font(.headline)
.bold()
.frame(width: 100)
.padding(10)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(20),
destination: HomeScreen()
)
}
}

Resources