Usually when we deal with interactions with SwiftUI views, we often use closures to determine the actions we want to perform when different events occur. For example, the following AddItemView
has two interactive elements, TextField and Button, both of which allow the user to add a new Item
to the application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | struct AddItemView: View { var handler: (Item) -> Void @State private var title = "" var body: some View { HStack { TextField("Add item", text: $title, onCommit: { guard !title.isEmpty else { return } let item = Item(title: title) handler(item) title = "" } ) Button("Add") { let item = Item(title: title) handler(item) title = "" } .disabled(title.isEmpty) } } } |
Apart from the leading guard
statement in the text field’s onCommit
action (which won’t be necessary if we are disabling the button when the text is empty), our two closures are exactly the same, so it would be good to get rid of the source of duplicate code by moving those actions away from the body
in the view. One way to do that is to create closures using a computed property. That will let us define our logic once, and if we also include the guard statement our TextField needs, then we could use the same closure implementation for both UI controls cuar. we:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private extension AddItemView { var addAction: () -> Void { return { guard !title.isEmpty else { return } let item = Item(title: title) handler(item) title = "" } } } |
With the above, now we can simply pass addAction
new addAction
property to both subviews and we’ve successfully eliminated our duplication of code and the implementation of the view body is now much more compact. :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | struct AddItemView: View { var handler: (Item) -> Void @State private var title = "" var body: some View { HStack { TextField("Add item", text: $title, onCommit: addAction ) Button("Add", action: addAction) .disabled(title.isEmpty) } } } |
While the above is a perfectly good solution, there is another option, which may not initially be obvious in the SwiftUI context, and that is to use the same technique as using UIKit’s target / action pattern. – by defining our action handler as a method, not a closure. To do that, let’s first refactor our addAction
property from the previous one into an addItem
method like this:
1 2 3 4 5 6 7 8 9 10 11 12 | private extension AddItemView { func addItem() { guard !title.isEmpty else { return } let item = Item(title: title) handler(item) title = "" } } |
Then, just like how we previously passed our addAction
property to both our TextField
and Button
, we can now do the same thing with our addItem
method – this method gives us the following implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | struct AddItemView: View { var handler: (Item) -> Void @State private var title = "" var body: some View { HStack { TextField("Add item", text: $title, onCommit: addItem ) Button("Add", action: addItem) .disabled(title.isEmpty) } } } |
When working with SwiftUI, we fall into the trap of thinking that the view’s layout, subviews, and actions of a given view all need to be defined in its body
, which – if we think about it – is precisely. a common approach. leads to a large view controller when working with UIKit. However, thanks to SwiftUI’s highly synthetic design, dividing the view’s body into separate parts is often quite easy, and may not even require creating any new View
types. Sometimes all we have to do is extract some of our logic into a separate method, and we’ll create a piece of code that is much more elegant, easy to read, and more maintainable.
Hope the article will be useful to you.
Reference: https://www.swiftbysundell.com/tips/passing-methods-as-swiftui-view-actions/