FixFlex 1.2.3

FixFlex 1.2.3

Pavel Sharanda 维护。



FixFlex 1.2.3

FixFlex Logo

FixFlex 是一个简单而强大的基于 NSLayoutAnchor API 的 Auto Layout 库,是对 视觉格式语言的轻量级和类型安全的重新想象

特点

  • 声明式 Auto Layout 代码,易于编写、阅读和修改
  • 简单的 API,包含两个函数和四个规范,涵盖 99% 的布局用例
  • 实现代码仅为 300 行
  • 与任何其他 Auto Layout 代码兼容
  • 基本生成一系列激活的 NSLayoutConstraintUILayoutGuide
  • 保持视图层次结构平面,无需额外容器
  • 轻量级的 UIStackView 替代方案
  • 非常直观的思维模型
  • 对 VFL 的类型安全替代方案
  • 动态类型和从右到左友好
  • 自动将 translatesAutoresizingMaskIntoConstraints 设置为 false
  • 支持 iOS 12.0+ / Mac OS X 10.13+ / tvOS 12.0+

使用方法

想象我们想创建一个类似这样的布局

  1. 让我们水平扫描布局并将其转换为 FixFlex 代码

大多数视图和间隔具有固定宽度(Fix),而标题和副标题宽度是灵活的,设计为占据剩余空间(Flex

parent.fx.hstack(Fix(15),
                 Fix(iconView, 44),
                 Fix(15),
                 Flex([titleLabel, subtitleLabel]),
                 Fix(15),
                 Fix(chevron, 20),
                 Fix(15))
  1. 垂直方向上,我们有三个不同的视图组。从图标开始

我们使用 Fix 在顶部做间隔。底部的间隔至少应为 15pt,以防标签的高度小于图标的宽度

parent.fx.vstack(Fix(15),
                 Fix(iconView, 44),
                 Flex(min: 15))
  1. 接下来,我们对标题和副标题进行垂直扫描

parent.fx.vstack(Fix(15),
                 Flex(titleLabel),
                 Flex(subtitleLabel),
                 Fix(15))
  1. 最后,我们对箭头进行垂直扫描

为了居中箭头,我们确保顶部的间隔等于底部的间隔,使用 Fill

parent.fx.vstack(Fill(),
                 Fix(chevron, 30),
                 Fill())

这就完成了!最好的部分是修改 FixFlex 布局代码有多么容易,可以轻松地插入额外的填充或视图,无需重新连接约束。

API

hstack/vstack

FixFlex 提供了两个函数用于水平(hstack)和垂直(vstack)布局,可以通过 view.fx.* 命名空间访问。

您可以指定 startAnchor/endAnchor,用于在任意锚点之间布局项目,而不是视图的边缘。使用 startOffset/endOffset 来添加从 startAnchorendAnchor 的间距或偏移量。

默认情况下,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 的具体实例。

修复

用于指定视图/间距的确切大小。

func Fix(_ value: CGFloat) -> SizingIntent

func Fix(_ view: _View, _ value: CGFloat) -> SizingIntent

func Fix(_ views: [_View], _ value: CGFloat) -> SizingIntent

弹性

适用于动态变化的大小。可选地,可以指定最小/最大约束以及拥抱和压缩抵抗的内部优先级设置。

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

填充

填充 允许视图/间距根据其权重按比例占用可用空闲空间。这对于实现等距、居中元素或设计对称布局(如表格或网格)特别有用。

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

匹配

用于将视图或间距的大小匹配到指定的 NSLayoutDimension。这在调整不同视图或间距的大小,或使它们的大小成比例时特别有用。

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.swift 文件中。

  dependencies: [
    .package(url: "https://github.com/psharanda/FixFlex.git", .upToNextMajor(from: "1.0.0"))
  ]

或者,在 Xcode 中选择 文件 > 添加包依赖... 并添加 FixFlex 存储库 URL

https://github.com/psharanda/FixFlex.git

贡献

我们欢迎贡献!如果您发现了一个错误,有一个功能请求,或想贡献代码,请打开一个问题或提交一个拉取请求。

许可证

FixFlex 可在 MIT 许可证下获得。有关更多信息,请参阅 LICENSE 文件。