简介
Lima 是一个开源框架,用于简化响应式 iOS 和 tvOS 应用程序的开发。项目的名称来自航海中的 L 或 Lima 标记,代表“布局”一词的第一个字母。
本指南介绍了 Lima 框架并概述了其关键功能。
内容
获取 Lima
Lima 以通用二进制的形式分发,可以在模拟器和实际设备上运行。它还通过 CocoaPods 提供。需要 iOS 11 或 tvOS 11 或更高版本。
安装
- 下载最新的 版本存档 并展开。
- 在 Xcode 中,在项目导航器视图中选择项目根节点
- 选择应用程序目标
- 选择“通用”选项卡
- 将 Lima.framework 拖到“已嵌入的二进制文件”部分
- 在出现的对话框中,确保选中“如有需要则复制项”,然后单击“完成”
请注意,在提交 App Store 之前,框架二进制文件必须进行“trim”。有关更多信息,请参阅部署部分。
利马类
自动布局是iOS的一项功能,允许开发者创建能够自动适应设备尺寸、方向或内容变化的应用程序。使用自动布局构建的应用程序通常具有很少或没有硬编码的视图定位逻辑,而是根据它们的首选或“固有”内容大小动态排列用户界面元素。
iOS中的自动布局主要通过布局约束实现,虽然功能强大,但使用起来并不特别方便。为了简化过程,利马提供了一系列视图类,它们唯一的责任是管理各自子视图的大小和位置
LMRowView
- 按水平线排列子视图LMColumnView
- 按垂直线排列子视图LMAnchorView
- 将子视图锚定到父视图的一个或多个边缘
这些类内部使用布局约束,允许开发者轻松利用自动布局,同时无需直接管理约束。它们可以嵌套以创建复杂的布局,这些布局可以自动调整方向或屏幕尺寸变化。
例如,下方的周期表是使用利马的布局视图和 UILabel
实例的组合构建的
利马为 UIView
添加了以下属性以自定义子视图在父视图内的大小和位置
width
- 为视图分配固定宽度height
- 为视图分配固定高度weight
- 当与行和列视图一起使用时,确定如何在父视图中分配多余空间anchor
- 当与锚定视图一起使用时,确定视图将在父视图内锚定的边缘isDisplayable
- 确定视图是否参与自动布局(默认为true
)
此外,LMSpacer
类可用于在其它视图之间创建固定或灵活的空间。
利马还提供了以下视图类以简化对几个UIKit类型的操作
LMScrollView
- 扩展了UIScrollView
以自动适应内容大小LMTableViewCell
- 扩展了UITableViewCell
以自动将内容固定到边缘LMTableViewHeaderFooterView
- 扩展了UITableViewHeaderFooterView
以自动将内容固定到边缘LMCollectionViewCell
- 扩展了UICollectionViewCell
以自动将内容固定到边缘
最后,利马向常见的UIKit视图和控制添加了初始化器,以简化在视图层次结构中的声明。例如,以下Swift代码创建了一个包含 UIImageView
和 UILabel
的 LMColumnView
实例
let columnView = LMColumnView(
UIImageView(image: UIImage(named: "world.png"), contentMode: .scaleAspectFit),
UILabel(text: "Hello, World!", textAlignment: .center)
)
同样,也可以如下实现相同的结果
let columnView = LMColumnView()
let imageView = UIImageView()
imageView.image = UIImage(named: "world.png")
imageView.contentMode = .scaleAspectFit
columnView.addSubview(imageView)
let label = UILabel()
label.text = "Hello, World!"
label.textAlignment = .center
columnView.addSubview(label)
虽然这两个示例产生了相同的结果,但第一个版本更简洁,更容易阅读。
LMLayoutView
LMLayoutView
是利马中所有布局视图的基类。除了其他功能,它还提供了以下初始化器,用于定义视图的布局边距
public convenience init(margin: CGFloat?,
topMargin: CGFloat?,
leadingMargin: CGFloat?,
bottomMargin: CGFloat?,
trailingMargin: CGFloat?) {
...
}
第一个参数指定应用于所有边距的值。其余参数指定特定边界的值。如果没有指定值,则默认为0。
LMLayoutView
的子类为所有边距值提供默认的nil
值。这允许在创建时方便地设置布局视图的边距。例如
LMColumnView(margin: 8, leadingMargin: 12, trailingMargin: 12,
...
)
默认情况下,布局视图不消费触摸事件。在布局视图中发生但与子视图不交叉的触摸将被忽略,允许事件通过视图。将非nil
的背景色赋予布局视图将导致视图开始消耗事件。
LMRowView 和 LMColumnView
LMRowView
和LMColumnView
类分别按水平或垂直线排列子视图。这两个类都扩展了抽象的LMBoxView
类,该类本身扩展了LMLayoutView
并添加了以下属性。
var horizontalAlignment: LMHorizontalAlignment
var verticalAlignment: LMVerticalAlignment
var spacing: CGFloat
var isAlignToBaseline: Bool
前两个属性分别指定盒视图子视图的水平和垂直对齐方式。水平对齐选项包括fill
、leading
、trailing
和center
。垂直对齐选项包括fill
、top
、bottom
和center
。两个值默认都设置为fill
,从而将子视图固定在盒视图的两个轴上。其他值将子视图固定在单个边缘或沿给定轴居中。
例如,以下代码创建了一个包含三个标签的行视图,这些标签按水平方向对齐到行的领先边缘,按垂直方向对齐到行的顶部。
LMRowView(horizontalAlignment: .leading, verticalAlignment: .top,
UILabel(text: "One"),
UILabel(text: "Two"),
UILabel(text: "Three")
)
空白视图也可以用于在行或列内对齐子视图。这将在稍后进行更详细的讨论。
spacing
属性表示连续子视图之间的空间量。对于行视图,这指的是子视图之间的水平空间;对于列视图,这指的是视图之间的垂直空间。例如,以下代码创建了一个行视图,其标签将各自间隔16像素。
LMRowView(spacing: 16,
UILabel(text: "One"),
UILabel(text: "Two"),
UILabel(text: "Three")
)
isAlignToBaseline
属性可以用于管理子视图相对于彼此的垂直对齐方式。这将在以下部分进行更详细的讨论。基线对齐默认是禁用的。
LMRowView
LMRowView
类按水平线对其子视图进行排列。子视图按它们声明的顺序从领先边缘到尾部边缘进行布局。例如,以下代码创建了一个包含三个标签的行视图。
LMRowView(
UILabel(text: "One"),
UILabel(text: "Two"),
UILabel(text: "Three")
)
如果行视图的垂直对齐设置为fill
(默认值),则每个子视图的顶部和底部边缘将固定到行的顶部和底部边缘(不包括布局边距),确保所有标签都具有相同的高度。否则,子视图将根据指定的值垂直对齐。
基线对齐
此代码创建了一个包含三个不同字体大小的标签的行视图。因为将 isAlignToBaseline
设置为 true
,所以三个标签的基线将对齐。
LMRowView(isAlignToBaseline: true,
UILabel(text: "abcd", font: UIFont.systemFont(ofSize: 16)),
UILabel(text: "efg", font: UIFont.systemFont(ofSize: 32)),
UILabel(text: "hijk", font: UIFont.systemFont(ofSize: 24))
)
此外,可以由 baseline
属性控制用于对齐子视图的基线。默认值为 first
,即子视图将对齐到第一个基线。但是,也可以将子视图对齐到最后一个基线;例如
LMRowView(isAlignToBaseline: true, baseline: .last,
...
)
LMColumnView
LMColumnView
类按照垂直方向排列其子视图。子视图将按照它们声明的顺序从上到下布局。例如,以下代码创建了一个包含三个标签的列视图:
LMColumnView(
UILabel(text: "One"),
UILabel(text: "Two"),
UILabel(text: "Three")
)
如果将列视图的横向对齐设置为 fill
(默认值),则每个子视图的左右边缘将固定到列的左右边缘(不包括布局边距),确保所有标签的宽度相同。否则,子视图将根据指定的值横向对齐。
基线对齐
此代码创建了一个包含三个具有不同字体大小的标签的列视图。因为将 isAlignToBaseline
设置为 true
,所以标签将根据它们的第一个和最后一个基线垂直排列,而不是它们的边界矩形。
LMColumnView(isAlignToBaseline: true,
UILabel(text: "abcd", font: UIFont.systemFont(ofSize: 16)),
UILabel(text: "efg", font: UIFont.systemFont(ofSize: 32)),
UILabel(text: "hijk", font: UIFont.systemFont(ofSize: 24))
)
网格对齐
LMColumnView
定义了以下额外的属性,指定嵌套子视图应垂直排列成网格,就像 HTML 表格一样。
var isAlignToGrid: Bool
当此属性设置为 true
时,连续行的子视图将调整大小以匹配列中最宽子视图的宽度。例如,以下代码将生成包含三个行并以两列排列的网格:
LMColumnView(isAlignToGrid: true,
LMRowView(
UILabel(text: "First row"),
UILabel(text: "This is row number one.")
)
LMRowView(
UILabel(text: "Second row"),
UILabel(text: "This is row number two.")
)
LMRowView(
UILabel(text: "Third row"),
UILabel(text: "This is row number three.")
)
)
排除非LMRowView
实例的列视图子视图进行对齐。这允许它们用作章节分隔符或标题,例如。
固定尺寸
虽然视图通常根据其内在内容大小进行排列,但有时需要为特定视图尺寸分配一个固定值。Lima向UIView
添加以下属性以支持显式大小定义
var width: CGFloat
var height: CGFloat
例如,以下代码声明了一个高度属性设置为240像素的图像视图
UIImageView(image: UIImage(named: "world.png"),
contentMode: .scaleAspectFit,
height: 240)
如果图像的高度小于或大于240像素,则将其缩放以适应此高度。由于内容模式设置为scaleAspectFit
,宽度将相应调整,以确保图像保留正确的宽高比。
视图权重
通常,行或列视图会分配比其子视图所需更多的空间。Lima向UIView
添加以下属性,用于确定如何分配额外空间
var weight: CGFloat
此值指定视图希望在其父视图中获得的多余空间量(一旦确定了所有未加权视图的大小),并且是与父视图中指定的所有其他权重相对的。对于行视图,权重应用于额外水平空间,而对于列视图则应用于额外垂直空间。
例如,下面的两个标签将被同等大小并给予列视图高度的50%
LMColumnView(
UILabel(text: "Hello", weight: 0.5),
UILabel(text: "World", weight: 0.5)
)
由于权重是相互相关的,此代码将产生相同的结果
LMColumnView(
UILabel(text: "Hello", weight: 1),
UILabel(text: "World", weight: 1)
)
在示例中,第一个标签将获得可用的六分之一空间,第二个标签获得三分之一,第三个标签获得一半
LMColumnView(
UILabel(text: "One", weight: 1),
UILabel(text: "Two", weight: 2),
UILabel(text: "Three", weight: 3)
)
在LMRowView
中处理权重的方式类似,但在水平方向。
请注意,显式定义的宽度和高度值优先于权重。如果视图既有权重又有固定尺寸值,则将忽略权重值。
LMSpacer
权重的常见用途是在视图周围添加灵活的空间。例如,以下代码可以用于在行视图中水平居中标签(稍后将对初始化视图实例使用的闭包进行更详细的讨论)
LMRowView(
UIView() { $0.weight = 1 },
UILabel(text: "Hello, World!"),
UIView() { $0.weight = 1 }
)
由于间距视图非常常见,Lima提供了一种专门的UIView
子类,称为LMSpacer
,以便方便地在其他视图之间创建灵活的空间。LMSpacer
的默认权重为1,因此前面的示例可以重写如下,删除闭包并提高可读性
LMRowView(
LMSpacer(),
UILabel(text: "Hello, World!"),
LMSpacer()
)
与布局视图一样,间距视图默认不消费触摸事件,所以它们不会干扰其下出现的任何用户界面元素。将非null
背景颜色分配给间距视图会使视图开始消费事件。
LMAnchorView
LMAnchorView
类可以选择将子视图与其自身的任一边缘相锚定
虽然可以通过使用行、列和间距视图的组合来实现类似的布局,但在某些情况下,锚定视图提供了更简单的替代方案。锚定视图也是唯一支持Z排序的布局容器。
锚定是可以默认选择的,它定义了视图将在其父视图内部锚定的边缘。例如,以下代码创建了一个包含四个标签的锚定视图,这些标签锚定在其顶部、左侧、右侧和底部边缘。这些标签都会向内缩进16像素
LMAnchorView(margin: 16,
UILabel(text: "Top", anchor: [.top]),
UILabel(text: "Left", anchor: [.left]),
UILabel(text: "Right", anchor: [.right]),
UILabel(text: "Bottom", anchor: [.bottom])
)
子视图也可以锚定到父视图的左侧和右侧边缘,以支持从右到左的区域;例如
LMAnchorView(margin: 16,
UILabel(text: "Leading", anchor: [.leading]),
UILabel(text: "Trailing", anchor: [.trailing])
)
此外,子视图还可能锚定到给定维度的多个边缘。例如,以下代码创建了一个包含两个标签的锚定视图,每个标签都会扩展到锚定视图的全宽
LMAnchorView(margin: 16,
UILabel(text: "Top", anchor: [.top, .left, .right]),
UILabel(text: "Bottom", anchor: [.bottom, .left, .right])
)
如果没有指定给定维度的锚定,则子视图将在此维度的锚定视图中居中。
LMScrollView
LMScrollView
类扩展了UIScrollView
以简化可滚动内容的声明。它呈现单个内容视图,可选地允许用户在任一或两个方向上滚动。
滚动视图的内容通过其content
属性指定。以下属性确定内容的呈现方式
var isFitToWidth: Bool
var isFitToHeight: Bool
当两个值都设置为false
(默认值)时,滚动视图会在需要时自动显示滚动条,允许用户在两个方向上平移以查看内容的全部内容。例如
LMScrollView(
UIImageView(image: UIImage(named: "large_image.png"))
)
当fitToWidth
设置为true
时,滚动视图将确保其内容宽度与自己的宽度匹配,导致内容在垂直方向上包裹和滚动。必要时将显示垂直滚动条,但永远不会显示水平滚动条,因为内容的宽度永远不会超过滚动视图的宽度
LMScrollView(fitToWidth: true,
UILabel(text: "Lorem ipsum dolor sit amet...",
numberOfLines: 0)
)
类似地,当fitToHeight
为true
时,滚动视图将确保其内容高度与自己的高度匹配,导致内容在水平方向上包裹和滚动。永远不会显示垂直滚动条,当需要时将显示水平滚动条。
LMTableViewCell和LMTableViewHeaderFooterView
LMTableViewCell
类简化了自定义表格视图内容的声明。当默认的UITableViewCell
类提供的内容选项不足以满足需求时,可以采用该类。正如之前所述,LMTableViewCell
会自动为其内容应用约束,以实现自适应大小行为。
例如,以下代码创建了一个包含UIDatePicker
的表格视图单元格。日期选择器将自动调整大小,以填充单元格的宽度和高度
LMTableViewCell(
UIDatePicker(datePickerMode: .date)
)
LMTableViewCell
也可用作自定义表格视图单元格类的基类。例如,以下初始化器可用于一个自定义单元格,该单元格使用列视图垂直排列其内容
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setContent(LMColumnView(
...
), ignoreMargins: false)
}
ignoreMargins
参数指示单元格将其内容固定到其边缘,而不仅仅是布局边距。由于前一个示例中的此值是false
,单元格内容将固定到其边距。
当设置为LMTableViewCell
实例的selectionStyle
属性为none
时,单元格不会消耗触摸事件。单元格内发生的触摸,而没有与子视图相交的,将被忽略,从而阻止选择。
与LMTableViewCell
类似,当默认的UITableViewHeaderFooterView
类提供的内容选项不足以满足需要时,可以使用LMTableViewHeaderFooterView
类。例如,以下代码创建了一个包含标签和开关的自定义头部/尾部视图
LMTableViewHeaderFooterView(
LMRowView(
UILabel(text: "On/Off", weight: 1),
UISwitch()
)
)
与LMTableViewCell
类似,LMTableViewHeaderFooterView
也可用作自定义表格视图头部/尾部视图类的基类。
LMCollectionViewCell
与LMTableViewCell
类似,《LMCollectionViewCell》表达式有助于声明自定义集合视图内容。它扩展了《UICollectionViewCell》,并自动对其内容应用约束以实现自适应大小功能。例如
override init(frame: CGRect) {
super.init(frame: frame)
content = LMColumnView(
...
}
}
UIKit 扩展
除了之前讨论的UIView扩展之外,Lima还为以下UIKit类型提供了扩展,以方便声明视图层次结构:
UILabel
UIImageView
UIButton
UITextField
UIDatePicker
UISwitch
UISegmentedControl
UISlider
UIStepper
UIPageControl
UIActivityIndicatorView
UIProgressView
UITextView
UITableView
UITableViewCell
UITableViewHeaderFooterView
UICollectionView
UICollectionViewFlowLayout
每个扩展都遵循类似模式,添加一个初始化器,为常见属性提供默认值,并增加一个可选的回调函数,用于自定义初始化。例如,UIButton
的初始化器定义如下
public extension UIButton {
public convenience init(type: UIButton.ButtonType,
title: String? = nil, image: UIImage? = nil,
tintColor: UIColor? = nil,
weight: CGFloat = .nan,
anchor: LMAnchor = [],
with: ((UIButton) -> Void)? = nil) {
...
}
}
此代码创建了一个标题为"按我!"的按钮,使用尾部闭包应用样式并注册事件监听器
UIButton(type: .system, title: "Press Me!") { button in
button.contentEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 6
button.addTarget(self, action: #selector(self.showGreeting), for: .primaryActionTriggered)
}
初始化回调也常用于将视图实例与出口变量关联。
还提供了扩展Lima类的扩展,例如LMRowView
和LMColumnView
。例如
public extension LMRowView {
public convenience init(margin: CGFloat? = nil,
topMargin: CGFloat? = nil,
leadingMargin: CGFloat? = nil,
bottomMargin: CGFloat? = nil,
trailingMargin: CGFloat? = nil,
horizontalAlignment: LMHorizontalAlignment = .fill,
verticalAlignment: LMVerticalAlignment = .fill,
spacing: CGFloat = .nan,
isAlignToBaseline: Bool = false,
baseline: LMBaseline = .first,
backgroundColor: UIColor? = nil,
weight: CGFloat = .nan,
anchor: LMAnchor = [],
_ subviews: UIView...,
with: ((LMRowView) -> Void)? = nil) {
...
}
}
请注意,可变参数subviews
是实现视图层次结构声明式构建的关键
LMColumnView(
UILabel(text: "One"),
UILabel(text: "Two"),
UILabel(text: "Three")
)
有关更多信息,请参阅扩展源代码
- UIKit 扩展
- LMRowView
- LMColumnView
- LMSpacer
- LMAnchorView
- LMScrollView
- LMTableViewCell
- LMTableViewHeaderFooterView
- LMCollectionViewCell
部署
Lima框架是一个通用二进制文件,必须在提交到App Store之前进行“裁剪”
- 将trim.sh脚本放置在项目根目录中
- 确保脚本具有执行权限(例如744)
- 在"嵌入框架"阶段之后创建一个新的"运行脚本"构建阶段
- 将新的构建阶段重命名为"裁剪框架可执行文件"或类似名称(可选)
- 调用脚本(例如
"${SRCROOT}/trim.sh" Lima
)
其他信息
本指南介绍了Lima框架并概述了其主要功能。有关更多信息,请参阅示例。