FixFlex
是一个基于 NSLayoutAnchor API 构建的简单而强大的 Auto Layout 库,是对 Visual Format Language 的一种 Swift 化的类型安全重新构想。
NSLayoutConstraint
和 UILayoutGuide
UIStackView
的轻量级替代方案translatesAutoresizingMaskIntoConstraints
设置为 false假设我们想要创建像这样的布局
大多数视图和间距具有固定的宽度 (Fix
),而标题和副标题的宽度是弹性的,旨在占据剩余空间 (Flex
)
parent.fx.hstack(Fix(15),
Fix(iconView, 44),
Fix(15),
Flex([titleLabel, subtitleLabel]),
Fix(15),
Fix(chevron, 20),
Fix(15))
我们使用 Fix
在顶部进行间距设置。底部间距应至少为 15pt,以应对标签高度小于图标高度的情况
parent.fx.vstack(Fix(15),
Fix(iconView, 44),
Flex(min: 15))
parent.fx.vstack(Fix(15),
Flex(titleLabel),
Flex(subtitleLabel),
Fix(15))
为了使 chevron 居中,我们使用 Fill
确保顶部间距等于底部间距
parent.fx.vstack(Fill(),
Fix(chevron, 30),
Fill())
就是这样! 最好的部分是 FixFlex 布局代码非常容易修改,可以轻松插入额外的填充或视图,而无需重新连接约束。
FixFlex
提供了两个函数用于水平 (hstack
) 和垂直 (vstack
) 布局视图,可通过 view.fx.*
命名空间访问。
您可以指定 startAnchor
/endAnchor
以在任意锚点之间而不是视图边缘之间布局项目。 startOffset
/endOffset
用于分别从 startAnchor
和 endAnchor
添加间距或偏移。
默认情况下,hstack
在自然定位模式下工作,并使用 leadingAnchor
/trailingAnchor
进行操作。 此设置确保布局针对从右到左的语言进行镜像。 但是,可以通过启用 useAbsolutePositioning
标志来覆盖此行为。 当此标志设置为 true 时,hstack
会切换为使用 leftAnchor
/rightAnchor
进行布局定位。
func hstack(
startAnchor: NSLayoutXAxisAnchor? = nil, // if nil, we use leadingAnchor or leftAnchor
startOffset: CGFloat = 0,
endAnchor: NSLayoutXAxisAnchor? = nil, // if nil, we use trailingAnchor or rightAnchor
endOffset: CGFloat = 0,
useAbsolutePositioning: Bool = false, // if true, we use leftAnchor/rightAnchor based positioning (force Left-To-Right)
_ intents: SizingIntent...
) -> StackingResult
func vstack(
startAnchor: NSLayoutYAxisAnchor? = nil, // if nil, we use topAnchor
startOffset: CGFloat = 0,
endAnchor: NSLayoutYAxisAnchor? = nil, // if nil, we use bottomAnchor
endOffset: CGFloat = 0,
_ intents: SizingIntent...
) -> StackingResult
SizingIntent
本质上是用于计算宽度或高度的指令
UILayoutGuide
)SizingIntent
的具体实例可以使用专门的构建器函数创建
用于指定视图/spacer 的确切大小。
func Fix(_ value: CGFloat) -> SizingIntent
func Fix(_ view: _View, _ value: CGFloat) -> SizingIntent
func Fix(_ views: [_View], _ value: CGFloat) -> SizingIntent
适用于动态变化的大小。 可选地,可以为 hugging 和 compression resistance 指定最小/最大约束和就地优先级设置。
func Flex(min: CGFloat? = nil, max: CGFloat? = nil) -> SizingIntent
func Flex(_ view: _View, min: CGFloat? = nil, max: CGFloat? = nil, huggingPriority: _LayoutPriority? = nil, compressionResistancePriority: _LayoutPriority? = nil) -> SizingIntent
func Flex(_ views: [_View], min: CGFloat? = nil, max: CGFloat? = nil, huggingPriority: _LayoutPriority? = nil, compressionResistancePriority: _LayoutPriority? = nil) -> SizingIntent
Fill
允许视图/spacer 根据其权重成比例地占据可用的自由空间。 它特别适用于实现相等的间距、居中元素或设计对称布局(如表格或网格)。
func Fill(weight: CGFloat = 1.0) -> SizingIntent
func Fill(_ view: _View, weight: CGFloat = 1.0) -> SizingIntent
func Fill(_ views: [_View], weight: CGFloat = 1.0) -> SizingIntent
这用于将视图或 spacer 的大小与指定的 NSLayoutDimension
相匹配。 它特别适用于对齐不同视图或 spacer 的大小,或使其大小彼此成比例。
public func Match(dimension: NSLayoutDimension, multiplier: CGFloat? = nil, offset: CGFloat? = nil) -> SizingIntent
public func Match(_ view: _View, dimension: NSLayoutDimension, multiplier: CGFloat? = nil, offset: CGFloat? = nil) -> SizingIntent
public func Match(_ views: [_View], dimension: NSLayoutDimension, multiplier: CGFloat? = nil, offset: CGFloat? = nil) -> SizingIntent
FixFlex 不是一个黑盒,也不使用任何魔法。 它只是一种声明式且方便的方式来创建约束和布局指南。 让我们看一下当我们想要垂直居中两个标签时,FixFlex 如何转换为标准 Auto Layout 调用
parent.fx.hstack(Flex([topLabel, bottomLabel]))
parent.fx.vstack(Fill(),
Flex(topLabel),
Fix(5),
Flex(bottomLabel),
Fill())
在底层,FixFlex 创建的约束和布局指南等同于以下内容
topLabel.translatesAutoresizingMaskIntoConstraints = false
bottomLabel.translatesAutoresizingMaskIntoConstraints = false
let layoutGuideTop = UILayoutGuide()
let layoutGuideMiddle = UILayoutGuide()
let layoutGuideBottom = UILayoutGuide()
parent.addLayoutGuide(layoutGuideTop)
parent.addLayoutGuide(layoutGuideMiddle)
parent.addLayoutGuide(layoutGuideBottom)
NSLayoutConstraint.activate([
// hstack
topLabel.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
topLabel.trailingAnchor.constraint(equalTo: parent.trailingAnchor),
bottomLabel.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
bottomLabel.trailingAnchor.constraint(equalTo: parent.trailingAnchor),
//vstack
layoutGuideTop.topAnchor.constraint(equalTo: parent.topAnchor),
layoutGuideTop.bottomAnchor.constraint(equalTo: topLabel.topAnchor),
topLabel.bottomAnchor.constraint(equalTo: layoutGuideMiddle.topAnchor),
layoutGuideMiddle.heightAnchor.constraint(equalToConstant: 5),
layoutGuideMiddle.bottomAnchor.constraint(equalTo: bottomLabel.topAnchor),
bottomLabel.bottomAnchor.constraint(equalTo: layoutGuideBottom.topAnchor),
layoutGuideBottom.bottomAnchor.constraint(equalTo: parent.bottomAnchor),
layoutGuideTop.heightAnchor.constraint(equalTo: layoutGuideBottom.heightAnchor),
])
哎呀,要写这么多代码,想象一下需要修改它——插入一个额外的视图或更改顺序。 一旦你尝试了 FixFlex,你就不会想回去了!
parent.fx.hstack(Fix(15),
Flex(child),
Fix(15))
parent.fx.vstack(Fix(15),
Flex(child),
Fix(15))
parent.fx.hstack(Flex(),
Fix(child, 100),
Fix(15))
parent.fx.vstack(Flex(),
Fix(child, 50),
Fix(15))
parent.fx.hstack(Fill(),
Fix(child, 100),
Fill())
parent.fx.vstack(Fill(),
Fix(child, 50),
Fill())
parent.fx.hstack(Fill(),
Flex(label),
Fill())
parent.fx.vstack(Fill(),
Flex(label),
Fill())
parent.fx.hstack(Flex([topLabel, bottomLabel]))
parent.fx.vstack(Fill(),
Flex(topLabel),
Fix(5),
Flex(bottomLabel),
Fill())
parent.fx.hstack(Fix(15),
Fix(iconView, 44),
Fix(15),
Flex([titleLabel, subtitleLabel]),
Fix(15),
Fix(chevron, 20),
Fix(15))
parent.fx.vstack(Fix(15),
Fix(iconView, 44),
Flex(min: 15))
parent.fx.vstack(Fix(15),
Flex(titleLabel),
Flex(subtitleLabel),
Fix(15))
parent.fx.vstack(Fill(),
Fix(chevron, 30),
Fill())
parent.fx.hstack(Fix(5),
Flex([iconView, titleLabel, subtitleLabel]),
Fix(5))
parent.fx.vstack(Fix(5),
Fix(iconView, 50),
Fix(10),
Flex(titleLabel),
Flex(subtitleLabel),
Fix(5))
parent.fx.vstack(Flex([leftLabel, rightLabel]))
parent.fx.hstack(Flex(leftLabel, compressionResistancePriority: .required),
Fix(5),
Flex(rightLabel))
parent.fx.vstack(Fix(5),
Flex([label1, label2, label3]),
Fix(5))
parent.fx.hstack(Fix(5),
Fill(label1, weight: 2),
Fix(5),
Fill(label2),
Fix(5),
Fill(label3),
Fix(5))
parent.fx.vstack(Fix(5),
Flex(label1),
Flex(label2),
Flex(label3),
Fix(5))
parent.fx.hstack(Fix(5),
Flex(label1),
Flex(),
Fix(5))
parent.fx.hstack(Fix(5),
Flex(label2, min: 175),
Flex(),
Fix(5))
parent.fx.hstack(Fix(5),
Flex(label3, max: 100),
Flex(),
Fix(5))
parent.fx.vstack(Flex([label, leadingView, trailingView]))
parent.fx.hstack(Fill(),
Flex(label),
Fill())
parent.fx.hstack(startAnchor: label.leadingAnchor,
endAnchor: label.trailingAnchor,
Fix(leadingView, 20),
Flex(),
Fix(trailingView, 20))
parent.fx.vstack(Flex([label, leadingView, trailingView]))
parent.fx.hstack(Fill(),
Flex(label),
Fill())
parent.fx.hstack(startAnchor: label.leftAnchor,
endAnchor: label.rightAnchor,
useAbsolutePositioning: true,
Fix(leadingView, 20),
Flex(),
Fix(trailingView, 20))
parent.fx.vstack(Fill(),
Flex(label),
Fill())
parent.fx.hstack(Fill(),
Flex(label),
Fill())
parent.fx.vstack(startAnchor: label.topAnchor,
Fix(10),
Match(matchView, dimension: label.heightAnchor),
Flex())
parent.fx.hstack(startAnchor: label.leadingAnchor,
Fix(10),
Match(matchView, dimension: label.widthAnchor),
Flex())
使用 Swift Package Manager 并将依赖项添加到 Package.swift
文件。
dependencies: [
.package(url: "https://github.com/psharanda/FixFlex.git", .upToNextMajor(from: "1.0.0"))
]
或者,在 Xcode 中选择 File > Add Package Dependencies…
并添加 FixFlex 存储库 URL
https://github.com/psharanda/FixFlex.git
将 github "psharanda/FixFlex"
添加到你的 Cartfile
FixFlex
可通过 CocoaPods 获得。 要安装它,只需将以下行添加到你的 Podfile
pod "FixFlex"
我们欢迎贡献! 如果您发现错误、有功能请求或想贡献代码,请打开一个 issue 或提交一个 pull request。
FixFlex 在 MIT 许可证下可用。 有关更多信息,请参阅 LICENSE 文件。