UtiliKit
目的
这个库为iOS应用提供了几个有用且常见的添加功能。这些扩展、协议和结构体旨在简化样板代码,以及移除常见的字符串类型用法。
关键概念
这个库分为7个部分,作为CocoaPods子规范可用。
- 实例化 - 这个子规范将"字符串类型"视图实例化、视图控制器实例化和可重用视图出队列更改为安全类型函数调用。
- 通用 - 这个子规范包括对
FileManager
和UIView
的扩展。这简化了获取常见URL和通过编程添加视图到简单的变量和函数调用。 - 版本 - 这个子规范简化了版本和构建号的显示。
- TimelessDate - 这个子规范是对
Date
和Calendar
的抽象。它主要是为简单调度和每天比较设计的,其中时间不如实际日期重要。 - Container - 这个子规范提供了一个简单的`ContainerViewController`,没有任何内置的导航结构。
- ActiveLabel - 这个子规范提供了一个`UILabel`子类,当`text`属性设置为`nil`时,它渲染渐变"加载"动画。
- Obfuscation - 这个子规范提供了一些简单的例行程序,可以从源代码中删除明文密码或密钥。
使用
实例化
可重用视图
注册和出队单元格、收集视图辅助视图、表格视图的头部和尾部以及注释就像调用它们的展示视图上的register方法,以及在collectionView(_:cellForItemAt:) -> UICollectionViewCell或等效功能中出队它们一样简单。
class ViewController: UIViewController {
@IBOutlet var collectionView: UICollectionView!
let dataA: [Int] = [0, 1, 2]
let dataB: [Int] = [0, 1, 2]
let dataC: [Int] = [0, 1, 2]
var data: [[Int]] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(ProgrammaticCell.self)
collectionView.registerHeaderFooter(ProgrammaticHeaderFooterView.self)
collectionView.delegate = self
collectionView.dataSource = self
}
}
// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data[section].count
}
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return data.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Dequeue a "ProgrammaticCell" from the collection view using only the cell type
let cell: ProgrammaticCell = collectionView.dequeueReusableCell(for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
// You need only provide the desired type and SupplementaryElementKind to receive a typed UICollectionReusableView
switch kind {
case UICollectionElementKindSectionHeader:
let header: ProgrammaticHeaderFooterView = collectionView.dequeueReusableSupplementaryView(of: .sectionHeader, for: indexPath)
return header
default:
let footer: ProgrammaticHeaderFooterView = collectionView.dequeueReusableSupplementaryView(of: .sectionFooter, for: indexPath)
footer.kind = .sectionFooter
return footer
}
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: 100)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: 50)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: 25)
}
}
视图控制器
为了从一个Storyboard中实例化一个视图控制器,您只需创建一个Storyboard.Identifier,并定义返回类型。一个简单的实现可能如下所示
extension UIStoryboard.Identifier {
static let myStoryboard = UIStoryboard.Identifier(name: "MyStoryboard")
}
class ViewController: UIViewController {
func presentMyViewController() {
let vc: MyViewController = UIStoryboard(identifier: .myStoryboard).instantiateViewController()
present(vc, animated: true)
}
}
通用
文件管理器扩展
在FileManager
上提供了几个便利方法作为扩展
let documentsDirectory = FileManager.default.documentsDirectory
let cachesDirectory = FileManager.default.cachesDirectory
let appSupportDirectory = FileManager.default.applicationSupportDirectory
let sharedContainerURL = FileManager.default.sharedContainerURL(forSecurityApplicationGroupIdentifier: "com.app.group")
UIView 扩展
在 UIView
上提供了一些便利方法作为扩展,主要用于轻松地将子视图约束到父视图中
let myView = UIView(frame: .zero)
view.addSubview(myView, constrainedToSuperview: true)
let anotherView = UIView(frame: .zero)
anotherView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(anotherView)
let insets = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
anotherView.constrainEdgesToSuperview(with: insets)
版本号
将版本号转换为用户视图字符串只需调用一个函数。*注意:如果提供的版本配置包含无效键,该函数将抛出错误。一个简单的实现可能如下所示
func printVersions() {
do {
let customVersionString = try Bundle.main.versionString(for: MyVersionConfig(), isShortVersion: false)
let verboseVersionString = try Bundle.main.verboseVersionString()
let versionString = try Bundle.main.versionString()
print(customVersionString)
print(verboseVersionString)
print(versionString)
} catch {
print(error)
}
}
永恒日期
永恒日期是一个非常简单的抽象,它从日期中移除时间,并使用日历进行计算。这对于日历和旅行用例非常有用,因为了解两个事件之间有多少天通常比两者之间的小时数 / 24 更重要。
func numberOfDaysBetween(start: TimelessDate, finish: TimelessDate) -> DateInterval {
return start.dateIntervalSince(finish)
}
func isOneWeekFrom(checkout: TimelessDate) -> Bool {
return checkout.dateIntervalSince(TimelessDate()) <= 7
}
这个结构还移除了给日期添加天数、小时数、分钟数和秒数的模糊计算,并用日历计算替换。
func addOneHourTo(date: Date) -> Date {
return date.adding(hours: 1)
}
容器视图控制器
用于管理多个子视图控制器的解决方案,ContainerViewController 管理子控制器的生命周期。这允许您专注于视图的导航结构以及它们之间的过渡。
containerViewController.managedChildren = [Child(identifier: "A", viewController: controllerA), Child(identifier: "B", viewController: controllerB)]
containerViewController.willMove(toParent: self)
addChild(containerViewController)
containerView.addSubview(containerViewController.view)
containerViewController.view.frame = containerView.bounds
containerViewController.didMove(toParent: self)
在此阶段,在容器的子视图之间进行切换非常简单。
let child = ...
containerViewController.transitionToController(for: child)
容器还具有几个委托回调,可以帮助自定义其行为。其中之一是一个返回 UIViewControllerAnimatedTransitioning 对象的函数。
func containerViewController(_ container: ContainerViewController, animationControllerForTransitionFrom source: UIViewController, to destination: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if useCustomAnimator, let sourceIndex = container.index(ofChild: source), let destinationIndex = container.index(ofChild: destination) {
return WipeTransitionAnimator(withStartIndex: sourceIndex, endIndex: destinationIndex)
}
return nil
}
ActiveLabel
ActiveLabel
是一个继承自 UILabel
的子类,在设置其 text
属性为 nil
时向您的标签添加水平进度指示器。您可以通过代码或在 Interface Builder 中自定义该视图,以适应您的特定需求。此子类的主要目的是在您向标签中加载数据时,在标签级别提供视觉指示。
默认配置
let label: ActiveLabel = ActiveLabel()
自定义配置
let label: ActiveLabel = ActiveLabel()
label.estimatedNumberOfLines = 3
label.finalLineTrailingInset = 100
使用便利初始化器进行自定义配置。
var configuration = ActiveLabelConfiguration.default
configuration.estimatedNumberOfLines = 3
configuration.finalLineLength = 100
configuration.loadingView.animationDuration = 2.0
configuration.loadingView.animationDelay = 0
let label: ActiveLabel = ActiveLabel(frame: CGRect(x: 0, y: 0, width: 335, height: 21), configuration: configuration)
添加一些颜色,更改行高和间距。
let label: ActiveLabel = ActiveLabel()
label.estimatedNumberOfLines = 3
label.finalLineTrailingInset = 100
label.loadingView.color = UIColor(red: 233.0/255.0, green: 231.0/255.0, blue: 237.0/255.0, alpha: 1.0))
label.loadingView.lineHeight = 16
label.loadingView.lineVerticalSpacing = 8
在 Storyboards 或 Xibs 中初始化 ActiveLabel
时,必须在代码中将标签的文本设置为 nil
,因为 IB 以空字符串的值初始化标签。
在用于快照测试的 ActiveLabel
中,您可以通过在您的标签上调用 configureForSnapshotTest()
来使渐变居中。
滚动页面控制器
ScrollingPageControl
是一个基于 Apple 的 UIPageControl
(但不继承自)构建的视图。该类旨在允许在有限的空间内表示大量的页面,并提供比 UIPageControl
更多的自定义选项。
默认配置,与 UIPageControl 的相似之处
let pageControl: ScrollingPageControl = ScrollingPageControl()
pageControl.numberOfPages = 30 // default is 0
pageControl.currentPage = 14 // default is 0
pageControl.hidesForSinglePage = false // default
pageControl.pageIndicatorTintColor = .systemGray // default
pageControl.currentPageIndicatorTintColor = .systemBlue // default
自定义点布局
pageControl.mainDotCount = 5 // default is 3
pageControl.marginDotCount = 3 // default is 2
pageControl.dotSize = CGSize(width: 5.0, height: 10.0) // default is 7.0 x 7.0
pageControl.dotSpacing = 14.0 // default is 9.0
pageControl.minimumDotScale = 0.25 // default is 0.4
响应滚动页面控制器交互
pageControl.didSetCurrentPage = { [weak self] (index) in
self?.scrollToPageAtIndex(index)
}
添加自定义页面点
pageControl.customPageDotAtIndex = { [weak self] (index) in
guard self?.pageData[index].isFavorited else { return nil }
return FavoriteIconView()
}
使用说明
- 在
customPageDotAtIndex
块中返回对index
的nil
将默认到指定索引的dotSize
的标准页面点。 - 建议从该块返回的任何自定义视图都应对
tintColorDidChange()
做出响应,以清楚地表明它是否是currentPage
。 - 建议从该块返回的任何自定义视图都应该考虑到
dotSize
和dotSpacing
,以保持一致的外观和感觉。 - 在任何使用该块的数据在它首次设置后更新时,应调用
updateDot(at:)
或updateDots(at:)
以保持页面控制器同步。
匿名密钥
要在代码中使用匿名密钥,创建一个并使用构建器变量来编码您的密钥。
let key = ObfuscatedKey().T.h.i.s.underscore.I.s.dash.o.b.f.u.s.c.a.t.e.d.value
示例
要运行示例项目,请克隆仓库,打开 UtiliKit.xcworkspace
,然后运行 "UtiliKit-iOSExample" 项目。
需求
- iOS 10.0+
- Swift 5.0
安装 - Swift Package Manager
dependencies: [
.package(url: "https://github.com/BottleRocketStudios/iOS-UtiliKit.git", from: "1.6.0")
]
然后,您需要从可用的库中选择要添加到项目中的库。这些库应与通过CocoaPods提供的子spec相匹配。
UtiliKit
- 导入以下所有可用库。GeneralUtilities
- 此子spec包括对FileManager
和UIView
的扩展。这些简化了获取常见URL和通过简单变量和函数调用程序添加视图的过程。Instantiation
- 此子spec将“Stringly-typed”视图实例化、视图控制器实例化和可重用视图提取转换为安全的函数调用。TimelessDate
- 此子spec是针对Date
和Calendar
的抽象。它主要设计用于简单的调度和日比较,其中时间比实际日子更重要。Versioning
- 此子spec简化了版本和构建号显示。ContainerViewController
- 此子spec提供不含任何内置导航结构的简单ContainerViewController
。ActiveLabel
- 此子spec提供负责在标签的text
属性设置为nil
时渲染渐变“加载”动画的UILabel
子类。Obfuscation
- 此子spec提供简单的例程,用于从源代码中移除明文密码或密钥。
安装 - CocoaPods
将以下内容添加到您的 Podfile
pod 'UtiliKit'
请确保您已选择使用框架
use_frameworks!
然后使用CocoaPods的0.36或更新版本运行 pod install
安装 - Carthage
将以下内容添加到您的 Cartfile
github "BottleRocketStudios/iOS-UtiliKit"
运行 carthage update
并按照Carthage的README中描述的步骤进行。