SwiftyUtils

CI Status Language CocoaPods Compatible Carthage compatible Platform

SwiftyUtils 汇集了我们在每个项目中需要发布的所有可重用代码。这个框架包含

可在 iOS、macOS、tvOS 和 watchOS 上运行,一切都为了易用性而设计!🎉

目录

查看仓库以找到每个功能的示例/测试。

Swift、Foundation 和 CoreGraphics 扩展

SwiftUI

SwiftUI 扩展

UIKit 扩展

UIKit 协议

AppKit 扩展

协议

PropertyWrappers

其他

Swift、Foundation 和 CoreGraphics 扩展

Array 扩展

安全访问元素

var array = [1, 2, 3]
print(array[safe: 0]) // Optional(1)
print(array[safe: 10]) // nil

查找对象的所有索引

var array = [1, 2, 3, 1]
print(array.indexes(of: 1)) // [0,3]

获取对象首次/最后一次出现的索引

var array = [1, 2, 3, 1]
print(array.firstIndex(of: 1)) // Optional(0)
print(array.lastIndex(of: 1)) // Optional(3)

移除一个对象

var array = [1, 2, 3]
myArray.remove(object: 2)
print(myArray) // [1, 3]
myArray.remove(objects: [1, 3])
print(myArray) // []

移除所有重复项

var array = [0, 0, 1, 1, 2]
array.removeDuplicates()
print(array) // [0,1,2]

let array = [0, 0, 1, 1, 2]
let newArray.removedDuplicates()
print(newArray) // [0,1,2]

移除某个项目的所有实例

var array = [0, 0, 1, 1, 2]
array.removeAll(0)
print(array) // [1,1,2]

let array = [0, 0, 1, 1, 2]
let newArray = array.removedAll(0)
print(newArray) // [1,1,2]

检查数组是否是另一个数组的子集

var array = [1, 2, 3]
print(array.contains([1, 2])) // true
print(array.contains([5])) // false

确定数组是否包含对象

var array = [1, 2, 3]
print(array.contains(1)) // true
print(array.contains(11)) // false

获取两个数组的交集和并集

var myArray = [1, 2, 3]
print(array.intersection(for: [1, 5, 3])) // [1, 3]
print(array.union(values: [5, 6])) // [1, 2, 3, 5, 6]

获取两个数组之间的差异

var array = [1, 2, 3]
print(array.difference(with: [1])) // [2, 3]

拆分成特定大小的块

var array = [1, 2, 3, 4]
print(array.split(intoChunksOf: 2)) // [[1, 2], [3, 4]]

Bundle 扩展

获取 Bundle 信息

Bundle.main.appName
Bundle(url: url)?.appName

Bundle.main.displayName
Bundle(url: url)?.displayName

Bundle.main.appVersion
Bundle(url: url)?.appVersion

Bundle.main.appBuild
Bundle(url: url)?.appBuild

Bundle.main.bundleId
Bundle(url: url)?.bundleId

Bundle.main.schemes
Bundle(url: url)?.schemes

Bundle.main.mainScheme
Bundle(url: url)?.mainScheme

Bundle.main.isInTestFlight
Bundle(url: url)?.isInTestFlight

CGFloat 扩展

从 Float 或 Integer 创建 CGFloat

let imageViewTop = 15.f

CGPoint 扩展

添加两个 CGPoint

var point1 = CGPoint(x: 10, y: 10)
let point2 = CGPoint(x: 10, y: 10)
print(point1 + point2) // CGPoint(x: 20, y: 20)

point1 += point2
print(point1) // CGPoint(x: 20, y: 20)

减去两个 CGPoint

var point1 = CGPoint(x: 10, y: 10)
let point2 = CGPoint(x: 10, y: 10)
print(point1 - point2) // CGPoint(x: 0, y: 0)

point1 -= point2
print(point1) // CGPoint(x: 0, y: 0)

CGPoint 与标量相乘

var point1 = CGPoint(x: 10, y: 10)
print(point1 * 2) // CGPoint(x: 20, y: 20)

point1 *= 2
print(point1) // CGPoint(x: 20, y: 20)

CGRect 扩展

获取原点的 x 和 y 坐标

aRect.x // instead of aRect.origin.x
aRect.y // instead of aRect.origin.y

更改 CGRect 的一个属性

let rect = CGRect(x: 10, y: 20, width: 30, height: 40) 
let widerRect = rect.with(width: 100) // x: 10, y: 20, width: 100, height: 40
let tallerRect = rect.with(height: 100) // x: 10, y: 20, width: 30, height: 100
let rectAtAnotherPosition = rect.with(x: 100).with(y: 200) // x: 100, y: 200, width: 30, height: 40
let rectWithAnotherSize = rect.with(size: CGSize(width: 200, height: 200)) // x: 10, y: 20, width: 200, height: 200
let rectAtYetAnotherPosition = rect.with(origin: CGPoint(x: 100, y: 100)) // x: 100, y: 100, width: 30, height: 40

CGSize 扩展

添加两个 CGSize

var size1 = CGSize(width: 10, height: 10)
let size2 = CGSize(width: 10, height: 10)
print(size1 + size2) // CGSize(width: 20, height: 20)

size1 += size2
print(size1) // CGSize(width: 20, height: 20)

减去两个 CGSize

var size1 = CGSize(width: 10, height: 10)
let size2 = CGSize(width: 10, height: 10)
print(size1 - size2) // CGSize(width: 0, height: 0)

size1 -= size2
print(size1) // CGSize(width: 0, height: 0)

CGSize 与标量相乘

var size1 = CGSize(x: 10, y: 10)
print(size1 * 2) // CGSize(width: 20, height: 20)

size1 *= 2
print(size1) // CGSize(width: 20, height: 20)

Color 扩展

使用 HEX 值创建颜色

let myUIColor = UIColor(hex: "233C64") // Equals 35,60,100,1
let myNSColor = NSColor(hex: "233C64") // Equals 35,60,100,1

访问单个颜色值

let myColor = UIColor(red: 120, green: 205, blue: 44, alpha: 0.3)
print(myColor.redComponent) // 120
print(myColor.greenComponent) // 205
print(myColor.blueComponent) // 44
print(myColor.alpha) // 0.3

获取颜色实例的更浅或更深的变体

let color = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0)
let lighter = color.lighter(amount: 0.5)
let darker = color.darker(amount: 0.5)
// OR
let lighter = color.lighter()
let darker = color.darker()

let color = NSColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0)
let lighter = color.lighter(amount: 0.5)
let lighter = color.lighter()
// OR
let darker = color.darker(amount: 0.5)
let darker = color.darker()

Data 扩展

从十六进制字符串初始化

let hexString = "6261736520313020697320736F2062617369632E206261736520313620697320776865726520697427732061742C20796F2E"
let data = Data(hexString: hexString)

从数据获取十六进制字符串

let data = Data(...)
let string = data.toHexString()
// string = "6261736520313020697320736F2062617369632E206261736520313620697320776865726520697427732061742C20796F2E" if using previous example value

从数据获取 UInt8 数组

let data = Data(...)
let array = data.bytesArray

将 Data 映射到 Dictionary

let dictionary = try data.toDictionary()

Date 扩展

从字符串初始化

let format = "yyyy/MM/dd"
let string = "2015/03/11"
print(Date(fromString: string, format: format)) // Optional("2015/03/11 00:00:00 +0000")

将日期转换为字符串

let now = Date()
print(now.string())
print(now.string(dateStyle: .medium, timeStyle: .medium))
print(now.string(format: "yyyy/MM/dd HH:mm:ss"))

查看过去了多长时间

let now = Date()
let later = Date(timeIntervalSinceNow: -100000)
print(later.days(since: now)) // 1.15740740782409
print(later.hours(since: now)) // 27.7777777733571
print(later.minutes(since: now)) // 1666.66666641732
print(later.seconds(since: now)) // 99999.999984026

检查日期是在未来还是过去

let later = Date(timeIntervalSinceNow: -100000)
print(now.isInFuture) // false
print(now.isInPast) // true

Dictionary 扩展

检查字典中是否存在键

let dic = ["one": 1, "two": 2]
print(dic.has(key: "one")) // True
print(dic.has(key: "1")) // False

将 Dictionary 映射到 Data

let data = try dictionary.toData()

轻松获取两个字典的并集

let dic1 = ["one": 1, "two": 2]
let dic2 = ["one": 1, "four": 4]

let dic3 = dic1.union(values: dic2)
print(dic3) // ["one": 1, "two": 2, "four": 4]

map 一个字典

let dic = ["a": 1, "b": 2, "c": 3]
let result = dic.map { key, value in
	return (key.uppercased(), "\(value * 2)")
}
print(dic) // ["A": "2, "B": "4", "C": "6"]

flatMap 一个字典

let dic = ["a": 1, "b": 2, "c": 3]
let result = dic.flatMap { key, value -> (String, String)? in
	if value % 2 == 0 {
	 	return nil
	}
	return (key.uppercased(), "\(value * 2)")
}
print(dic) // ["A": "2, "C": "6"]

获取两个字典的差异

let dic1 = ["one": 1, "two": 2]
let dic2 = ["one": 1, "four": 4]
difference(with: dic1, dic2) // ["two": 2, "four": 4]

合并多个字典

let dic1 = ["one": 1, "two": 2]
let dic2 = ["three": 3, "four": 4]
var finalDic = [String: Int]()
finalDic.merge(with: dic1, dic2)
print(finalDic) // ["one": 1, "two": 2, "three": 3, "four": 4]

Double 扩展

获取毫秒、秒、小时或天数的timeInterval

print(1.second) // 1
print(1.minute) // 60
print(1.hour) // 3600
print(1.2.seconds) // 1.2
print(1.5.minutes) // 90.0
print(1.5.hours) // 5400.0
print(1.3.milliseconds) // 0.0013
print(0.5.day) // 43200
print(1.day) // 86400
print(2.day) // 172800

使用本地货币格式化的值

print(Double(3.24).formattedPrice) // "$3.24"
print(Double(10).formattedPrice) // "$10.00"

FileManager 扩展

获取跟随操作系统的 documents 目录 URL

FileManager.document
// OR
FileManager.default.document

创建一个新目录

FileManager.createDirectory(at: directoryUrl)
// OR
FileManager.default.createDirectory(at: directoryUrl)

删除临时目录的内容

FileManager.removeTemporaryFiles()
// OR
FileManager.default.removeTemporaryFiles()

删除 documents 目录的内容

FileManager.removeDocumentFiles()
// OR
FileManager.default.removeDocumentFiles()

Int 扩展

var myNumber = -33
print(myNumber.isEven) // false
print(myNumber.isOdd) // true
print(myNumber.isPositive) // false
print(myNumber.isNegative) // true
print(myNumber.digits) // 2

四舍五入到最近的 / 最近的向下 / 最近的向上

var value = 17572
print(value.nearestDozens) // 17570
print(value.nearestHundreds) // 17600
print(value.nearestThousands) // 18000
print(value.nearest(to: 1000) // 18000

value = 17578
print(value.nearestBelowDozens) // 17570
print(value.nearestBelowHundreds) // 17500
print(value.nearestBelowThousands) // 17000
print(value.nearestBelow(to: 1000) // 17000

value = 17442
print(value.nearestUpDozens) // 17450
print(value.nearestUpHundreds) // 17500)
print(value.nearestUpThousands) // 18000
print(value.nearestUp(to: 1000) // 18000

使用本地货币格式化的值

print(10.formattedPrice) // "$10.00"

MutableCollection 扩展

使用 KeyPath 就地对可变集合进行排序

var articles = [Article(title: "B"), Article(title: "C"), Article(title: "A")]
articles.sort(by: \.title) // [A, B, C]
articles.sort(by: \.title, order: >) // [C, B, A]

NotificationCenter 扩展

从特定队列发布通知

NotificationCenter.default.postNotification("aNotification", queue: DispatchQueue.main) 
NotificationCenter.default.postNotification("aNotification", object: aObject queue: DispatchQueue.main)
NotificationCenter.default.postNotification("aNotification", object: aObject userInfo: userInfo queue: DispatchQueue.main)

NSAttributedString 扩展

检查属性是否应用于所需的子字符串

let text = "Hello"
let attrString = NSMutableAttributedString(text: "Hello world")
attrString = attrString.underlined(occurences: text)
attrString.isAttributeActivated(.underlineStyle, appliedOn: text, value: 1) // true

NSLayoutConstraint 扩展

watchOS 不可用

将乘数应用于约束(目前仅适用于宽度和高度)

let view = UIView(CGRect(x: 0, y: 0, width: 100, height: 200))
let constraint = NSLayoutConstraint(item: view, attribute: .width, ...)
constraint.apply(multiplier: 0.5, toView: superview)
print(constraint.constants) // 50

let constraint = NSLayoutConstraint(item: view, attribute: .height, ...)
constraint.apply(multiplier0.5, toView: superview)
print(constraint.constants) // 100

NSMutableAttributedString 扩展

为每个出现项着色

let attrStr: NSMutableAttributedString = NSMutableAttributedString.colored(inText: "hello world", color: .yellow, occurences: "llo")

// OR

let attrStr: NSMutableAttributedString = NSMutableAttributedString(string: "Hello world")
attrStr.color(.yellow, occurences: "llo")

为出现项之后的所有内容着色

let attrStr = NSMutableAttributedString.colored(inText: "Hello world", color: .yellow, afterOcurrence: "llo")

// OR

let attrStr = NSMutableAttributedString(string: "Hello world")
attrStr.color(.yellow, afterOcurrence: "llo")

删除线每个出现项

let attrStr: NSMutableAttributedString = NSMutableAttributedString.struck(inText: "Hello world", occurences: "llo")

// OR

let attrStr = NSMutableAttributedString(string: "Hello world")
attrStr.strike(occurences: "llo")

删除线出现项之后的所有内容

let attrStr: NSMutableAttributedString = NSMutableAttributedString.struck(inText: "Hello world", afterOcurrence: "llo")

// OR

let attrStr = NSMutableAttributedString(string: "Hello world")
attrStr.strike(ocurrences: "llo")

下划线每个出现项

let attrStr: NSMutableAttributedString = NSMutableAttributedString.underlined(inText: "Hello world", occurences: "llo")

// OR

let attrStr = NSMutableAttributedString(string: "Hello world")
attrStr.underline(occurences: "llo")

下划线出现项之后的所有内容

let attrStr: NSMutableAttributedString = NSMutableAttributedString.underlined(inText: "Hello world", afterOcurrence: "llo")

// OR

let attrStr = NSMutableAttributedString(string: "Hello world")
attrStr.underline(afterOcurrence: "llo")

为每个出现项使用自定义字体

let font = UIFont.boldSystemFont(ofSize: 15)
let attrStr: NSMutableAttributedString = NSMutableAttributedString.font(inText: "hello world", font: font, occurences: "llo")

// OR

let attrStr: NSMutableAttributedString = NSMutableAttributedString(string: "Hello world")
attrStr.font(font, occurences: "llo")

为出现项之后的所有内容使用自定义字体

let font = UIFont.boldSystemFont(ofSize: 15)
let attrStr = NSMutableAttributedString.colored(inText: "Hello world", font: font, afterOcurrence: "llo")

// OR

let attrStr = NSMutableAttributedString(string: "Hello world")
attrStr.font(font, afterOcurrence: "llo")

NSObject 扩展

获取 NSObject 的类名

#if !os(macOS)
	let vc = NSViewController()
	print(vc.className) // NSViewController
#else
	let vc = UIViewController()
	print(vc.className) // UIViewController
	print(UIViewController.className) // UIViewController
#endif

NSRange 扩展

出现项之后的范围

let string = "Hello world"
let range = NSRange(text: string, afterOccurence: "llo")
print(range) // location: 3, length: 8

字符串范围

let string = "Hello world"
let stringToFind = "ello wo"
let range = NSRange(textToFind: stringToFind, in: string)
print(range) // location: 1, length: 7

ReusableFormatters

重用您的格式化程序以避免大量分配

SUDateFormatter.shared
SUNumberFormatter.shared
SUByteCountFormatter.shared
SUDateComponentsFormatter.shared
SUDateIntervalFormatter.shared
SUEnergyFormatter.shared
SUMassFormatter.shared

Sequence 扩展

使用 keyPath 对序列进行排序

let articles = [Article(title: "B"), Article(title: "C"), Article(title: "A")]
var sortedArticles = articles.sorted(by: \.title) // [A, B, C]
sortedArticles = articles.sorted(by: \.title, order: >) // [C, B, A]

String 扩展

使用下标访问

var string = "hello world"
print(string[0]) // h
print(string[2]) // l
print(string[Range(1...3)]) // ell

检查它是否包含字符串

let string = "Hello world"
print (string.contains(text: "hello")) // true
print (string.contains(text: "hellooooo")) // false

检查它是否是数字

var string = "4242"
print(string.isNumber) // true

var string = "test"
print(string.isNumber) // false

检查它是否是有效的电子邮件

// (deprecated)
var string = "test@gmail.com"
print(string.isEmail) // true
var string = "test@"
print(string.isEmail) // false
// current
var support = try "test@gmail.com".validateEmailAddress() // EmailSupport.widelySupported
string = try "test+tag@gmail.com".validateEmailAddress() // EmailSupport.mostlySupported
string = try "\"abc@def\"@gmail.com".validateEmailAddress() // EmailSupport.technicallySupported
string = try "test@".validateEmailAddress() // throws an error for lack of a domain

检查它是否是有效的域名

try "google.com".validateDomain() // doesn't throw
try "google..com".validateDomain() // throws because of sequential dots in value

检查它是否是有效的 IP 地址

let ip4 = "1.2.3.4"
let ip6 = "fc00::"
let notIPAtAll = "i'll bribe you to say i'm an ip address!"

ip4.isIP4Address //true
ip4.isIP6Address //false
ip4.isIPAddress //true

ip6.isIP4Address //false
ip6.isIP6Address //true
ip6.isIPAddress //true

notIPAtAll.isIP4Address //false
notIPAtAll.isIP6Address //false
notIPAtAll.isIPAddress //false

取消驼峰字符串

var camelString = "isCamelled"
print(camelString.uncamelize) // is_camelled

将首字母大写

var string = "hello world"
string = string.capitalizedFirst
print(string)// Hello world

修剪空格和换行符

var string = " I'  am a    test  \n  "
print(string.trimmed()) // I'am a test

截断为字符数限制

var string = "0123456789aaaa"
print(string.truncate(limit: 10)) // 0123456789...

将字符串拆分为 n 个元素的块

let string = "abcd"
print(string.split(intoChunksOf: 2)) // ["ab", "cd"]

Timer 扩展

每秒调度计时器

var count = 0
Timer.every(1.second, fireImmediately: true) { timer in // fireImmediately is an optional parameter, defaults to false
    print("Will print every second")
    if count == 3 {
        timer.invalidate()
    }
    count++
}

在一定延迟后调度计时器

Timer.after(2.seconds) { _ in
    print("Prints this 2 seconds later in main queue")
}

手动调度计时器

let timer = Timer.new(every: 2.seconds) { _ in
    print("Prints this 2 seconds later in main queue")
}
timer.start(onRunLoop: RunLoop.current, modes: RunLoopMode.defaultRunLoopMode)

手动调度带有延迟的计时器

let timer = Timer.new(after: 2.seconds) { _ in
    print("Prints this 2 seconds later in main queue")
}
timer.start(onRunLoop: RunLoop.current, modes: RunLoopMode.defaultRunLoopMode)

URL 扩展

从 URL 获取查询参数

let url = URL(string: "http://example.com/api?v=1.1&q=google")
let queryParameters = url?.queryParameters
print(queryParameters?["v"]) // 1.1
print(queryParameters?["q"]) // google
print(queryParameters?["other"]) // nil

向您的 URL 添加跳过备份属性

let url = URL(string: "/path/to/your/file")        
url?.addSkipBackupAttribute() // File at url won't be backupped!

UserDefaults 扩展

使用下标从 UserDefaults 获取和设置值

let Defaults = UserDefaults.standard
Defaults["userName"] = "test"
print(Defaults["userName"]) // test

检查 UserDefaults 是否有键

UserDefaults.has(key: "aKey")
// OR
UserDefaults.standard.has(key: "aKey")

移除 UserDefaults 中的所有值

UserDefaults.standard.removeAll()

SwiftUI

UIElementPreview

自动生成多个预览,包括

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        UIElementPreview(ContentView(),
                         previewLayout: .sizeThatFits, // default is `.device`
                         previewDevices: ["iPhone SE"], // default is iPhone SE and iPhone XS Max. Note: it won't be used if `previewLayout` is `.sizeThatFits`
                         dynamicTypeSizes:[.extraSmall] // default is: .extraSmall, .large, .extraExtraExtraLarge
                        )
    }
}

SwiftUI 扩展

Binding 扩展

传递一个交互式值,该值将充当绑定的预览占位符

struct MyButton: View {
    @Binding var isSelected: Bool
    // ...
}

struct MyButton_Previews: PreviewProvider {
    static var previews: some View {
        MyButton(isSelected: .mock(true))
    }
}

UIKit 扩展

UIAlertController 扩展

创建一个自定义 UIAlertController

let alertController1 = UIAlertController(title: "Title",
                                        message: "Message")
                          
let alertController2 = UIAlertController(title: "Title",
                                        message: "Message",
                                        defaultActionButtonTitle: "Cancel")
                                                      
let alertController3 = UIAlertController(title: "Title",
                                        message: "Message",
                                        defaultActionButtonTitle: "Cancel",
                                        defaultActionButtonStyle: .cancel) 
                                        
let alertController1 = UIAlertController(title: "Title",
                                        message: "Message",
                                        defaultActionButtonTitle: "Cancel",
                                        defaultActionButtonStyle: .cancel,
                                        tintColor: .blue)

显示一个 UIAlertController

alertController.show()
alertController.show(animated: false)
alertController.show(animated: true, completion: {
    print("Presented")
})

UIAlertController 添加一个操作

alertController.addAction(title: "ActionTitle")

alertController.addAction(title: "ActionTitle",
                          style: .destructive)
                          
alertController.addAction(title: "ActionTitle",
                          style: .destructive,
                          isEnabled: false)
                          
alertController.addAction(title: "ActionTitle",
                          style: .destructive,
                          isEnabled: false,
                          handler: nil)

UIApplication 扩展

获取当前视图控制器显示

UIApplication.shared.topViewController() // Using UIWindow's rootViewController as baseVC
UIApplication.shared.topViewController(from: baseVC) // topVC from the base view controller

获取 app delegate

UIApplication.delegate(AppDelegate.self)

打开应用设置

UIApplication.shared.openAppSettings()

打开应用评价页面

let url = URL(string: "https://itunes.apple.com/app/{APP_ID}?action=write-review")
UIApplication.shared.openAppStoreReviewPage(url)

UIButton 扩展

向按钮添加带有自定义偏移量的右侧图像

let button = UIButton(frame: .zero)
button.addRightImage(image, offset: 16)

UICollectionView 扩展

安全地注册和出列您的 UICollectionViewCell

// 1. Make your `UICollectionCell` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)
final class ReusableClassCollectionViewCell: UICollectionViewCell, Reusable {}
// 2. Register your cell:
collectionView.register(cellType: ReusableClassCollectionViewCell.self)
// 3. Dequeue your cell:
let cell: ReusableClassCollectionViewCell = collectionView.dequeueReusableCell(at: indexPath)

安全地注册和出列您的 UICollectionReusableView

// 1. Make your `UICollectionReusableView` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)
final class ReusableNibCollectionReusableView: UICollectionReusableView, NibReusable
// 2. Register your cell:
collectionView.register(supplementaryViewType: ReusableNibCollectionReusableView.self, ofKind: UICollectionView.elementKindSectionHeader)
// 3. Dequeue your cell:
let header: ReusableNibCollectionReusableView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath)

UICollectionViewCell 扩展

向单元格应用圆角

let cell = UICollectionViewCell()
cell.applyCornerRadius(10)

在单元格高亮显示时添加动画

class MyCollectionViewCell: UICollectionViewCell {
    // ...
    override var isHighlighted: Bool {
        willSet {
            self.animate(scale: newValue, options: .curveEaseInOut) // Note that the animation is customisable, but all parameters as default value
        }
    }
    // ...
}

UIFont 扩展

获取可缩放以支持动态类型的字体

let font = UIFont.dynamicStyle(.body, traits: .traitsBold)

UIDevice 扩展

访问您的设备信息

print(UIDevice.idForVendor) // 104C9F7F-7403-4B3E-B6A2-C222C82074FF
print(UIDevice.systemName()) // iPhone OS
print(UIDevice.systemVersion()) // 9.0
print(UIDevice.deviceName) // iPhone Simulator / iPhone 6 Wifi
print(UIDevice.deviceLanguage) // en
print(UIDevice.isPhone) // true or false
print(UIDevice.isPad) // true or false

检查您的系统版本

print(UIDevice.isVersion(8.1)) // false
print(UIDevice.isVersionOrLater(8.1)) // true
print(UIDevice.isVersionOrEarlier(8.1)) // false

强制设备方向

UIDevice.forceRotation(.portrait)
UIDevice.current.forceRotation(.portrait)

UIImage 扩展

从颜色创建图像

let image = UIImage(color: .green)

用颜色填充图像

let image = UIImage(named: "image")
let greenImage = image.filled(with: .green)

将图像与另一个图像组合

let image = UIImage(named: "image")
let image2 = UIImage(named: "image2")
let combinedImage = image.combined(with: image2)

更改渲染模式

var image = UIImage(named: "image")
image = image.template // imageWithRenderingMode(.alwaysTemplate)
image = image.original // imageWithRenderingMode(.alwaysOriginal)

UILabel 扩展

为标签配置动态文本样式

label.configureDynamicStyle(.body, traits: .traitBold)

检测标签文本是否被截断

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))
label.text = "I will be truncated :("
print(label.isTruncated()) // true

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))
label.text = ":)"
print(label.isTruncated()) // false

自定义标签行高

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))
label.setText("A long multiline text")
label.setLineHeight(0.9)

自定义标签截断文本(替换默认的 ...

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 30, height: 40))
label.setText("I will be truncated :(", truncatedText: ".")
print(label.text) // I wi.

UIScreen 扩展

获取屏幕方向

if UIInterfaceOrientationIsPortrait(UIScreen.currentOrientation) {
    // Portrait
} else {
    // Landscape
}

获取屏幕尺寸

print(UIScreen.size) // CGSize(375.0, 667.0) on iPhone6
print(UIScreen.width) // 375.0 on iPhone6
print(UIScreen.height) // 667.0 on iPhone6
print(UIScreen.heightWithoutStatusBar) // 647.0 on iPhone6

获取状态栏高度

print(UIScreen.statusBarHeight) // 20.0 on iPhone6

UISlider 扩展

使用 UITapGestureRecognizer 获取用户点击的值

let slider = UISlider(frame: .zero)
slider.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(sliderTapped(_:))))

func sliderTapped(sender: UITapGestureRecognizer) {
    let value = slider.value(for: sender)
}

UIStoryboard 扩展

获取应用程序的主 storyboard

let storyboard = UIStoryboard.main

UISwitch 扩展

切换 UISwitch

let aSwitch = UISwitch(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
aSwitch.toggle()
print(aSwitch.isOn) // true

aSwitch.toggle(animated: false)

UITableView

安全地注册和出列您的 UITableViewCell

// 1. Make your `UITableViewCell` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)
final class ReusableClassTableViewCell: UITableViewCell, Reusable {}
// 2. Register your cell:
tableView.register(cellType: ReusableClassTableViewCell.self)
// 3. Dequeue your cell:
let cell: ReusableClassTableViewCell = tableView.dequeueReusableCell(at: indexPath)

安全地注册和出列您的 UITableViewHeaderFooterView

// 1. Make your `UITableViewHeaderFooterView` conforms to `Reusable` (class-based) or `NibReusable` (nib-based)
final class ReusableClassHeaderFooterView: UITableViewHeaderFooterView, Reusable {}
// 2. Register your header or footer:
tableView.register(headerFooterViewType: ReusableClassHeaderFooterView.self)
// 3. Dequeue your header or footer:
let cell: ReusableClassHeaderFooterView = tableView.dequeueReusableHeaderFooterView()

UITextField 扩展

为文本字段配置动态文本样式

textField.configureDynamicStyle(.body, traits: .traitBold)

修改清除按钮图像

let clearButtonImage = UIImage(named: "clear_button")
let textField = UITextField()
textField.setClearButton(with: clearButtonImage)

修改占位符颜色

let textField = UITextField()
// set `placeholder` or `attributedPlaceholder`
textField.setPlaceHolderTextColor(.blue)

UITextView 扩展

为文本字段配置动态文本样式

textView.configureDynamicStyle(.body, traits: .traitBold)

UIView 扩展

轻松更改视图的 frame

let aView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
aView.x += 100 // move  to right
aView.y += 100 // move downwards
aView.width -= 10 // make the view narrower
aView.height -= 10 // make the view shorter 

向视图应用圆角

let view = UIView()
view.applyCornerRadius(10)
view.applyCornerRadius(20, maskedCorners: [.layerMaxXMaxYCorner])

查找包含此视图的 ViewController

let parent: UIViewController? = aView.parentViewController

使用其 accessibilityIdentifier 查找子视图,对于测试私有 outlets 很有用

aView.findView(forIdentifier: "accessibilityIdentifier")

查找与特定类型对应的第一个子视图

let scrollView: UIScrollView? = aView.findView()

添加 SwiftUI View 作为子视图

aView.addSubSwiftUIView(SwiftUIView())

自动化您的本地化

aView.translateSubviews()

它将迭代视图的所有子视图,并使用文本/占位符作为 NSLocalizedString 中的键。通过在您的 xib/storyboard 中设置您的本地化键,只需调用上述方法,您的所有字符串都将自动翻译。

在视图及其父视图之间添加约束

aView.addConstraints() // Add constraints to all edges with zero insets
aView.addConstraints(to: [.top, .bottom]) // Add constraints to top and bottom edges with zero insets
aView.addConstraints(to: [.top, .left], insets: UIEdgeInsets(top: 10, left: 20, bottom: 0, right: 0)) // Add constraints to top and left edges with custom insets

UIViewController 扩展

为任何视图控制器生成 Xcode 预览

@available(iOS 13, *)
struct MyViewPreview: PreviewProvider {
    static var previews: some View {
        MyViewController().preview
    }
}

通过删除之前的视图控制器来重置导航堆栈

let navController = UINavigationController()
navController.pushViewController(vc1, animated: true)
navController.pushViewController(vc2, animated: true)
navController.pushViewController(vc3, animated: true)
vc3.removePreviousControllers(animated: true)
print(navController.viewControllers) // [vc3]

检查 ViewController 是否在屏幕上且未隐藏

let viewController = UIViewController()
print(viewController.isVisible) // false

检查 ViewController 是否以模态方式呈现

let viewController = UIViewController()
print(viewController.isModal)

以模态方式打开 Safari

let url = URL(string: "https://www.apple.com")
vc.openSafariVC(url: url, delegate: self)

将子视图控制器添加到另一个控制器

vc.addChildController(childVC, subview: vc.view, animated: true, duration: 0.35, options: [.curveEaseInOut, .transitionCrossDissolve])

将子视图控制器添加到容器视图

vc.addChildController(childVC, in: containerView)

移除子视图控制器

vc.removeChildController(childVC)

将 SwiftUI View 添加为输入 UIView 的子项

vc.addSubSwiftUIView(SwiftUIView(), to: vc.view)

UIKit 协议

NibLoadable

使您的 UIView 子类符合此协议,以便从它们的 NIB 安全地实例化它们。注意: 确保您的 UIView 基于 Nib,并用作 Xib 的根视图。

class NibLoadableView: UIView, NibLoadable {
    // ...
}

let view = NibLoadableView.loadFromNib()

NibOwnerLoadable

使您的 UIView 子类符合此协议,以便从它们的 Xib 的文件所有者安全地实例化它们。注意: 确保您的 UIView 基于 Nib,并用作 Xib 的文件所有者。

class NibLoadableView: UIView, NibOwnerLoadable {
    // ...
    
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      self.loadNibContent()
    }

}

// Then use it directly from another xib or whatever...

AppKit, Cocoa 扩展

NSView 扩展

轻松更改视图的 frame

let aView = NSView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
aView.x += 100 // move  to right
aView.y += 100 // move downwards
aView.width -= 10 // make the view narrower
aView.height -= 10 // make the view shorter 

自动化您的本地化

aView.convertLocalizables()

它将迭代视图的所有子视图,并使用文本/占位符作为 NSLocalizedString 中的键。通过在您的 xib/storyboard 中设置您的本地化键,只需调用上述方法,您的所有字符串都将自动翻译。

协议

Injectable

协议,用于在 Swift 中使用 Storyboard 和 Segue 进行 ViewController 数据注入。灵感来自 Nastasha's blog

class RedPillViewController: UIViewController, Injectable {

    @IBOutlet weak private var mainLabel: UILabel!

    // the type matches the IOU's type
    typealias T = String

    // this is my original dependency (IOU)
    // I can now make this private!
    private var mainText: String!

    override func viewDidLoad() {
        super.viewDidLoad()

        // this will crash if the IOU is not set
        assertDependencies()

        // using the IOU if needed here,
        // but using it later is fine as well
        mainLabel.text = mainText
    }

    // Injectable Implementation
    func inject(text: T) {
        mainText = text
    }

    func assertDependencies() {
        assert(mainText != nil)
    }
}

// ViewController that will inject data...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

    switch segueIdentifierForSegue(segue) {
    case .TheRedPillExperience
        let redPillVC = segue.destinationViewController as? RedPillViewController
        redPillVC?.inject("😈")
    case .TheBluePillExperience:
        let bluePillVC = segue.destinationViewController as? BluePillViewController
        bluePillVC?.inject("👼")
    }
}

Occupiable

以下用例适用于字符串数组、字典和集合

isEmpty / isNotEmpty

仅限非可选类型

var string = "Hello world"
print(string.isNotEmpty) // true
print(string.isEmpty) // false

isNilOrEmpty

仅限可选类型

let string: String? = ""
print(string.isNilOrEmpty) // true

Then

Swift 初始化程序的语法糖

let label = UILabel().then {
    $0.textAlignment = .Center
    $0.textColor = .blackColor()
    $0.text = "Hello, World!"
}

PropertyWrappers

UserDefaultsBacked

类型安全的 UserDefaults 访问,支持默认值。

struct SettingsViewModel {
    @UserDefaultsBacked(key: "search-page-size", defaultValue: 20)
    var numberOfSearchResultsPerPage: Int

    @UserDefaultsBacked(key: "signature")
    var messageSignature: String?
}

其他

UnitTesting

Grand Central Dispatch 语法糖

检测 UITests 是否正在运行

if UnitTesting.isRunning {
  // tests are running
} else {
  // everything is fine, move along
}

测量测试性能

func testPerformance() {
  let measurement = measure {
    // run operation
  }
}

UITesting

检测 UITests 是否正在运行

if UITesting.isRunning {
  // tests are running
} else {
  // everything is fine, move along
}

Shell Utility

(仅限 macOS)

在系统 shell 上运行命令,并提供成功返回代码、STDOUT 和 STDERR。

STDOUT 作为连续的字符串

let (rCode, stdOut, stdErr) = SystemUtility.shell(["ls", "-l", "/"])
// rCode = 0 (which is "true" in shell)
// stdOut = "total 13\ndrwxrwxr-x+ 91 root  admin  2912 Feb 11 01:24 Applications" ...  etc
// stdErr = [""]

STDOUT 作为由换行符分隔的字符串数组

let (rCode, stdOut, stdErr) = SystemUtility.shellArrayOut(["ls", "-l", "/"])
// rCode = 0 (which is "true" in shell)
// stdOut = ["total 13", "drwxrwxr-x+ 91 root  admin  2912 Feb 11 01:24 Applications" ...  etc]
// stdErr = [""]

安装

手动

将 SwiftyUtils 文件夹复制到您的 Xcode 项目中。(确保将文件添加到您的目标)

CocoaPods

pod SwiftyUtils 添加到您的 Podfile。

Carthage

github "tbaranes/SwiftyUtils" 添加到您的 Cartfile。

Swift Package Manager

您可以使用 Swift Package Manager 通过将正确的描述添加到您的 Package.swift 文件来安装 SwiftyUtils

import PackageDescription

let package = Package(
    dependencies: [
        .Package(url: "https://github.com/tbaranes/SwiftyUtils.git", majorVersion: 0)
    ]
)

反馈

联系方式

许可

SwiftyUtils 基于 MIT 许可证。有关更多信息,请参阅 LICENSE 文件。dic.testAll