2

I am trying to setup a success/error view on a controller via protocol and extensions.

What I want to achieve is that I want to get into the state where it is enough to implement the protocol on a controller, and from there get access to the successView (no additional boilerplate).

This is what I have so far:

protocol SucessViewProtocol where Self: UIViewController {
    
    func initSuccessView()

    var successView: UIView! { get set }
    var topConstraint: NSLayoutConstraint! { set get }

    func showSuccess()
    func hideSucess()
}

extension SucessViewProtocol {

    func showSuccess() {
        //animate displaying success message
    }

    func hideSucess() {
        //animate hiding success message
    }

    func initSuccessView()  {
        successView = UIView()
        topConstraint = NSLayoutConstraint()
        // init success view and top constraint
    }
}

Now when I implement the protocol on the controller it looks like this:

// MARK: SuccessView
extension ConsumingViewController: SucessViewProtocol {
    var successView: UIView! {
        get {
            //getter
        }
        set {
            //setter
        }
    }

    var topConstraint: NSLayoutConstraint! {
        get {
            //getter
        }
        set {
            //setter
        }
    }
 
}

I guess my problem is obvious because I get the successView and topConstraint as properties inside my controller that is implementing the SucessViewProtocol. I am initializing the properties from the protocol inside the extension, so what I need would be just an access to these properties (not declaring them again in my controller). I guess I am missing some "glue" part between the protocol - extension - controller

I want to be able to implement the protocol on a controller, call initSuccessView() and from there it should just be enough to call showSuccess and hideSuccess.

Edit:

This is how I want to use this construct:

class ConsumingViewController: UIViewController {
   func viewDidLoad() {
     initSuccessView()
     
     loadData()
   }

   private func loadData() {
     //successfullyloaded
     showSuccess()
   }

}

// MARK: SuccessView
extension ConsumingViewController: SucessViewProtocol {
  var successView: UIView! {
    get {
        //getter
    }
    set {
        //setter
    }
  } *PROBLEMATIC*

  var topConstraint: NSLayoutConstraint! {
    get {
        //getter
    }
    set {
        //setter
    }
  } *PROBLEMATIC*

}

As I said, the problem is that the properties successView and topConstraing are being redeclared inside ConsumingViewController (because they are part of the protocol). I would need to actually not be visibile inside the controller, but just being used inside the extension. But then there is the problem with stored properties inside extensions ...

rimes
  • 761
  • 1
  • 8
  • 25
  • Can you edit your question to include an example of how you want to use the protocol functions and why you can't. – Paulw11 Feb 01 '21 at 10:37
  • @JoakimDanielson can you elaborate? Just to be clear, I want to avoid a BaseViewController and declaring the successview inside every ViewController in which I want to use it – rimes Feb 01 '21 at 11:02
  • It sounds like you probably need to use inheritance and create a subclass of UIViewController. You are trying to fake multiple inheritance through a protocol but that isn't how it works. – Paulw11 Feb 01 '21 at 11:07
  • Not sure what you mean by multiple inheritance. I want to add a subview to a controller and have access to it. I just don't want to add that view to every controller manually but through some "automatism" (this said, my last resort of this automatism would be a BaseViewController) – rimes Feb 01 '21 at 11:13
  • Protocol conformance can't add stored properties. Inheritance can. You are trying to have it both ways, using composition and getting inherited prosperities and behaviour. That is what I mean by multiple inheritance. You want `ConsumingViewController` to inherit behaviour of `UIViewController` and `SuccessViewProtocol` but that just isn't how it works. – Paulw11 Feb 01 '21 at 11:16
  • Ok got it. I think I found some other way by just using an extension where I display the success message and remove it with a delay. This way I dont need a reference to it. – rimes Feb 01 '21 at 11:23

3 Answers3

1

May be you want this?

protocol SucessViewProtocol {
    func showSuccess()
    func hideSucess()
}

fileprivate struct Key {
    static var runtimeKey: Int = 0
}

extension SucessViewProtocol where Self: UIViewController  {
    var successView: UIView? {
        get {
            return objc_getAssociatedObject(self, &Key.runtimeKey) as? UIView
        }
        set {
            objc_setAssociatedObject(self, &Key.runtimeKey, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    func showSuccess() {
        successView = UIView()
        //animate displaying success message
        view.addSubview(successView)
    }

    func hideSucess() {
        //animate hiding success message
        successView?.removeFromSuperview()
    }
}
arturdev
  • 10,884
  • 2
  • 39
  • 67
  • I also thought about the tag, but the problem is that it is not 100% sure to work. I can set some random value for the tag, but it just needs someone along the way to come up with the same value, and "bum" I have a problem :) – rimes Feb 01 '21 at 10:52
  • I have looked into that too, there would be an even "swiftier" solution if this would be the way to go. Look here https://valv0.medium.com/computed-properties-and-extensions-a-pure-swift-approach-64733768112c – rimes Feb 01 '21 at 10:59
  • I wouldn't use that. the .debugDescription is only for debugging purposes and it shouldn't be used in a production code ever! – arturdev Feb 01 '21 at 11:00
  • This isn't the end :). I the last version they actually use the address as a unique identifier – rimes Feb 01 '21 at 11:04
0

add protocol variables inside main scope not extension scope UIViewController like this.

public class ViewController: UIViewController, SucessViewProtocol {
    var successView: UIView!
    var topConstraint: NSLayoutConstraint!    
}

By doing this you do not need to define getter and setter for properties

and inside ViewDidLoaded you can initSuccessView:

public override func viewDidLoad() {
        super.viewDidLoad()
        
        self.initSuccessView()
    }

and call custom function:

func show() {
        self.showSuccess()
    }
    
    func hide() {
        self.hideSucess()
    }
hessam
  • 404
  • 3
  • 10
  • 1
    But then I would need to have a "BaseViewController" (in this case ViewController) for all my ViewControllers, don't I? – rimes Feb 01 '21 at 11:01
  • yeah, You can use BaseViewController, otherwise you will have to copy the code for all ViewControllers if show SuccessView – hessam Feb 01 '21 at 11:05
  • Creating base classes is considered a bad practice (there are some exceptions but I think it would be an overkill to do it in this case). – rimes Feb 01 '21 at 11:09
  • There is nothing wrong with creating base classes if that is the solution you need. While composition may be favoured over inheritance, composition doesn't provide general implementation. If you need additional properties to meet your needs you either need to inherit them or declare them in each conforming class if you are using a protocol. – Paulw11 Feb 01 '21 at 11:12
0

Solution = Optional Porotocols

You just need add properties to the extension as well to make them optional.

protocol SucessViewProtocol where Self: UIViewController {

    func initSuccessView()

    var successView: UIView! { get set }
    var topConstraint: NSLayoutConstraint! { set get }

    func showSuccess()
    func hideSucess()
}

extension SucessViewProtocol {

    // ------ these 2 properties added ------
    var successView: UIView! { get{ nil } set{} }
    var topConstraint: NSLayoutConstraint! { get{ nil } set{} }

    func showSuccess() {}

    func hideSucess() {}

    func initSuccessView()  {
        successView = UIView()
        topConstraint = NSLayoutConstraint()
    }
}

Then, when you conform SucessViewProtocol on ConsumingViewController you won't require to implement protperties.

// MARK: SuccessView
extension ConsumingViewController: SucessViewProtocol {
    // There's NO compiler error here!
}
Mamad Farrahi
  • 394
  • 1
  • 5
  • 20