SwiftUI AppStorage, UserDefaults and ObservableObject - swiftui

I have two different views, ContentView and CreateView.
In CreateView, I get user's inputs by textfield, and once user clicks on Save button, the inputs will be stored in AppStorage.
Then, I want to display the saved inputs on ContentView.
Here, I tried to use State & Binding but it didn't work out well.
How would I use the variable, that is created in CreateView, in ContentView?
what property should I use..
Thanks
Here's the updated questions with the code...
struct ContentView: View {
// MARK: - PROPERTY
#ObservedObject var appData: AppData
let createpage = CreatePage(appData: AppData())
var body: some View {
HStack {
NavigationLink("+ create a shortcut", destination: CreatePage(appData: AppData()))
.padding()
Spacer()
} //: HStack - link to creat page
VStack {
Text("\(appData.shortcutTitle) - \(appData.shortcutOption)")
}
}
struct CreatePage: View {
// MARK: - PROPERTY
#AppStorage("title") var currentShortcutTitle: String?
#AppStorage("option") var currentOption: String?
#Environment(\.presentationMode) var presentationMode
#ObservedObject var appData: AppData
var body: some View {
NavigationView{
ScrollView{
Text("Create a ShortCut")
.padding()
HStack {
TextField("what is the title?", text: $appData.titleInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
//.frame(width: 150, height: 60, alignment: .center)
.border(Color.black)
.padding()
} //: HStack - Textfield - title
.padding()
HStack (spacing: 10) {
TextField("options?", text: $appData.optionInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 80, height: 40, alignment: .leading)
.padding()
} //: HStack - Textfield - option
.padding()
Button(action: {
self.appData.shortcutTitle = self.appData.titleInput
self.appData.shortcutOption = self.appData.optionInput
UserDefaults.standard.set(appData.shortcutTitle, forKey: "title")
UserDefaults.standard.set(appData.shortcutOption, forKey: "option")
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Save")
.padding()
.frame(width: 120, height: 80)
.border(Color.black)
}) //: Button - save
.padding(.top, 150)
} //: Scroll View
} //: Navigation View
} //: Body
class AppData: ObservableObject {
#Published var shortcutTitle : String = "Deafult Shortcut"
#Published var shortcutOption : String = "Default Option"
#Published var titleInput : String = ""
#Published var optionInput : String = ""
}
So the problem here is that
when I put new inputs on CreatePage and tab the save button, the new inputs do not appear on ContentView page.The output keeps showing the default values of title and option, not user inputs.
If user makes a new input and hit the save button, I want to store them in AppStorage, and want the data to be kept on ContentView (didn't make the UI yet). Am I using the AppStorage and UserDefaults in a right direction?
If anyone have insights on these issues.. would love to take your advice or references.

You're creating instances of AppData in multiple places. In order to share data, you have to share one instance of AppData.
I'm presuming that you create AppData in a parent view of ContentView since you have #ObservedObject var appData: AppData defined at the top of the view (without = AppData()). This is probably in your WindowGroup where you also must have a NavigationView.
I removed the next (let createpage = CreatePage(appData: AppData())) because it does nothing. And in the NavigationLink, I passed the same instance of AppData.
struct ContentView: View {
// MARK: - PROPERTY
#StateObject var appData: AppData = AppData() //Don't need to have `= AppData()` if you already create it in a parent view
var body: some View {
// I'm assuming there's a NavigationView in a parent view
VStack { //note that I've wrapped the whole view in a VStack to avoid having two root nodes (which can perform differently in NavigationView depending on the platform)
HStack {
NavigationLink("+ create a shortcut", destination: CreatePage(appData: appData))
.padding()
Spacer()
} //: HStack - link to creat page
VStack {
Text("\(appData.shortcutTitle) - \(appData.shortcutOption)")
}
}
}
}
struct CreatePage: View {
// MARK: - PROPERTY
#AppStorage("title") var currentShortcutTitle: String?
#AppStorage("option") var currentOption: String?
#Environment(\.presentationMode) var presentationMode
#ObservedObject var appData: AppData
var body: some View {
NavigationView{
ScrollView{
Text("Create a ShortCut")
.padding()
HStack {
TextField("what is the title?", text: $appData.titleInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
//.frame(width: 150, height: 60, alignment: .center)
.border(Color.black)
.padding()
} //: HStack - Textfield - title
.padding()
HStack (spacing: 10) {
TextField("options?", text: $appData.optionInput)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 80, height: 40, alignment: .leading)
.padding()
} //: HStack - Textfield - option
.padding()
Button(action: {
appData.shortcutTitle = appData.titleInput
appData.shortcutOption = appData.optionInput
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Save")
.padding()
.frame(width: 120, height: 80)
.border(Color.black)
}) //: Button - save
.padding(.top, 150)
} //: Scroll View
} //: Navigation View
} //: Body
}
Regarding #AppStorage and UserDefaults, it's a little hard to tell what your intent is at this point with those. But, you shouldn't need to declare AppStorage and call UserDefaults on the same key -- #AppStorage writes to UserDefaults for you. Read more at https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-appstorage-property-wrapper

you can use a singleton ObservableObject that conforms to NSObject so you can observe everything even older apple objects like progress.
class appData : NSObject , ObservableObject {
static let shared = appData()
#Published var localItems = Array<AVPlayerItem>()
#Published var fractionCompleted : Double = 0
#Published var downloaded : Bool = false
#Published var langdentifier = UserDefaults.standard.value(forKey: "lang") as? String ?? "en" {
didSet {
print("AppState isLoggedIn: \(langIdentifier)")
}
}
var progress : Progress?
override init() {
}
}
then u can use it anywhere in your code like this:
appData.shared.langIdentifier == "en" ? .leading : .trailing

You should be able to simply put AppStorage objects in the ObservableClass and call them from there. There should be no need to put AppStorage in the View and then read from UserDefaults in the class.
class AppData: ObservableObject {
#AppStorage(keyForValue) var valueForKey: ValueType = defaultValue
...
}
Of course, you could make #Published property in the class and define the getter and setter for it so it reads and writes directly to the UserDefaults but at that point, you're just creating more work than simply using AppStorage from the beginning directly in the class.

Related

How to make a custom UIView Appear/Dissapear in SwiftUI

I have a CameraView in my app that I'd like to bring up whenever a button is to be presssed. It's a custom view that looks like this
// The CameraView
struct Camera: View {
#StateObject var model = CameraViewModel()
#State var currentZoomFactor: CGFloat = 1.0
#Binding var showCameraView: Bool
// MARK: [main body starts here]
var body: some View {
GeometryReader { reader in
ZStack {
// This black background lies behind everything.
Color.black.edgesIgnoringSafeArea(.all)
CameraViewfinder(session: model.session)
.onAppear {
model.configure()
}
.alert(isPresented: $model.showAlertError, content: {
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
model.alertError.primaryAction?()
}))
})
.scaledToFill()
.ignoresSafeArea()
.frame(width: reader.size.width,height: reader.size.height )
// Buttons and controls on top of the CameraViewfinder
VStack {
HStack {
Button {
//
} label: {
Image(systemName: "xmark")
.resizable()
.frame(width: 20, height: 20)
.tint(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
Spacer()
flashButton
}
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
Spacer()
flipCameraButton
}
.padding([.horizontal, .bottom], 20)
.frame(maxHeight: .infinity, alignment: .bottom)
}
} // [ZStack Ends Here]
} // [Geometry Reader Ends here]
} // [Main Body Ends here]
// More view component code goes here but I've excluded it all for brevity (they don't add anything substantial to the question being asked.
} // [End of CameraView]
It contains a CameraViewfinder View which conforms to the UIViewRepresentable Protocol:
struct CameraViewfinder: UIViewRepresentable {
class VideoPreviewView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
let session: AVCaptureSession
func makeUIView(context: Context) -> VideoPreviewView {
let view = VideoPreviewView()
view.backgroundColor = .black
view.videoPreviewLayer.cornerRadius = 0
view.videoPreviewLayer.session = session
view.videoPreviewLayer.connection?.videoOrientation = .portrait
return view
}
func updateUIView(_ uiView: VideoPreviewView, context: Context) {
}
}
I wish to add a binding property to this camera view that allows me to toggle this view in and out of my screen like any other social media app would allow. Here's an example
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView(showCamera: $showCamera)
}
}
I understand that the code to achieve this must be written inside the updateUIView() method. Now, although I'm quite familiar with SwiftUI, I'm relatively inexperienced with UIKit, so any help on this and any helpful resources that could help me better code situations similar to this would be greatly appreciated.
Thank you.
EDIT: Made it clear that the first block of code is my CameraView.
EDIT2: Added Example of how I'd like to use the CameraView in my App.
Judging by the way you would like to use it in the app, the issue seems to not be with the CameraViewFinder but rather with the way in which you want to present it.
A proper SwiftUI way to achieve this would be to use a sheet like this:
#State var showCamera: Bool = false
var body: some View {
mainTabView
.sheet(isPresented: $showCamera) {
CameraView()
.interactiveDismissDisabled() // Disables swipe to dismiss
}
}
If you don't want to use the sheet presentation and would like to cover the whole screen instead, then you should use the .fullScreenCover() modifier like this.
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView()
.fullScreenCover(isPresented: $showCamera)
}
}
Either way you would need to somehow pass the state to your CameraView to allow the presented screen to set the state to false and therefore dismiss itself, e.g. with a button press.

Dismissing view using ObservableObject class provokes Publishing changes from within view updates is not allowed

When dismissing a fullScreenCover using a variable inside an ObservableObject (lines commented with 1.-) it shows the "Publishing changes from within view updates is not allowed, this will cause undefined behavior." message in the console, but using a #State variable (lines commented with 2.-) does not show the warning. I do not understand why.
Here is the code:
import SwiftUI
final class DismissWarningVM: ObservableObject {
#Published var showAnotherView = false
}
struct DismissWarningView: View {
#StateObject private var dismissWarningVM = DismissWarningVM()
#State private var showAnotherView = false
var body: some View {
VStack {
HStack {
Spacer()
Button {
// 1.- This line provokes the warning
dismissWarningVM.showAnotherView = true
// 2.- This line DO NOT provokes the warning
//showAnotherView = true
} label: {
Text("Show")
}
}
.padding(.trailing, 20)
Spacer()
Text("Main view")
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.white)
// 1.- This line provokes the warning
.fullScreenCover(isPresented: $dismissWarningVM.showAnotherView) {
// 2.- This line DO NOT provokes the warning
//.fullScreenCover(isPresented: $showAnotherView) {
AnotherView()
}
}
}
struct AnotherView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
VStack(spacing: 30) {
Text("Another view")
Button {
dismiss()
} label: {
Text("Dismiss")
.foregroundColor(.red)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
}
}
struct DismissWarningView_Previews: PreviewProvider {
static var previews: some View {
DismissWarningView()
}
}
Fixed. It was a problem of the XCode version. I was trying with 14.0.1 version but after updating to 14.1 version the warning is no longer shown
#StateObject isn't designed for view model objects. In SwiftUI the View struct is the view model already you don't another one. Remember SwiftUI is diffing these structs and creating/updating/removing UIView objects automatically for us. If you use view model objects then you'll have viewModel object -> View struct -> UIView object which is a big mess and will lead to bugs. #StateObject is designed for when you need a reference type in an #State which isn't very often nowadays given we have .task and .task(id:) for asynchronous features.
You can achieve what you need like this:
struct WarningConfig {
var isPresented = false
// mutating func someLogic() {}
}
struct SomeView: View {
#State private var config = WarningConfig()
...
.fullScreenCover(isPresented: $config.isPresented) {
WarningView(config: $config)

SwiftUI: Updating a View to include a Custom Subview based on a user action is a separate view

so I am trying to have a view update to display a custom view based on a user selection from another view. This is a simple task app project I started to get a better understanding of SwiftUI and have hit my first major roadblock. The custom view is generated from a Tag object from Core Data, so it would be this information that is passed from View 2 to View 1.
I've marked where the update would take place as well as where the action is performed with TODOs. Hopefully I did a good job at explaining what I am hoping to accomplish, nothing I have tried seems to work. I am sure it's something simple but the solution is evading me.
View 1: View that needs to be updated when user returns
View 2: View where selection is made
The View that needs to be updated and its ViewModel.
struct AddTaskView: View {
//MARK: Variables
#Environment(\.managedObjectContext) var coreDataHandler
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = AddTaskViewModel()
#StateObject var taskListViewModel = TaskListViewModel()
#State private var title: String = ""
#State private var info: String = ""
#State private var dueDate = Date()
var screenWidth = UIScreen.main.bounds.size.width
var screenHeight = UIScreen.main.bounds.size.height
var body: some View {
VStack(spacing: 20) {
Text("Add a New Task")
.font(.title)
.fontWeight(.bold)
//MARK: Task.title Field
TextField("Task", text: $title)
.font(.headline)
.padding(.leading)
.frame(height: 55)
//TODO: Update to a specific color
.background(Color(red: 0.9, green: 0.9, blue: 0.9))
.cornerRadius(10)
//MARK: Task.tag Field
HStack {
Text("Tag")
Spacer()
//TODO: UPDATE TO DISPLAY TAG IF SELECTED OTHERWISE DISPLAY ADDTAGBUTTONVIEW
NavigationLink(
destination: TagListView(),
label: {
AddTagButtonView()
}
)
.accentColor(.black)
}
//MARK: Task.info Field
TextEditor(text: $info)
.frame(width: screenWidth - 40, height: screenHeight/4, alignment: .center)
.autocapitalization(.sentences)
.multilineTextAlignment(.leading)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black, lineWidth: 0.5)
)
//MARK: Task.dateDue Field
DatePicker(
"Due Date",
selection: $dueDate,
in: Date()...
)
.accentColor(.black)
.font(.headline)
Spacer()
Button(action: {
viewModel.addTask(taskTitle: title, taskInfo: info, taskDueDate: dueDate)
//Dismiss View if successful
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Add Task")
.frame(width: 150, height: 60)
.font(.headline)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(30)
})
}
.padding()
.navigationBarTitleDisplayMode(.inline)
}
}
final class AddTaskViewModel : ObservableObject {
var coreDataHandler = CoreDataHandler.shared
#Published var tag : Tag?
func addTask(taskTitle: String, taskInfo: String, taskDueDate: Date) {
let newTask = Task(context: coreDataHandler.container.viewContext)
newTask.title = taskTitle
newTask.info = taskInfo
newTask.dateCreated = Date()
newTask.dateDue = taskDueDate
newTask.completed = false
newTask.archived = false
coreDataHandler.save()
}
}
The View where the selection is made and its ViewModel
struct TagListView: View {
#FetchRequest(entity: Tag.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Tag.title, ascending: true)]) var tagList : FetchedResults<Tag>
#Environment(\.presentationMode) var presentationMode
#StateObject var viewModel = TagListViewModel()
var body: some View {
VStack {
HStack {
Text("Create a Tag")
.font(.system(size: 20))
.fontWeight(.medium)
Spacer()
NavigationLink(
destination: CreateTagView(),
label: {
Image(systemName: "plus.circle")
.font(.system(size: 25))
})
}
Divider()
.padding(.bottom, 10)
ScrollView(.vertical, showsIndicators: false, content: {
if tagList.count != 0 {
LazyVStack(spacing: 20) {
ForEach(tagList, id: \.self) { tag in
let tagColour = Color(red: tag.colourR, green: tag.colourG, blue: tag.colourB, opacity: tag.colourA)
Button {
//TODO: UPDATE ADDTASKVIEW TO DISPLAY THE SELECTED TAG
//Dismiss view
self.presentationMode.wrappedValue.dismiss()
} label: {
TagView(title: tag.title ?? "Tag", color: tagColour, darkText: false)
}
}
}
} else {
Text("Add your first tag.")
}
})
}
.padding()
}
}
final class TagListViewModel : ObservableObject {
}

SwiftUI TextEditor - save the state after completion of editing

Overview of the issue
I am building a note taking app and there is a note editing view where a few TextEditors are used to listen to users' input. Right now an environment object is passed to the this note editing view and I designed a save button to save the change. It works well except that users have to click the save button to update the model.
Expected behaviors
The text editor is expected to update the value of instances of the EnvironmentObject once the editing is done. Do not necessarily click the save button to save the changes.
below is the sample code of view
struct NoteDetails: View {
#EnvironmentObject var UserData: Notes
#Binding var selectedNote: SingleNote?
#Binding var selectedFolder: String?
#Binding var noteEditingMode: Bool
#State var title:String = ""
#State var updatedDate:Date = Date()
#State var content: String = ""
var id: Int?
var label: String?
#State private var wordCount: Int = 0
var body: some View {
VStack(alignment: .leading) {
TextEditor(text: $title)
.font(.title2)
.padding(.top)
.padding(.horizontal)
.frame(width: UIScreen.main.bounds.width, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
Text(updatedDate, style: .date)
.font(.subheadline)
.padding(.horizontal)
Divider()
TextEditor(text: $content)
.font(.body)
.lineSpacing(15)
.padding(.horizontal)
.frame(maxHeight:.infinity)
Spacer()
}
}
}
below is the sample code of edit func of the EnvironmentObject
UserData.editPost(label: label!, id: id!, data: SingleNote(updateDate: Date(), title: title, body: content))
Ways I have tried
I tried to add a onChange modifier to the TextEditor but it applies as soon as any change happens, which is not desired. I also tried a few other modifiers like onDisapper etc.
User data has #Published var NoteList: [String: [SingleNote]] and I tried to pass the $UserData.NoteList[label][id].title in to the TextEditor and it was not accepted either
Did not find sound solutions in the Internet so bring up this question here. Thanks for suggestions in advance!
I don't know exactly what you mean to save once the editing is done. Here are two possible approaches I found.
Note:
In the following demos, the text with blue background displays the saved text.
1. Saving when user dismisses keyboard
Solution: Adding a tap gesture to let users dismiss the keyboard when tapped outside of the TextEditor. Call save() at the same time.
Code:
struct ContentView: View {
#State private var text: String = ""
#State private var savedText: String = ""
var body: some View {
VStack(spacing: 20) {
Text(savedText)
.frame(width: 300, height: 200, alignment: .topLeading)
.background(Color.blue)
TextEditor(text: $text)
.frame(width: 300, height: 200)
.border(Color.black, width: 1)
.onTapGesture {}
}
.onTapGesture { hideKeyboardAndSave() }
}
private func hideKeyboardAndSave() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
save()
}
private func save() {
savedText = text
}
}
2. Saving after no changes for x seconds
Solution: Using Combine with .debounce to publish and observe only after x seconds have passed with no further events.
I have set x to 3.
Code:
struct ContentView: View {
#State private var text: String = ""
#State private var savedText: String = ""
let detector = PassthroughSubject<Void, Never>()
let publisher: AnyPublisher<Void, Never>
init() {
publisher = detector
.debounce(for: .seconds(3), scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
var body: some View {
VStack(spacing: 20) {
Text(savedText)
.frame(width: 300, height: 200, alignment: .topLeading)
.background(Color.blue)
TextEditor(text: $text)
.frame(width: 300, height: 200)
.border(Color.black, width: 1)
.onChange(of: text) { _ in detector.send() }
.onReceive(publisher) { save() }
}
}
private func save() {
savedText = text
}
}

SwiftUI: popover to persist (not be dismissed when tapped outside)

I created this popover:
import SwiftUI
struct Popover : View {
#State var showingPopover = false
var body: some View {
Button(action: {
self.showingPopover = true
}) {
Image(systemName: "square.stack.3d.up")
}
.popover(isPresented: $showingPopover){
Rectangle()
.frame(width: 500, height: 500)
}
}
}
struct Popover_Previews: PreviewProvider {
static var previews: some View {
Popover()
.colorScheme(.dark)
.previewDevice("iPad Pro (12.9-inch) (3rd generation)")
}
}
Default behaviour is that is dismisses, once tapped outside.
Question:
How can I set the popover to:
- Persist (not be dismissed when tapped outside)?
- Not block screen when active?
My solution to this problem doesn't involve spinning your own popover lookalike. Simply apply the .interactiveDismissDisabled() modifier to the parent content of the popover, as illustrated in the example below:
import SwiftUI
struct ContentView: View {
#State private var presentingPopover = false
#State private var count = 0
var body: some View {
VStack {
Button {
presentingPopover.toggle()
} label: {
Text("This view pops!")
}.popover(isPresented: $presentingPopover) {
Text("Surprise!")
.padding()
.interactiveDismissDisabled()
}.buttonStyle(.borderedProminent)
Text("Count: \(count)")
Button {
count += 1
} label: {
Text("Doesn't block other buttons too!")
}.buttonStyle(.borderedProminent)
}
.padding()
}
}
Tested on iPadOS 16 (Xcode 14.1), demo video included below:
Note: Although it looks like the buttons have lost focus, they are still interact-able, and might be a bug as such behaviour doesn't exist when running on macOS.
I tried to play with .popover and .sheet but didn't found even close solution. .sheet can present you modal view, but it blocks parent view. So I can offer you to use ZStack and make similar behavior (for user):
import SwiftUI
struct Popover: View {
#State var showingPopover = false
var body: some View {
ZStack {
// rectangles only for color control
Rectangle()
.foregroundColor(.gray)
Rectangle()
.foregroundColor(.white)
.opacity(showingPopover ? 0.75 : 1)
Button(action: {
withAnimation {
self.showingPopover.toggle()
}
}) {
Image(systemName: "square.stack.3d.up")
}
ModalView()
.opacity(showingPopover ? 1: 0)
.offset(y: self.showingPopover ? 0 : 3000)
}
}
}
// it can be whatever you need, but for arrow you should use Path() and draw it, for example
struct ModalView: View {
var body: some View {
VStack {
Spacer()
ZStack {
Rectangle()
.frame(width: 520, height: 520)
.foregroundColor(.white)
.cornerRadius(10)
Rectangle()
.frame(width: 500, height: 500)
.foregroundColor(.black)
}
}
}
}
struct Popover_Previews: PreviewProvider {
static var previews: some View {
Popover()
.colorScheme(.dark)
.previewDevice("iPad Pro (12.9-inch) (3rd generation)")
}
}
here ModalView pops up from below and the background makes a little darker. but you still can touch everything on your "parent" view
update: forget to show the result:
P.S.: from here you can go further. For example you can put everything into GeometryReader for counting ModalView position, add for the last .gesture(DragGesture()...) to offset the view under the bottom again and so on.
You just use .constant(showingPopover) instead of $showingPopover. When you use $ it uses binding and updates your #State variable when you press outside the popover and closes your popover. If you use .constant(), it will just read the value from you #State variable, and will not close the popover.
Your code should look like this:
struct Popover : View {
#State var showingPopover = false
var body: some View {
Button(action: {
self.showingPopover = true
}) {
Image(systemName: "square.stack.3d.up")
}
.popover(isPresented: .constant(showingPopover)) {
Rectangle()
.frame(width: 500, height: 500)
}
}
}