'SwiftUI - Ability to call different methods with different parameters and returned results while going through another struct

I am currently writing a multi-platform app where multiple views execute code in another class. Each method called takes different parameters and returns different results (either string? for error or an object containing string? for error and a result?). Some of these methods take a while to execute so I hoped to display a message to the user letting them know to be patient and what was happening. Through a previous StackOverflow question I now know how to use a protocol and delegate to update a message to the user BUT it works by hardcoding the method call in the in-between struct rather than calling it direct from the original view. I started looking into closures but I'm not sure if that would work or play nice with the current setup. I guess I'm wondering if this is even possible and if so how would I go about doing it?

Here's the code so you can see what I mean.

  • Class where I original have all my methods called from the view. I hoped to add the delegate lines at the top to my actual class and go from there. In this example I have exampleOne that is currently being called and another exampleTwo method to show multiple methods with different stuff

      // representing my class with tons of methods using different parameters and returned results. Currently these are called direct from the ContentView equivalent where I use the result immediately.
      class LongMethodsCalled {
      var delegate: ClassMessageDelegate?
    
      init(delegate: ClassMessageDelegate) {
          self.delegate = delegate
      }
    
      // First method
      public func exampleOne(title:String) async -> String? {
          print("in example one - \(title)")
          self.delegate?.changeMessage(newMessage: "Starting example one")
          // a wait enable the text to update
          try! await Task.sleep(nanoseconds: 2_000_000_000)
          print("after first sleep")
          self.delegate?.changeMessage(newMessage: "Middle of example one")
          try! await Task.sleep(nanoseconds: 2_000_000_000)
          print("after second sleep")
          self.delegate?.changeMessage(newMessage: "About to return - example one")
          return "result of example one"
      }
    
    
      // Another method with different parameters and returned result
      public func exampleTwo(title:String) async -> (error:String?, result:String?) {
          print("in example two - \(title)")
          self.delegate?.changeMessage(newMessage: "Starting example two")
          try! await Task.sleep(nanoseconds: 2_000_000_000)
          print("after first sleep")
          self.delegate?.changeMessage(newMessage: "Middle of example two")
          try! await Task.sleep(nanoseconds: 2_000_000_000)
          print("after second sleep")
          self.delegate?.changeMessage(newMessage: "About to return - example two")
          return (error:nil, result:"Finished")
      }
      }
    
  • Protocol used in the above to update the text for the long running task

      protocol ClassMessageDelegate {
          func changeMessage(newMessage:String)
      }
    
  • Newly created in between struct that currently calls the previous method and displays the updating message to the user. Technically, this wouldn't know what method to call, parameters to use, or result returned. Hoped to get around this by returning Any? and calling a method based on an enum (hacky) but realized I couldn't even do this if the parameters are different for all of them

      // Called from the view
      struct TheLongTaskView: View, ClassMessageDelegate {
      @Binding var progressViewMessage: String
      @Binding var showProgress: Bool
    
      var body: some View {
      Text(progressViewMessage)
          .task {
              // Currently call the method from here but I want to be able to call different methods (in this case exampleOne and exampleTwo) with different parameters (known in ContentView and not here) and returning different objects to ContentView while updating the progreesViewMessage for the user.
              let longMethod = LongMethodsCalled(delegate: self)
              print("About to call method - \(self.progressViewMessage)")
              let error = await longMethod.exampleOne(title: "My Passed In Title")
              // Here I can use the returned value if it's an object or now if it passed if it's String?
              print("Error: \(error ?? "No error")")
              print("after printing error - \(self.progressViewMessage)")
              // save the error message and close view
              self.progressViewMessage = error!
              self.showProgress = false
          }
          .frame(width: 500, height: 300, alignment: .center)
          .padding(30)
      }
    
      // updating the text
      func changeMessage(newMessage:String) {
          print("changeMessage: \(newMessage)")
          self.progressViewMessage = newMessage
      }
      }
    
  • Main ContentView. In my main program this would be called from several different Views but here there's just one with two buttons which would ideally call exampleOne and exampleTwo in the above Class.

      struct ContentView: View {
      @State var showProgress:Bool = false
      @State var progressViewMessage: String = "will do something"
    
      var body: some View {
     NavigationView {
         VStack {
             HStack {
                 Text("Result: \(progressViewMessage)")
             }
    
             Text("")
    
             Button(action: {
                 print("Pressed Button 1")
                 progressViewMessage = "Pressed Button 1"
                 showProgress = true
             }, label: {
                 // text will be return value
                 // so one can see that it ran
                 Text("Button 1")
             })
             .sheet(isPresented: $showProgress, content: {
                 // create the vue that will display the progress
                 TheLongTaskView(progressViewMessage: $progressViewMessage, showProgress: $showProgress)
             })
    
             Text("")
    
             Button(action: {
                 print("Pressed Button 2")
                 progressViewMessage = "Pressed Button 2"
                 showProgress = true
             }, label: {
                 // text will be return value
                 // so one can see that it ran
                 Text("Button 2")
             })
             .sheet(isPresented: $showProgress, content: {
                 // create the vue that will display the progress
                 TheLongTaskView(progressViewMessage: $progressViewMessage, showProgress: $showProgress)
             })
         }
     }
     }
     }
    

Thank you for any help you can provide. Would love to know if my idea is possible in this way or something else that gets me the result I'd want. Thanks



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source