使用 PureLayout 以编程方式创建 UIViews 约束
作者:Aly Yakan
今天,我们将指导您如何以编程方式创建约束——完全用代码构建一个简单的移动应用程序的 UI,而无需使用 Storyboard 或 NIB。我不会讨论哪种方法更好,因为它们各有优缺点,所以我只留下一个链接,它会更深入地探讨这个问题:https://www.toptal.com/ios/ios-user-interfaces-storyboards-vs-nibs-vs-custom-code。
概述
本教程是使用 Xcode 9 和 Swift 4 编写的。我还假设您熟悉 Xcode、Swift 和 CocoaPods。
事不宜迟,让我们开始构建我们的项目:一个简单的联系人卡片应用。本教程旨在教您如何用代码构建应用的 UI,因此,除非用于本教程的目的,否则本教程不会包含任何与应用功能相关的逻辑。
设置项目
首先启动 Xcode -> “创建新的 Xcode 项目”。选择“单视图应用”,然后按“下一步”。
给项目起个你喜欢的名字,我选择叫它“ContactCard”,原因不言而喻。取消勾选下面所有三个选项,当然,选择 Swift 作为编程语言,然后点击“下一步”。
在电脑上选择一个位置来保存项目。取消勾选“在我的 Mac 上创建 Git 仓库”,然后点击“创建”。
由于我们不会使用 Storyboard 或 NIB,因此请继续删除“Main.storyboard”,您可以在项目导航器中找到它:
之后,在项目导航器中点击该项目,在“常规”选项卡下找到“部署信息”部分,删除“主界面”旁边的内容,通常是“Main”。这行代码告诉 Xcode 在应用程序启动时加载哪个 Storyboard 文件。但由于我们刚刚删除了“Main.storyboard”,保留这行代码会导致应用程序崩溃,因为 Xcode 找不到该文件。
因此,请继续删除“Main”一词。
创建 ViewController
此时,如果您运行该应用程序,将出现黑屏,因为应用程序现在没有任何UI源可以呈现给用户,因此下一步我们将为其提供一个UI源。打开“AppDelegate.swift”并在其中application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)
插入以下代码片段:
self.window = UIWindow(frame: UIScreen.main.bounds)
let viewController = ViewController()
self.window?.rootViewController = viewController
self.window?.makeKeyAndVisible()
它的作用基本上是为用户与应用程序的交互提供一个窗口。此窗口的视图控制器是项目创建时提供的,可以在“ViewController.swift”中找到。为了快速测试一切是否正常,请前往“ViewController.swift”并在viewDidLoad()
方法中插入以下代码:
self.view.backgroundColor = .blue
现在在您喜欢的模拟器设备上运行该应用程序。
在 Xcode 中在文件之间导航的一个有用的快捷方式是“⇧⌘O”,然后输入文件的名称或甚至是您要查找的一段代码,屏幕上就会出现一个文件列表供您选择。
运行应用程序后,模拟器屏幕上将显示以下结果:
当然,我们不会使用那种丑陋的蓝色,所以只需.blue
用.white
inside替换将背景变回白色即可viewDidLoad()
。
布局 UI
为了布局我们的 UI,我们将使用一个非常有用的库,它将使我们的工作变得轻松许多。它的仓库位于https://github.com/PureLayout/PureLayout。要安装 PureLayout,首先打开终端,然后“cd”进入项目目录。您可以输入cd
,然后按空格,将项目文件夹拖放到终端中,并按“Enter”键。现在,在终端中运行以下命令:
pod init
pod install
这应该是运行第二个命令后终端的输出:
之后,关闭 Xcode,打开 Finder 中的文件夹,你应该会找到一个名为“.xcworkspace”的文件。如果我们需要使用 CocoaPods,就可以打开它来访问我们的应用程序。现在找到一个名为“PodFile”的文件,并在以下语句下写入以下行:use_frameworks!
pod “PureLayout”
再次在终端中运行 pod install,然后按“Command + B”构建您的项目。
咖啡休息
现在一切都设置好了,让我们开始真正的工作吧。打开“ViewController.swift”,泡杯咖啡,看看最终的效果:
创建 ImageView
import PureLayout
在下面插入一行,import UIKit
以便能够在此文件中使用该库。接下来,在类声明下方、任何函数外部,我们将使用Avatar ImageView
以下代码片段创建惰性变量,如下所示:
lazy var avatar: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "avatar.jpg"))
imageView.autoSetDimensions(to: CGSize(width: 128.0, height: 128.0))
imageView.layer.borderWidth = 3.0
imageView.layer.borderColor = UIColor.lightGray.cgColor
imageView.layer.cornerRadius = 64.0
imageView.clipsToBounds = true
return imageView
}()
至于图像,获取桌面上任何您想要用作头像的图像,将其绘制并放置在 Xcode 文件夹下,在我的情况下是“ContactCard”,然后选中“如果需要,复制项目”。
然后点击“完成”。之后,在UIImage
“avatar.jpg”的声明中写入该文件的名称及其扩展名。
对于那些不了解的人来说,惰性变量与普通变量类似,只是它们在第一次需要或调用时才会被初始化(或分配任何内存空间)。这意味着惰性变量不会在视图控制器初始化时被初始化,而是等到稍后真正需要它们时才被初始化,从而为其他进程节省了处理能力和内存空间。这在初始化 UI 组件时尤其有用。
PureLayout 实际应用
正如您在初始化代码中看到的,这一行代码imageView.autoSetDimensions(to: CGSize(width: 128.0, height: 128.0))
就是 PureLayout 的实际代码。仅用一行代码,我们就为布局的高度和宽度设置了约束,UIImageView
并且所有必要的NSLayoutConstraint
布局行都创建好了,无需处理繁琐的函数调用。如果您曾经以编程方式创建过约束,那么您现在一定已经爱上了这个出色的库。
为了让这个 Image View 变成圆形,我们将其圆角半径设置为其宽度(或高度)的一半,即 64.0 点。此外,为了让图像本身也遵循 Image View 的圆度,我们将clipsToBounds
属性设置为 true,这告诉图像应该裁剪掉我们刚刚设置的半径之外的所有部分。
接下来,我们创建一个UIView
,作为视图的上半部分,位于灰色头像的后面。以下惰性变量是该视图的声明:
lazy var upperView: UIView = {
let view = UIView()
view.autoSetDimension(.height, toSize: 128)
view.backgroundColor = .gray
return view
}()
添加子视图
在我们忘记之前,让我们创建一个名为的函数func addSubviews()
,将我们刚刚创建的视图(以及我们将要创建的所有其他视图)作为子视图添加到视图控制器的视图中:
func addSubviews() {
self.view.addSubview(avatar)
self.view.addSubview(upperView)
}
现在将以下行添加到viewDidLoad(): self.addSubviews()
设置约束
为了直观地了解我们目前进展如何,让我们为这两个视图设置约束。创建另一个名为 func setupConstraints() 的函数,并插入以下约束:
func setupConstraints() {
avatar.autoAlignAxis(toSuperviewAxis: .vertical)
avatar.autoPinEdge(toSuperviewEdge: .top, withInset: 64.0)
upperView.autoPinEdge(toSuperviewEdge: .left)
upperView.autoPinEdge(toSuperviewEdge: .right)
upperView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom)
}
现在在viewDidLoad()
call内部setupConstraints()
添加其函数调用,如下所示:self.setupConstraints()
。在调用之后添加此函数addSubviews()
。这应该是最终输出:
置于最前面
哎呀,这似乎不对。如您所见,我们的upperView
位于 之上avatar
。这是因为我们avatar
在 之前添加了一个子视图upperView
,而这些子视图是以某种堆栈形式排列的,所以出现这种结果是很自然的。为了解决这个问题,我们可以将这两行代码互换,但我还想向您展示另一个技巧,那就是:self.view.bringSubview(toFront: avatar)
。
此方法会将头像从堆栈底部直接移回堆栈顶部,无论其上方有多少个视图。因此,您可以选择您喜欢的方法。当然,为了便于阅读,最好按照子视图的显示顺序添加它们(如果它们发生交叉),同时请记住,第一个添加的子视图将位于堆栈底部,因此任何其他交叉视图都将显示在它上面。
它实际上应该是这样的:
创建分段控制
接下来,我们将创建分段控件,即包含三个部分的灰色条。创建分段控件其实很简单。只需执行以下操作:
lazy var segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Personal", "Social", "Resumè"])
control.autoSetDimension(.height, toSize: 32.0)
control.selectedSegmentIndex = 0
control.layer.borderColor = UIColor.gray.cgColor
control.tintColor = .gray
return control
}()
我相信一切都很清楚,唯一的不同之处在于,初始化时我们提供了一个字符串数组,每个字符串代表我们想要的区块标题之一。我们还将 selectedSegmentIndex 设置为 0,这告诉分段控件在初始化时突出显示/选择第一个分段。其余的只是一些样式设置,您可以自行调整。
现在让我们继续将其添加为子视图,方法是将以下行插入到末尾func addSubviews(): self.view.addSubview(segmentedControl)
,其约束如下:
segmentedControl.autoPinEdge(toSuperviewEdge: .left, withInset: 8.0)
segmentedControl.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0)
segmentedControl.autoPinEdge(.top, to: .bottom, of: avatar, withOffset: 16.0)
花点时间好好理解一下这些。我们告诉分段控件,我们希望将它固定在其父视图的左侧,但是,我们希望它留出一些间距,而不是直接贴到屏幕边缘。如果你注意到,我使用了所谓的八点网格,其中所有间距和大小都是八的倍数。我对分段控件的右侧也做了同样的处理。至于最后一个约束,它只是简单地表示将其顶部固定在头像的底部,间距为 16 点。
将上述约束添加到后func setupConstraints()
,运行代码并确保它看起来像下面这样:
添加按钮
现在来看看本教程的最后一部分 UI,即“编辑”按钮。添加以下惰性变量:
lazy var editButton: UIButton = {
let button = UIButton()
button.setTitle("Edit", for: .normal)
button.setTitleColor(.gray, for: .normal)
button.layer.cornerRadius = 4.0
button.layer.borderColor = UIColor.gray.cgColor
button.layer.borderWidth = 1.0
button.tintColor = .gray
button.backgroundColor = .clear
button.autoSetDimension(.width, toSize: 96.0)
button.autoSetDimension(.height, toSize: 32.0)
return button
}()
别被初始化代码的庞大吓到,但请注意我是如何通过调用 button.setTitle 和 button.setTitleColor 函数来设置标题和颜色的。由于某些原因,我们无法直接通过访问按钮的 titleLabel 来设置按钮的标题,这是因为按钮有不同的状态,而很多人会觉得在不同状态下使用不同的标题/颜色会更方便。
现在将按钮添加为子视图,就像其余组件一样,并添加以下约束以使其出现在应出现的位置:
editButton.autoPinEdge(.top, to: .bottom, of: upperView, withOffset: 16.0)
editButton.autoPinEdge(toSuperviewEdge: .right, withInset: 8.0)
这里我们只设置了按钮的右侧和顶部约束,并且由于我们指定了大小,它不会扩展,也不需要其他任何设置。现在运行项目来查看最终结果:
最后说明
尝试一下,添加尽可能多的 UI 元素。尝试重新创建任何你认为具有挑战性的应用程序视图。从简单的开始,然后逐步完善。尝试在纸上画出 UI 组件,这样你就可以想象它们是如何组合在一起的。
了解更多信息,请访问blog.instabug.com。
鏂囩珷鏉ユ簮锛�https://dev.to/instabug/creating-uiviews-constraints-programmatically-using-purelayout-48am