Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift3 Call function in parent out of function in child view

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. ...)
}
like image 491
Mercury Avatar asked Dec 19 '22 08:12

Mercury


1 Answers

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.

enter image description here

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.
}
like image 126
kennytm Avatar answered Feb 16 '23 01:02

kennytm