该库为 iOS 应用程序提供了一些有用的、常见的补充功能。 这些扩展、协议和结构旨在简化样板代码,并消除常见的“字符串类型”用例。
该库分为 7 个部分,可通过 CocoaPods 子规范获得。
FileManager
和 UIView
的扩展。 这些简化了获取常用 URL 和以编程方式添加视图的方式,使其简化为简单的变量和函数调用。Date
和 Calendar
的抽象。 它主要用于简单的日程安排和日期比较,其中时间的重要性低于实际日期。ContainerViewController
,没有任何内置的导航结构。UILabel
子类,该子类在标签的 text
属性设置为 nil
时呈现渐变“加载”动画。注册和出列单元格、集合视图补充视图、表视图标题和页脚以及注释,就像在其呈现视图上调用注册一样简单,并在 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.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
的扩展,提供了几种便捷方法,主要用于轻松地将子视图约束到其父视图
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)
}
}
Timeless Date 是一个简单的抽象,它从 Date 中删除时间,并使用 Calendar 进行计算。 这对于日历和旅行用例特别有用,因为查看某件事情还有多少天通常比它们之间的小时数 / 24 重要。
func numberOfDaysBetween(start: TimelessDate, finish: TimelessDate) -> DateInterval {
return start.dateIntervalSince(finish)
}
func isOneWeekFrom(checkout: TimelessDate) -> Bool {
return checkout.dateIntervalSince(TimelessDate()) <= 7
}
此结构还消除了将天、小时、分钟和秒添加到日期的不精确计算,并用 Calendar 计算替换它们。
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
是一个 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
在 Storyboard 或 Xib 中初始化 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
响应 ScrollingPageControl 交互
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
的标准页面点。currentPage
的方式响应 tintColorDidChange()
。dotSize
和 dotSpacing
,以保持统一的外观。updateDot(at:)
或 updateDots(at:)
以使页面控件保持同步。 通过使用 ObfuscatedKey
,您可以构建一个人类可读的密钥,该密钥不会简单地通过对已编译的代码运行“strings”来显示,甚至不会作为字符串出现在您的源代码中。 只需创建一个 ObfuscatedKey
并使用构建器变量来编码您的密钥。
let key = ObfuscatedKey().T.h.i.s.underscore.I.s.underscore.O.b.f.u.s.c.a.t.e.d.value // This_Is_Obfuscated
let key = ObfuscatedKey().e.x.a.m.p.l.e.dash.n1.n2.n3.n4.n5.value // example-12345
要运行示例项目,请克隆 repo,打开 UtiliKit.xcworkspace
,然后运行“UtiliKit-iOSExample”项目。
dependencies: [
.package(url: "https://github.com/BottleRocketStudios/iOS-UtiliKit.git", from: "1.6.0")
]
然后,您需要从可用的库中进行选择,以添加到您的项目中。 这些库应与通过 Cocoapods 提供的子规范相匹配。
UtiliKit
- 导入以下所有可单独使用的库。GeneralUtilities
- 此子规范包括 FileManager
和 UIView
的扩展。 这些简化了获取常用 URL 和以编程方式添加视图的方式,使其简化为简单的变量和函数调用。Instantiation
- 此子规范将“字符串类型”的视图实例化、视图控制器实例化和可重用视图出列转换为类型安全函数调用。TimelessDate
- 此子规范是对 Date
和 Calendar
的抽象。 它主要用于简单的日程安排和日期比较,其中时间的重要性低于实际日期。Versioning
- 此子规范简化了版本和构建号的显示。ContainerViewController
- 此子规范提供了一个简单的 ContainerViewController
,没有任何内置的导航结构。ActiveLabel
- 此子规范提供了一个 UILabel
子类,该子类在标签的 text
属性设置为 nil
时呈现渐变“加载”动画。Obfuscation
- 此子规范提供简单的例程,以从源代码中删除纯文本密码或密钥。将以下内容添加到您的 Podfile
pod 'UtiliKit'
您还需要确保选择使用框架
use_frameworks!
然后使用 CocoaPods 0.36 或更高版本运行 pod install
。
将以下内容添加到您的 Cartfile
github "BottleRocketStudios/iOS-UtiliKit"
运行 carthage update
并按照 Carthage 的 README 中描述的步骤进行操作。
请参阅 CONTRIBUTING 文档。 感谢各位贡献者!