Search code examples
swiftgenericsany

Swift: "any generic" in function causes "_ErrorCodeProtocol has no member" error


Swift 5.8 using 'any generic' causes strange error (_ErrorCodeProtocol)

Hello! In my Pet project I want to reduce layout code for UIKit (I'm using programmatic UI layout). The idea is to make enum for most used cases in my pet like:

// view is a view controller's view
let searchArea = UIView()
view.addSubview(searchArea)
searchArea.applyConstraints(
   .alignTopWithTop(of: guide),
   .alignLeadingWithLeading(of: view),
   .alignTrailingWithTrailing(of: view),
   .alignHeight(with: view, mult: 0.1)
)

I really like it much more than:

let searchArea = UIView()
view.addSubview(searchArea)
object.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
  searchArea.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
  searchArea.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
  searchArea.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
  searchArea.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1, constant: 0)
])

Because UIViews should be aligned to other UIView childs as well as UIViewController safe Area (UILayoutGuide) I implemented this using generics:

protocol UISnapable {
    var leadingAnchor: NSLayoutXAxisAnchor { get }
    // Here are other anchors that UIView and UILayoutGuide have at the same time
    var centerYAnchor: NSLayoutYAxisAnchor { get }
}


extension UIView: UISnapable {}
extension UILayoutGuide: UISnapable {}

enum Constraints<T: UISnapable> {
    case alignTopWithTop(of: T, const: CGFloat = 0)
    // Other align cases here
    case setAspectWidth(ratio: CGFloat)
}


func applyConstraints(object: inout UIView, constraints: any UISnapable...) {
    object.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate(
        // Transforming input variadic to constraints
        constraints.map {
            switch $0 {
            case .alignTopWithTop(of: let of, const: let const):
                return object.topAnchor.constraint(
                    equalTo: of.topAnchor,
                    constant: const
                )
            // Other align cases here
            case .setAspectWidth(ratio: let ratio):
                return object.heightAnchor.constraint(
                    equalTo: object.widthAnchor,
                    multiplier: ratio
                )
            }
        }
    )
}

I use "any" keyword for generic UISnapable as it's was recently introduced in Swift for such cases.

But unfortunately, I get error "Type _ErrorCodeProtocol has no member alignTopWithTop"

What I am doing wrong? I dunno how _ErrorCodeProtocol is connected to my generic case.

I've checked source of _ErrorCodeProtocol and it lead to Foundation. But I didn't find its declaration somewhere in foundation. Also I didn't find information on it in Apple docs (https://developer.apple.com/search/?q=_ErrorCodeProtocol&type=Documentation)


Solution

  • You're passing a list of UISnapable, but you mean to pass a list of Constraints. In this case you're putting any in the wrong place.

    What I believe you mean is this:

    // Constraints are not generic, but they can hold any UISnapable in some cases
    enum Constraints {
        // Use `any UISnapable` rather than T
        case alignTopWithTop(of: any UISnapable, const: CGFloat = 0)
        // Other align cases here
        case setAspectWidth(ratio: CGFloat)
    }
    

    And then the method is (note that the inout here is unnecessary; UIView is a reference type):

    func applyConstraints(object: inout UIView, constraints: Constraints...) {
                                                             ^^^^^^^^^^^
    

    As for the error, the compiler is just getting confused because you're trying to switch on a non-enum ($0 here is any UISnapable). The error is not particularly helpful, and is not related to the underlying problem.