24个好用的Extension让代码更整洁(转自Medium)

avatar

​ 扩展(Extension)是swift和oc最好用的功能之一,能够无侵入的对现有系统类和工程添加新方法

作为一个iOS和Android的双料开发者,我经常能看到Android代码的功能方法更简洁,易懂;

当然通过扩展,我们也可以把这些特性使用在swift上,使swift项目也更精炼,更加好用

​ 以下代码是基于swift,但其实对于oc,大部分也可以很容易的移植

1.String.trim() and Swift.trimmed

​ 我们经常会用到string的trim功能,帮助把string里的空格,制表符,换行符等特殊符号去掉

扩展可以这么写

1
2
3
4
5
6
7
8
9
10
11
import Foundation

extension String {
var trimmed: String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}

mutating func trim() {
self = self.trimmed
}
}

用法:

1
2
3
var str1 = "  a b c d e   \n"
var str2 = str1.trimmed
str1.trim()

2. Int.toDouble() and Double.toInt()

​ Int和Double,CGFloat的转换是项目中经常会用到.但实际使用上遇到Double(a),必须要求a是非可选类型,所以经常要做额外处理

可以对Int和Double的扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
import Foundation

extension Int {
func toDouble() -> Double {
Double(self)
}
}

extension Double {
func toInt() -> Int {
Int(self)
}
}

用法:

1
2
let a = 15.78
let b = a.toInt()

3. String.toDate(…) and Date.toString(…)

​ 对Date和String的转换做易用的封装

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Foundation

extension String {
func toDate(format: String) -> Date? {
let df = DateFormatter()
df.dateFormat = format
return df.date(from: self)
}
}

extension Date {
func toString(format: String) -> String {
let df = DateFormatter()
df.dateFormat = format
return df.string(from: self)
}
}

用法:

1
2
3
let strDate = "2020-08-10 15:00:00"
let date = strDate.toDate(format: "yyyy-MM-dd HH:mm:ss")
let strDate2 = date?.toString(format: "yyyy-MM-dd HH:mm:ss")

4.Int.fenToYuan()

​ 没什么可说的,金额的分转元

扩展:

1
2
3
4
5
6
7
8

import Foundation

extension Int {
func fenToYuan() -> Double {
Double(self) / 100
}
}

用法:

1
2
let fen = 12350
let yuan = fen.fenToYuan()

5.String.asCoordinates()

​ string转成坐标,包括两个值,精度和纬度,在3D场景下还有高度数据,高度不是很常用

我们根据string里的逗号来解析,转成CLLocationCoordinate2D对象

扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Foundation
import CoreLocation

extension String {
var asCoordinates: CLLocationCoordinate2D? {
let components = self.components(separatedBy: ",")
if components.count != 2 { return nil }
let strLat = components[0].trimmed
let strLng = components[1].trimmed
if let dLat = Double(strLat),
let dLng = Double(strLng) {
return CLLocationCoordinate2D(latitude: dLat, longitude: dLng)
}
return nil
}
}

用法:

1
2
let strCoordinates = "41.6168, 41.6367"
let coordinates = strCoordinates.asCoordinates

6.String.asURL()

​ URL是用处理链接的常用类,很灵活,可以连接不同组建,可以把各种类型处理为URLs,同时,也经常会配合着string的使用

扩展:

1
2
3
4
5
6
7
import Foundation

extension String {
var asURL: URL? {
URL(string: self)
}
}

用法:

1
2
let strUrl = "https://medium.com"
let url = strUrl.asURL

7.UIDevice.vibrate()

​ iPhone的震动效果是手机行业内做的最好的,可以主动调用这个

扩展:

1
2
3
4
5
6
7
8
import UIKit
import AudioToolbox

extension UIDevice {
static func vibrate() {
AudioServicesPlaySystemSound(1519)
}
}

用法:

1
UIDevice.vibrate()

8.String.width(…) and String.height(…)

​ 计算UILabel里的text占用的宽度,根据Font计算

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import UIKit

extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)

return ceil(boundingBox.height)
}

func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)

return ceil(boundingBox.width)
}
}

extension NSAttributedString {
func height(withConstrainedWidth width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

return ceil(boundingBox.height)
}

func width(withConstrainedHeight height: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

return ceil(boundingBox.width)
}
}

用法:

1
2
let text = "Hello, world!"
let textHeight = text.height(withConstrainedWidth: 100, font: UIFont.systemFont(ofSize: 16))

9.String.containsOnlyDigits

​ 检查string是否只含有数字

扩展:

1
2
3
4
5
6
7
8
import Foundation

extension String {
var containsOnlyDigits: Bool {
let notDigits = NSCharacterSet.decimalDigits.inverted
return rangeOfCharacter(from: notDigits, options: String.CompareOptions.literal, range: nil) == nil
}
}

用法:

1
2
let digitsOnlyYes = "1234567890".containsOnlyDigits
let digitsOnlyNo = "12345+789".containsOnlyDigits

10.String.isAlphanumeric

​ 检查string是否只有数字和大小写字母,经常用于密码格式验证

扩展

1
2
3
4
5
6
7
import Foundation

extension String {
var isAlphanumeric: Bool {
!isEmpty && range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil
}
}

用法:

1
2
let alphanumericYes = "asd3kJh43saf".isAlphanumeric
let alphanumericNo = "Kkncs+_s3mM.".isAlphanumeric

11.String 下标

​ swift的截取内容是比较繁琐的,比如:获取第n位内容,可以只设置一个Int参数

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import Foundation

extension String {
subscript (i: Int) -> Character {
return self[index(startIndex, offsetBy: i)]
}

subscript (bounds: CountableRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
if end < start { return "" }
return self[start..<end]
}

subscript (bounds: CountableClosedRange<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(startIndex, offsetBy: bounds.upperBound)
if end < start { return "" }
return self[start...end]
}

subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
let start = index(startIndex, offsetBy: bounds.lowerBound)
let end = index(endIndex, offsetBy: -1)
if end < start { return "" }
return self[start...end]
}

subscript (bounds: PartialRangeThrough<Int>) -> Substring {
let end = index(startIndex, offsetBy: bounds.upperBound)
if end < startIndex { return "" }
return self[startIndex...end]
}

subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
let end = index(startIndex, offsetBy: bounds.upperBound)
if end < startIndex { return "" }
return self[startIndex..<end]
}
}

用法:

1
2
let subscript1 = "Hello, world!"[7...]
let subscript2 = "Hello, world!"[7...11]

12. UIImage.squared

​ 如果需要用户提交一张方形照片,他们很少有标准的方形照片,但一般App里的头像控件往往都是圆形或者方形的

这个扩展可以快速的做处理

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import UIKit

extension UIImage {
var squared: UIImage? {
let originalWidth = size.width
let originalHeight = size.height
var x: CGFloat = 0.0
var y: CGFloat = 0.0
var edge: CGFloat = 0.0

if (originalWidth > originalHeight) {
// landscape
edge = originalHeight
x = (originalWidth - edge) / 2.0
y = 0.0

} else if (originalHeight > originalWidth) {
// portrait
edge = originalWidth
x = 0.0
y = (originalHeight - originalWidth) / 2.0
} else {
// square
edge = originalWidth
}

let cropSquare = CGRect(x: x, y: y, width: edge, height: edge)
guard let imageRef = cgImage?.cropping(to: cropSquare) else { return nil }

return UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation)
}
}

​ 以上是类方法,也可以改为成实例方法

用法:

1
2
let img = UIImage() // Must be a real UIImage
let imgSquared = img.squared // img.squared() for method

13.UIImage.resized(…)

​ 图片资源上传服务器的时候,会有大小限制,往往会处理成一张小尺寸照片

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import UIKit

extension UIImage {
func resized(maxSize: CGFloat) -> UIImage? {
let scale: CGFloat
if size.width > size.height {
scale = maxSize / size.width
}
else {
scale = maxSize / size.height
}

let newWidth = size.width * scale
let newHeight = size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}

用法

1
2
let img2 = UIImage() // Must be a real UIImage
let img2Thumb = img2.resized(maxSize: 512)

可以结合第12条一起使用

1
2
let img = UIImage() // Must be a real UIImage
let imgPrepared = img.squared?.resized(maxSize: 512)

14.Int.toString()

​ 一般可以用”‘\(num)”的方法把Int类型转为string,但是有个问题,如果你的num是可选类型,则会把”optional”也加进去,Kotlin处理可选类型比较优雅:对任意类型someVar?.toString,返回可选string

​ 可惜,swift不允许对Any扩展,但至少可以给Int加上

扩展

1
2
3
4
5
6
7
import Foundation

extension Int {
func toString() -> String {
"\(self)"
}
}

用法

1
2
let i1 = 15
let i1AsString = i1.toString()

15.Double.toString()

​ 和14同理,对Double加toString方法,但限制是保留两位小数

扩展:

1
2
3
4
5
6
7
import Foundation

extension Double {
func toString() -> String {
String(format: "%.02f", self)
}
}

用法:

1
2
let d1 = 15.67
let d1AsString = d1.toString()

16.Double.toPrice()

​ 用的地方不多,转成”323,232,21.33$”这种格式

扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Foundation

extension Double {
func toPrice(currency: String) -> String {
let nf = NumberFormatter()
nf.decimalSeparator = ","
nf.groupingSeparator = "."
nf.groupingSize = 3
nf.usesGroupingSeparator = true
nf.minimumFractionDigits = 2
nf.maximumFractionDigits = 2
return (nf.string(from: NSNumber(value: self)) ?? "?") + currency
}
}

用法:

1
2
let dPrice = 16.50
let strPrice = dPrice.toPrice(currency: "€")

17.String.asDict

​ 把JSON格式的string拆成字典,swift很方便

扩展:

1
2
3
4
5
6
7
8
9

import Foundation

extension String {
var asDict: [String: Any]? {
guard let data = self.data(using: .utf8) else { return nil }
return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
}
}

用法:

1
2
let json = "{\"hello\": \"world\"}"
let dictFromJson = json.asDict

18.String.asArray

​ 和上个类似,转成集合类型

扩展:

1
2
3
4
5
6
7
8
import Foundation

extension String {
var asArray: [Any]? {
guard let data = self.data(using: .utf8) else { return nil }
return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any]
}
}

用法:

1
2
let json2 = "[1, 2, 3]"
let arrFromJson2 = json2.asArray

19.String.asAttributedString

​ 有时我们需要一些跨平台的文本格式,最常用的是HTML格式,UILabel可以对粗体,下划线等要是做处理.把HTML格式转成NSAttributedString格式,然后给UILabel.attributedText赋值使用

扩展:

1
2
3
4
5
6
7
8
import Foundation

extension String {
var asAttributedString: NSAttributedString? {
guard let data = self.data(using: .utf8) else { return nil }
return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
}
}

用法:

1
2
let htmlString = "<p>Hello, <strong>world!</string></p>"
let attrString = htmlString.asAttributedString

20.Bundle.appVersion

​ 可以从Info.plist里加载app的版本信息,用途有:

  • 获取版本信息
  • 检查是否有更新
  • 展示版本信息
  • 支持邮件里包含版本信息

扩展:

1
2
3
4
5
6
7
8
9
10
11
import Foundation

extension Bundle {
var appVersion: String? {
self.infoDictionary?["CFBundleShortVersionString"] as? String
}

static var mainAppVersion: String? {
Bundle.main.appVersion
}
}

用法:

1
let appVersion = Bundle.mainAppVersion

原文:

https://medium.com/better-programming/24-swift-extensions-for-cleaner-code-41e250c9c4c3

原文确实只有20个…