0

So I have a sheet like this

      .sheet(item: $logMyPractice, content: { item in
          AssesmentRecorderView(
            assessment: DrillModel(
              questions: [
                .init(prompt: "Question 1", resultValue: .integer),
              ]
            ),
            completion: { date, answers in
              print("response", date, answers)
              // custom logic, save in the db
            })
          .presentationDetents([.fraction(0.85), .large ])
        }

What is happening is that I need to open that sheet from a different views. I could copy/paste it to the separate views but it's not following a DRY concept.

I could create an ObservableObject in the main view that holds information about the sheet and attach it as a environmentObject, but I wonder if there's a better approach?

breq
  • 24,412
  • 26
  • 65
  • 106

1 Answers1

0

Here comes the power of the ViewModifier

You can do something like this

    struct ContentView: View {
    
    @State var isPresented: Bool = false
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Button {
                isPresented = true
            } label: {
                Text("Hello, world!")
            }

        }
        .customSheet(isPresented: $isPresented) { singleString, arrayString in
            print(singleString)
            print(arrayString)
        }
    }
 }
    
    struct CustomSheetViewModifier: ViewModifier {
        
        @Binding var isPresented: Bool
        
        let completionHandler: ((String, [String]) -> Void)?
        
        func body(content: Content) -> some View {
            content
                .sheet(isPresented: $isPresented) {
                    Button {
                        completionHandler?("Done", ["Button 1", "Button 2"])
                        isPresented = false
                    } label: {
                        Text("Click me")
                    }
    
                }
        }
    }

extension View {
    func customSheet(isPresented: Binding<Bool>, completionHandler: ((String, [String]) -> Void)?) -> some View {
        modifier(CustomSheetViewModifier(isPresented: isPresented, completionHandler: completionHandler))
    }
}

and in your case you will replace the body of the CustomeSheetViewModifier with this

func body(content: Content) -> some View {
        content
            .sheet(isPresented: $isPresented) {
                AssesmentRecorderView(
                    assessment: DrillModel(
                        questions: [
                            .init(prompt: "Question 1", resultValue: .integer),
                        ]
                    ),
                    completion: { date, answers in
                        completionHandler(date, answers)
                    })
                .presentationDetents([.fraction(0.85), .large ])
            }
    }

assuming that the date is a string and the answers is an array of Strings you of course can replace the completionHandler parameters with whatever you like

Ahmed Mohiy
  • 190
  • 1
  • 7
  • this is more less what I have now. Now Imagine you have a `ContentView` and `DetailView`. Both should have the same `.sheet`. One solution is to copy over and over the same code, so you will have `.customSheet(isPresented: $isPresented)` in both views. – breq Mar 03 '23 at 07:58
  • I wonder if ther's a good approach to add a `.customSheet(isPresented: $isPresented)` to the `MainView` and from `ContentView` open that sheet. Assuming that `ContentView` and `DetailView` are 4th child views of MainView. One solution is like I've already mentioned, to put an ObservaleObject in the MainView and attach it as an `environmentObject` but I don't know if that's the right way to do it – breq Mar 03 '23 at 08:01
  • 1
    I totally understand what you are trying to do but keep in mind .sheet is a modifier like .frame and .foregroundColor so it's normal to repeat it but whatever you put inside the .sheet modifier closure it should't be repeated so what's why the best practice is to create customView and add it to the .sheet closure but in your case you have the same .sheet with the same customView inside the closure will be repeated for multiple views so the best practice here is to make viewModifier and instead of writing .sheet and closure you will write .customSheet() and that's it – Ahmed Mohiy Mar 03 '23 at 11:23
  • The approach you are suggesting is the to put something in the environment and ad the sheet to the mainView it will work but you have to make it bindable so it can opens the sheet you will do something like this https://stackoverflow.com/questions/69731360/get-a-binding-from-an-environment-value-in-swiftui but i don't like to write the code like this a always try to make each component can be added to any place in my code with minimal modification at possible so imagine that you want to remove DetailView and add it to another parent than MainView you will have to add .sheet to that parent – Ahmed Mohiy Mar 03 '23 at 11:28
  • And you that parent will not use that sheet at all and it's there because the DetailView needs it. Now you have two solutions and they both work but you have to choose what fits your code more. – Ahmed Mohiy Mar 03 '23 at 11:32