zarochintsev
zarochintsev

Reputation: 33

How to implement function like onAppear in SwiftUI?

So, I would like to create a custom view and add function. How can I implement a function like .onAppear(perform: (() -> Void)?)? My code does not work, onDismiss closure does not call in the DashboardView.

struct DashboardView: View {

  @State var employees = ["Alex", "Olga", "Mark"]
  @State var presentEmployeeView = false

  var body: some View {
    NavigationView {
      List {
        Section {
          Button(action: {
            self.presentEmployeeView = true
          }, label: {
            Text("All employees")
          }).buttonStyle(BorderlessButtonStyle())
        }
      }
    }
    .sheet(isPresented: $presentEmployeeView) {
      EmployeesView(employees: self.employees).onDismiss {
        self.presentEmployeeView = false
      }
    }
  }
}

struct EmployeesView: View {
  let employees: [String]

  @State private var onDismissClosure: (() -> Void)? = nil
  func onDismiss(perform action: (() -> Void)? = nil) -> some View {
    self.onDismissClosure = action
    return self
  }

  var body: some View {
    NavigationView {
      List {
        ForEach(employees) { employee in
          EmployeeCell(employee: employee)
        }
      }.navigationBarItems(leading:
        Button(action: {
          self.onDismissClosure?()
        }, label: {
          Text("Close")
       })
      )
    }
  }
}

Upvotes: 3

Views: 2027

Answers (2)

Jacob Sánchez
Jacob Sánchez

Reputation: 459

EDIT: See this answer first: https://stackoverflow.com/a/64256360/4517781

You can achieve this without the constructor wrapper by using a combination of:

  1. @Environment
  2. ViewModifier
    // define a custom type of environment key for closures
    private struct ClosureKey: EnvironmentKey {
        static let defaultValue: (() -> Void)? = {}
    }

    // define a new environment property
    extension EnvironmentValues {
        var myAction: (() -> Void)? {
            get { self[ClosureKey.self]}
            set { self[ClosureKey.self] = newValue }
        }
    }

    struct ChildView: View {
        @Environment(\.myAction) var action: (() -> Void)?
        
        var body: some View {
            Button(action: {
                self.action?()
            })
        }

        func myAction(_ perform: (() -> Void)?) -> some View { 
           environment(\.myAction, perform) 
        }
    }

The payoff:

    struct ParentView: View {
        var body: some View {
            ChildView()
                .myAction({print("hello world")})
        }
    }

In reality this is pure madness. I think it's vastly preferable to use an optional closure in the constructor. But perhaps this process can be simplified. After all, declaring ClosureKey only has to be done once. And perhaps MyActionModifier can be refactored to use a dynamic KeyPath? Which would leave the EnvironmentValues extension as the major task for each custom environment value.

However, I'm not even 100% sure this works yet.

Edit: Removed unnecessary modifier wrapper and simplified

Sources:

  1. https://developer.apple.com/documentation/swiftui/environmentkey
  2. https://useyourloaf.com/blog/swiftui-custom-environment-values/
  3. SwiftUI ViewModifier for custom View
  4. SwiftUI: store closure in environment?

Upvotes: 0

Asperi
Asperi

Reputation: 257533

Here is possible approach. Tested & worked with Xcode 11.4 / iOS 13.4

struct DashboardView: View {

  @State var employees = ["Alex", "Olga", "Mark"]
  @State var presentEmployeeView = false

  var body: some View {
    NavigationView {
      List {
        Section {
          Button(action: {
            self.presentEmployeeView = true
          }, label: {
            Text("All employees")
          }).buttonStyle(BorderlessButtonStyle())
        }
      }
    }
    .sheet(isPresented: $presentEmployeeView) {
      EmployeesView(employees: self.employees) {
        self.presentEmployeeView = false
      }
    }
  }
}

struct EmployeesView: View {
  let employees: [String]
  var onDismiss = {}

  var body: some View {
    NavigationView {
      List {
        ForEach(employees, id: \.self) { employee in
          Text("\(employee)")
        }
      }.navigationBarItems(leading:
        Button(action: {
          self.onDismiss()
        }, label: {
          Text("Close")
       })
      )
    }
  }
}

Update: possible alternate for usage with modifier:

    ...
    .sheet(isPresented: $presentEmployeeView) {
      EmployeesView(employees: self.employees).onDismiss {
        self.presentEmployeeView = false
      }
    }
  }
}

struct EmployeesView: View {
  let employees: [String]
  var onDismiss = {}

  func onDismiss(_ callback: @escaping () -> ()) -> some View {
    EmployeesView(employees: employees, onDismiss: callback)
  }

  var body: some View {
    NavigationView {
      List {
        ForEach(employees, id: \.self) { employee in
          Text("\(employee)")
        }
      }.navigationBarItems(leading:
        Button(action: {
          self.onDismiss()
        }, label: {
          Text("Close")
       })
      )
    }
  }
}

Upvotes: 1

Related Questions