iosswiftextension-methodsinout

error "Value of type 'UIViewController' has no member..." when moving a func inside extension


I need to move a method for adding and removing a logging view inside an Extension, in order to give it to every controller. to do so I added a inout UIVew parameter to original method, where I used a global var for the view. no I have this error

Value of type 'UIViewController' has no member 'containerForLoading'

removing self from self.containerForLoading will give error:

Escaping closure captures 'inout' parameter 'containerForLoading'

inside the animate closure (see the comment) is all wrong the entire process or I am lost at the last step?

extension UIViewController {
        
    func showLoadingView(containerForLoading: inout UIView, uponView: UIView) {
        
           containerForLoading = UIView(frame: uponView.bounds)
           uponView.addSubview(containerForLoading)
           
           containerForLoading.backgroundColor = .white
           containerForLoading.alpha = 0
           UIView.animate(withDuration: 0.24) { self.containerForLoading.alpha = 0.8 } //here the error
           let activivityIndicator = UIActivityIndicatorView()
           containerForLoading.addSubview(activivityIndicator)
           
           activivityIndicator.translatesAutoresizingMaskIntoConstraints = false
           
           
           NSLayoutConstraint.activate([
               
               activivityIndicator.centerYAnchor.constraint(equalTo: uponView.centerYAnchor),
               activivityIndicator.centerXAnchor.constraint(equalTo: uponView.centerXAnchor)
               
           ])
           
           activivityIndicator.startAnimating()
       }
       
    func removeLoading(containerForLoading: inout UiView, uponView: UIView) {
           
           containerForLoading.removeFromSuperview()
           
       }
    
}

this is the code inside the original viewController

using this var

var containerForLoading = UIView()

called this way when needed

self.showLoadingView(uponView: self.view)
extension ViewController {
    
    func showLoadingView(uponView: UIView) {
        containerForLoading = UIView(frame: uponView.bounds)
        uponView.addSubview(containerForLoading)

        containerForLoading.backgroundColor = .white
        containerForLoading.alpha = 0
        UIView.animate(withDuration: 0.24) { self.containerForLoading.alpha = 0.8 }
        let activivityIndicator = UIActivityIndicatorView()
        containerForLoading.addSubview(activivityIndicator)

        activivityIndicator.translatesAutoresizingMaskIntoConstraints = false


        NSLayoutConstraint.activate([

            activivityIndicator.centerYAnchor.constraint(equalTo: uponView.centerYAnchor),
            activivityIndicator.centerXAnchor.constraint(equalTo: uponView.centerXAnchor)

        ])

        activivityIndicator.startAnimating()
    }

    func removeLoading(uponView: UIView) {

        containerForLoading.removeFromSuperview()

    }
    
    
}

Solution

  • You could make loadingContainerTag a local variable, and change the parameter name to something else. Then assign to the parameter just after you create the container view:

    extension UIViewController {
        func showLoadingView(containerForLoadingProperty: inout UIView, uponView: UIView) {
            // local variable!
            let containerForLoading = UIView(frame: uponView.bounds)
            
            // also set property
            containerForLoadingProperty = containerForLoading
            
            uponView.addSubview(containerForLoading)
            containerForLoading.backgroundColor = .white
            containerForLoading.alpha = 0
            
            // no "self."!
            UIView.animate(withDuration: 0.24) { containerForLoading.alpha = 0.8 }
            // ... everything else is the same
    

    removeLoading could just have one non-inout parameter:

    func removeLoading(containerForLoadingProperty: UIView) {
        containerForLoadingProperty.removeFromSuperview()
    }
    

    But...


    It is very weird for a method that shows a loading indicator, to need an inout parameter. It shouldn't assign to a special property that the caller provides. It should just show the loading indicator!

    The purpose of your containerForLoading property is so that in removeLoading, you know which view to remove. If you don't store the containerForLoading view somewhere in a property, you wouldn't know which view to remove, right? Well, we can use the tag property of a view to identify views, so you can just make containerForLoading a local variable, and later in removeLoading, use its tag to find it.

    extension UIViewController {
        
        static let loadingContainerTag = <a number you like>
    
        func showLoadingView(uponView: UIView) {
            // local variable!
            let containerForLoading = UIView(frame: uponView.bounds)
            uponView.addSubview(containerForLoading)
            containerForLoading.backgroundColor = .white
            containerForLoading.alpha = 0
            
            // set tag
            containerForLoading.tag = UIViewController.loadingContainerTag
            
            // no "self."!
            UIView.animate(withDuration: 0.24) { containerForLoading.alpha = 0.8 }
            let activivityIndicator = UIActivityIndicatorView()
            containerForLoading.addSubview(activivityIndicator)
            activivityIndicator.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                activivityIndicator.centerYAnchor.constraint(equalTo: uponView.centerYAnchor),
                activivityIndicator.centerXAnchor.constraint(equalTo: uponView.centerXAnchor)
            ])
            activivityIndicator.startAnimating()
        }
        func removeLoading(uponView: UIView) {
            // find view with the tag
            uponView.viewWithTag(UIViewController.loadingContainerTag)?.removeFromSuperview()
        }
    }