How can a ViewController communicate an action to a button in a View
Alex
Posted on May 8, 2023
As a beginner, I'm constantly struggling with communication between objects. So I decided to try some approaches to facilitate this communication.
I'm still in the beginning of my journey, so please feel free to correct my approach if you see something wrong.
UI Object Communication
I find separating Views
from ViewControllers
to be one of the most challenging aspects. In this tutorial, we will explore various methods for declaring an action method in a UIViewController
and setting it for a private UIButton
inside a UIView
.
By following the principle of Separation of Concerns, our View should be only responsible for the presentation layer, any action that is not related to the presentation logic must be delegated to another layer.
In this scenario a button action should not be handled by the View itself, so we need to find a way to "inject" this functionality somehow from the outside.
Using callbacks (closures)
One way to do this is to inject a function from the ViewController
inside our View
.
Let's imagine we have the following classes for our View
and ViewController
that represent the user interface and the logic for what to do when an event happens.
// View.swift
import UIKit
final class CustomView: UIView {
// We define the callback method we want to use in our button
var callback: (() -> Void)?
private lazy var button: UIButton = {
var button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
button.setTitle("Tap Me", for: .normal)
button.backgroundColor = .systemBlue
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(button)
// setting the target method to the button
button.addTarget(self, action: #selector(buttonHandler), for: .touchUpInside)
positioningButton()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func positioningButton() {
button.translatesAutoresizingMaskIntoConstraints = false
// centering the button to the center of the custom view
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
button.centerYAnchor.constraint(equalTo: self.centerYAnchor)
])
}
@objc
private func buttonHandler(_ sender: UIButton) {
// Calling the outside callback for the handler
// if the callback doesn't exist then it won't do anything
callback?()
}
}
// ViewController.swift
import UIKit
final class ViewController: UIViewController {
let customView = CustomView()
override func viewDidLoad() {
super.viewDidLoad()
// Setting the callback to the button
customView.callback = customAction
view.addSubview(customView)
customView.backgroundColor = .cyan
setupCustomViewConstrains()
}
func setupCustomViewConstrains() {
customView.translatesAutoresizingMaskIntoConstraints = false
// Making the customView to take all screen space
NSLayoutConstraint.activate([
customView.topAnchor.constraint(equalTo: view.topAnchor),
customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
customView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
func customAction() {
print("Button from View was tapped!")
}
}
Since we are not using Interface builder, we also need to tell SceneDelegate
to use our ViewController
as the root view controller, if we don't do this our screen will be black.
To solve this, let us open SceneDelegate.swift
and locate the method called func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
.
Then replace the body of the method (the code between the curly braces) with this:
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let viewController = ViewController()
window.rootViewController = viewController
self.window = window
window.makeKeyAndVisible()
Once we have all this code ready we can run the app. The button will appear in the center of the screen and if we tap it, the closure defined in the ViewController will be executed.
Conclusion
In this small tutorial, we created an app that communicates the view and the viewController programmatically using closures. This approach provides an effective way to facilitate communication between our objects. While there are other mechanisms to achieve this, such as delegation (which I'll cover in another post), closures offer a convenient solution.
This mechanism of communication is not restricted to only Views and ViewControllers, you can use with any type of objects you need.
I hope this tutorial helps you gain a better understanding of this topic and sheds some light on your learning process.
Thanks for reading.
Posted on May 8, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024