Reputation: 33
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
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:
// 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:
Upvotes: 0
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