I have a child UIView class instantiated in an UIViewController, the UIViewController has a function called sigIn(){}. I need to call this function from within UIView.
I tried somehow using the self.superview and casting it into a UIViewController but this did not work. I found a method where you use a listener to detect the trigger but I think this is not a good solution. Is there anther simple way of accessing functions in the paren view form the subview ?
class SignUIView: UIView {
func signInUIButtonH(sender: UIButton) {
("Call parent signIn() function")
}
(init etc. ...)
}
Firstly, View and a View Controller are two different concepts. .superview
will not give you a View Controller, and casting it will only crash the program.
While it is possible to find out the current View Controller, it is very unidiomatic in your use case because you cannot be sure that View Controller has the signIn()
function, and you can't even ensure the "current View Controller" is the View Controller of the view.
iOS typically use the "delegate pattern" instead. In your case, first you define a protocol for the signIn()
function:
protocol SignInDelegate {
func signIn()
}
Then, the View needs to provide a variable of this protocol. This variable is called the delegate. Whenever we want to sign in, just call the delegate's signIn()
function.
The variable should be a weak reference to avoid strong reference cycle. The protocol also needs to be class-bound, otherwise the compiler will complain.
protocol SignInDelegate: class { // <-- needs :class for weak
func signIn()
}
class SignUIView: UIView {
weak var delegate: SignInDelegate? // <-- delegate
func signInUIButtonH(sender: UIButton) {
delegate?.signIn() // <-- call the delegate
}
}
Next we adapt the protocol to the View Controller:
class SignInViewController: UIViewController, SignInDelegate { // <-- adapt the protocol
func signIn() { // <-- implement the function
print("sign in")
}
override func viewDidLoad() {
super.viewDidLoad()
signInView.delegate = self // <-- tell the subview we will be the delegate.
}
}
We usually expose this delegate to Interface Builder so we could simply connect the View to the View Controller to assign the delegate.
This is done by adding @IBOutlet
to the delegate field. Unfortunately, Xcode only recognizes AnyObject
/NSObject
for IBOutlet which defeats the whole purpose of using the delegate pattern for type-safety. So we need to introduce some ugly hack to workaround it for IB. The protocol now also needs @objc
because resolving these IB connections requires the Objective-C runtime.
@objc protocol SignInDelegate { // <-- needs @objc for IB (:class is implied by @objc)
func signIn()
}
class SignUIView {
// Blame Xcode for this mess. See https://stackoverflow.com/a/42227800/224671
#if TARGET_INTERFACE_BUILDER
@IBOutlet weak var delegate: AnyObject?
#else
weak var delegate: SignInDelegate?
#endif
func signInUIButtonH(sender: UIButton) {
delegate?.signIn()
}
}
class SignInViewController: UIViewController, SignInDelegate { // <-- adapt the protocol
func signIn() { // <-- implement the function
print("sign in")
}
// Note: no need to set signInView.delegate manually.
}