我试图定义一个帮助方法来为UIViews设置AutoLayout约束。理想情况下,它应该是UIView类中定义的函数anchor
,它以这种方式运行:
let A = UIView()
let B = UIView()
B.anchor(left: A.rightAnchor + 24, top: A.bottomAnchor, width: A.widthAnchor + 100, height: 200)
其中A.rightAnchor + 24
表示第二个视图的leftAnchor应该被约束到第一个视图的rightAnchor,常量为24。
以下是我为实现此行为而尝试做的事情:
为了简化,我们假设anchor(_ left:)
只有一个参数可以是:
NSLayoutXAxisAnchor
NSLayoutXAxisAnchor
+ CGFloat
protocol LayoutProtocol {
// LayoutType can be NSLayoutXAxisAnchor, NSLayoutYAxisAnchor or NSLayoutDimension
associatedtype LayoutType
var anchor: NSLayoutAnchor<LayoutType> { get }
var constant: CGFloat { get }
}
现在让NSLayoutXAxisAnchor
和NSLayoutYAxisAnchor
符合该协议:
extension NSLayoutXAxisAnchor: LayoutProtocol {
typealias LayoutType = NSLayoutXAxisAnchor
var anchor: NSLayoutAnchor<NSLayoutXAxisAnchor> { return self }
var constant: CGFloat { return 0 }
}
extension NSLayoutYAxisAnchor: LayoutProtocol {
typealias LayoutType = NSLayoutYAxisAnchor
var anchor: NSLayoutAnchor<NSLayoutYAxisAnchor> { return self }
var constant: CGFloat { return 0 }
}
NSLayoutAnchor<AnchorType>
和CGFloat
让我们定义一个符合该协议的结构:
struct LayoutOperation<T>: LayoutProtocol {
typealias LayoutType = T
var anchor: NSLayoutAnchor<T>
var constant: CGFloat
}
现在,sum运算符可以返回该结构:
func +<T>(left: NSLayoutAnchor<T>, right: CGFloat) -> LayoutOperation<T> {
return LayoutOperation<T>(anchor: left, constant: right)
}
最后,我们可以扩展UIView
类:
extension UIView {
func anchor<T: LayoutProtocol>(_ left: T? = nil) {
translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint]()
if let left = left {
constraints.append(leftAnchor.constraint(equalTo: left.anchor as! NSLayoutAnchor<NSLayoutXAxisAnchor>, constant: left.constant))
}
constraints.forEach { $0.isActive = true }
}
}
一切正常,我可以单独传递一个NSLayoutXAxisAnchor
,或者在右侧传递Int
或Double
。
现在让我们专注于概括结构以及向anchor()
添加其他参数。
LayoutProtocol
是通用的LayoutOperation<T>
也是一般性的NSLayoutAnchor
专业化不应该有问题,所以:
extension UIView {
func anchor<T: LayoutProtocol, U: LayoutProtocol>(left: T? = nil, top: U? = nil) {
translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint]()
if let left = left {
constraints.append(leftAnchor.constraint(equalTo: left.anchor as! NSLayoutAnchor<NSLayoutXAxisAnchor>, constant: left.constant))
}
if let top = top {
constraints.append(topAnchor.constraint(equalTo: top.anchor as! NSLayoutAnchor<NSLayoutYAxisAnchor>, constant: top.constant))
}
constraints.forEach { $0.isActive = true }
}
}
T
和U
是不同的泛型,因为水平和垂直锚点具有不同的特殊类型(总共3种泛型类型足以处理各种布局锚点。)
测试用例:
B.anchor(left: A.rightAnchor + 24, top: A.bottomAnchor)
B.anchor(left: A.rightAnchor + 24, top: A.bottomAnchor + 12)
B.anchor(left: A.rightAnchor, top: A.bottomAnchor)
B.anchor(left: A.leftAnchor, top: A.topAnchor + 120.5)
所有工作都按预期正常。
但如果我只传递一个参数,无论是左边还是顶部:
B.anchor(left: A.rightAnchor + 24)
B.anchor(top: A.bottomAnchor)
Swift不再编译,出现以下错误
Cannot convert call result type 'LayoutOperation<_>' to expected type '_?'
我还尝试添加where
来指定相关类型,但没有运气:
extension UIView {
func anchor<T: LayoutProtocol, U: LayoutProtocol>(left: T? = nil, top: U? = nil)
where T.LayoutType == NSLayoutXAxisAnchor,
U.LayoutType == NSLayoutYAxisAnchor {
translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint]()
if let left = left {
constraints.append(leftAnchor.constraint(equalTo: left.anchor, constant: left.constant))
}
if let top = top {
constraints.append(topAnchor.constraint(equalTo: top.anchor, constant: top.constant))
}
constraints.forEach { $0.isActive = true }
}
}
仍然是同样的错误。
有关如何解决此问题的任何建议?我对Swift来说相对较新,所以这肯定是处理像这样(酷)功能的最糟糕方式。除此之外,先谢谢!