0

I'd like to do something like:

public enum LayoutEdge
{
    case top
    case right
    ...
}

func anchorForLayoutEdge(_ edge : LayoutEdge) -> NSLayoutAnchor {
    switch edge
    {
    case .top:      return topAnchor
    case .right:    return rightAnchor
    ... 
    }
}

public func constrain_edge(_ edge : LayoutEdge,
                           toEdge : LayoutEdge,
                           view : UIView) -> NSLayoutConstraint{
    let a1 = anchorForLayoutEdge(edge)
    let a2 = anchorForLayoutEdge(toEdge)
    return a1.constraint(equalTo: a2))
}

But this does not compile. It fails in anchorForLayoutEdge. Xcode suggests changing the return type to NSLayoutAnchor which seems wrong. How can I make it so that it returns the correct NSLayoutXAxisAnchor or NSLayoutYAxisAnchor depending on the edge specified?

GoldenJoe
  • 7,874
  • 7
  • 53
  • 92

1 Answers1

1

Swift needs to be able to determine the types at compile-time but you try to return either NSLayoutAnchor<NSLayoutXAxisAnchor> or NSLayoutAnchor<NSLayoutYAxisAnchor> objects depending on the passed edge parameter.

What you could do is is split up your edges into edges related to x-axis and y-axis:

extension UIView
{
    public enum XLayoutEdge {
        case right
        // ...
    }

    public enum YLayoutEdge {
        case top
        // ...
    }

    func anchor(for edge: XLayoutEdge) -> NSLayoutAnchor<NSLayoutXAxisAnchor> {
        switch edge
        {
        case .right: return rightAnchor
        // ...
        }
    }

    func anchor(for edge: YLayoutEdge) -> NSLayoutAnchor<NSLayoutYAxisAnchor> {
        switch edge
        {
        case .top: return topAnchor
        // ...
        }
    }

    public func constrain(edge edge1: XLayoutEdge, to edge2: XLayoutEdge, of view: UIView) -> NSLayoutConstraint {
        return anchor(for: edge1).constraint(equalTo: view.anchor(for: edge2))
    }

    public func constrain(edge edge1: YLayoutEdge, to edge2: YLayoutEdge, of view: UIView) -> NSLayoutConstraint {
        return anchor(for: edge1).constraint(equalTo: view.anchor(for: edge2))
    }

    func useEdges(view: UIView)
    {
        _ = constrain(edge: .right, to: .right, of: view)
        _ = constrain(edge: .top, to: .top, of: view)
    }
}

It would get even worse because you would have to consider NSLayoutDimension, too. You could play around with generics but you'd probably end up replicating in some way what Apple has already put in place for you :).

That's why I think you are working against the system here. Taking a step back, why not work with anchors directly?

extension UIView
{
    func useAnchors(view: UIView)
    {
        _ = rightAnchor.constraint(equalTo: view.rightAnchor)
        _ = topAnchor.constraint(equalTo: view.bottomAnchor)
    }
}

If you want to write your own convenience functions you could do it like this:

extension UIView
{
    public func constrain<T>(_ anchor1: NSLayoutAnchor<T>, to anchor2: NSLayoutAnchor<T>) -> NSLayoutConstraint {
        return anchor1.constraint(equalTo: anchor2)
    }

    func useYourOwnFunctions(view: UIView)
    {
        _ = constrain(rightAnchor, to: view.rightAnchor)
        _ = constrain(topAnchor, to: view.bottomAnchor)
    }
}
thm
  • 1,217
  • 10
  • 12