iOS三方库
如果是混编项目使用Swift第三方时,需要在Podfile文件中加上use_frameworks!。这是因为静态库的生成方式不同。
markdown
* 静态库:(静态链接库)(.a)在编译时会将库copy一份到目标程序中,编译完成之后,目标程序不依赖外部的库,也可以运行。 缺点是会使应用程序变大
* 动态库:(.dylib)编译时只存储了指向动态库的引用。
* 可以多个程序指向这个库,在运行时才加载,不会使体积变大,
* 但是运行时加载会损耗部分性能,并且依赖外部的环境,如果库不存在或者版本不正确则无法运行
* Framework:实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。可以是动态库也可以是静态库1
2
3
4
5
2
3
4
5
对于是否使用
Framework,CocoaPods 通过use_frameworks来控制.
如果不使用use_frameworks!,cocoapods会生成相应的 .a文件(静态链接库),Link Binary With Libraries: libPods-**.a 包含了其他用pod导入有第三库的.a文件。use_frameworks!表示使用动态库。
如果使用了use_frameworks!,cocoapods会生成对应的frameworks文件(包含了头文件,二进制文件,资源文件等等),Link Binary With Libraries:Pods_xxx.framework包含了其它用pod导入的第三方框架的.framework文件
markdown
1. 纯oc项目中 通过pod导入纯oc项目, 一般都不使用frameworks
2. swift 项目中通过pod导入swift项目,必须要使用use_frameworks!,在需要使用的到地方 `import AFNetworking`
3. swift 项目中通过pod导入OC项目, 使用`use_frameworks`,在桥接文件里加上`#import "AFNetworking/AFNetworking.h"`, 不使用frameworks,桥接文件加上`#import "AFNetworking.h"`1
2
3
2
3
SnapKit
SnapKit 和 Masonry 一样,是布局中常用的三方库,主要记录一些常用方法。
- 跟父类的关系
swift
// 跟父类大小一样
make.edges.equalToSuperview()
// 四周有间距
make.edges.equalToSuperview().inset(5)
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 10, left: 5, bottom: 20, right: 10))
// 左右间距相同
make.left.right.equalToSuperview().inset(5)
// 适配安全距离
make.top.equalTo(view.snp.topMargin)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 优先级
swift
make.top.equalTo(label.snp.top).priority(600)
make.top.equalTo(label.snp.top).priority(.medium)1
2
2
- 约束关系
方法 含义 equalTo()设置属性等于某个数值 greaterThanOrEqualTo()设置属性大于或等于某个数值 lessThanOrEqualTo()设置属性小于或等于某个数值 multipliedBy()设置属性乘以因子后的值
Moya
Moya图片上传、文档上传、下载文档
swift
// 第一步
enum InterfaceAPI {
// 获取想要存储的文件路径
var localLocation: URL {
switch self {
case .download(_, let fileName):
let filePath = FileManager.default.temporaryDirectory
return filePath.appendingPathComponent(fileName)
default:
return URL(string: "")!
}
}
// 封装下下载路径
var downloadDestination: DownloadDestination {
// `createIntermediateDirectories` will create directories in file path
return { _, _ in return (self.localLocation, [.removePreviousFile, .createIntermediateDirectories]) }
}
case uploadImg(files: [UIImage], name: String)
case documentationTranslation(files: [URL], name: String, transType: String)
case download(filePath: String, fileName: String)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
第二步
swift
extension InterfaceAPI: TargetType {
var baseURL: URL {
switch self {
case let .download(filePath, _):
return URL(string: filePath)!
default:
return URL(string: InterfaceUrl)!
}
}
var method: Moya.Method {
switch self {
case .download:
return .get
default:
return .post
}
}
var path: String {
switch self {
case .uploadImg
return "/assist/UploadImg"
case .documentationTranslation:
return "/translate/DocumentationTranslation"
case .download:
return ""
}
}
}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
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
第三步
swift
var task: Task {
// 公共参数
var params: [String: Any] = [:]
// 收集参数
switch self {
case let .uploadImg(files, name):
var formDatas = [MultipartFormData]()
for image in files {
let imageData = image.pngData()
let fileName = "iOS\(Date().millisecondsSince1970).png"
let formData = MultipartFormData(provider: .data(imageData!), name: name, fileName: fileName, mimeType: "image/png")
formDatas.append(formData)
}
return .uploadMultipart(formDatas)
case let .documentationTranslation(fileUrls, name, transType):
var formDatas = [MultipartFormData]()
for fileUrl in fileUrls {
do {
let data = try Data(contentsOf: fileUrl, options: .alwaysMapped)
let fileName = fileUrl.absoluteString.components(separatedBy: "/").last?.toString
let formData = MultipartFormData(provider: .data(data), name: name, fileName: fileName, mimeType: "multipart/form-data")
formDatas.append(formData)
}
catch{
}
}
params["transType"] = transType
return .uploadCompositeMultipart(formDatas, urlParameters: params)
case .download:
return .downloadDestination(downloadDestination)
}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
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
EmptyDataSet-Swift 空数据处理
- 新建一个空界面的类,里面有图标、文字、间距、点击回调和是否显示的属性
swift
class EmptyView{
var image: UIImage?
var allowShow: Bool = false // 是否显示
var verticalOffset: CGFloat = 0
var title: String = ""
private var tapClosure:(() -> Void)?
init(imgName: String = "nodata", title: String = "" , verticalOffset: CGFloat = 0, tapClosure: (() -> Void)?) {
self.image = UIImage.init(named: imgName)
self.verticalOffset = verticalOffset
self.tapClosure = tapClosure
self.title = title
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 在
UIScrollView的扩展 通过Runtime添加当前界面为属性,这样每个滚动视图都有这个属性
swift
extension UIScrollView{
private struct AssociatedKeys{
static var emptyKey: Void?
}
var empty: EmptyView?{
get{
return objc_getAssociatedObject(self, &AssociatedKeys.emptyKey) as? EmptyView
}
set{
self.emptyDataSetSource = newValue
self.emptyDataSetDelegate = newValue
objc_setAssociatedObject(self, &AssociatedKeys.emptyKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 让当前类实现
EmptyDataSetSource和EmptyDataSetDelegate协议,通过协议将数据返回过去
swift
extension EmptyView: EmptyDataSetSource, EmptyDataSetDelegate{
//返回标题
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let placeholserAttributes = [NSAttributedString.Key.foregroundColor : UIColor.textGrayColor,NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0, weight: .regular)]
return NSAttributedString(string:title ,attributes: placeholserAttributes)
}
//返回图片偏移量
func verticalOffset(forEmptyDataSet scrollView: UIScrollView) -> CGFloat {
return verticalOffset
}
//返回展示图片
internal func image(forEmptyDataSet scrollView: UIScrollView) -> UIImage? {
return image
}
//显示状态
internal func emptyDataSetShouldDisplay(_ scrollView: UIScrollView) -> Bool {
return allowShow
}
//点击时回调
internal func emptyDataSet(_ scrollView: UIScrollView, didTapView view: UIView) {
guard let tapClosure = tapClosure else { return }
tapClosure()
}
}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
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
- 使用时可以直接赋值
swift
tableView.empty = EmptyView.init(imgName: "loan_empty", title: localizedString("你没有任何贷款订单"),tapClosure: {
})1
2
3
2
3
HandJSON
模型需要继承自HandJSON,结构体和类都可以
swift
class Info: HandyJSON {
var age: Int = 0
required init() {}
}
class User: HandyJSON{
var name: String?
var infos: [Info]? // 数组
var first: Info? // 嵌套模型
required init() {}
//方法映射,把解析到的json数据,映射给其他的变量,
func mapping(mapper: HelpingMapper) {
mapper <<< self.first <-- "info" //把info 转换为模型中的 first
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
数据解析成模型
swift
let jsonString = """
{
"name":"user111",
"infos":[ {"age":"18"},
{ "age":"14"}
],
"info":{
"age":"19"
}
}
"""
// 这里是两个重名方法,可以传入json字符串和字典
let u = User.deserialize(from: jsonString)
let u1 = JSONDeserializer<User>.deserializeFrom(json: jsonString) // 这样也可以转
// 模型转换成字典,属性为nil的不显示,映射的键不会改成属性名
u.toJSON()
// 模型转换成字符串,属性为nil的不显示,映射的键不会改成属性名
u.toJSONString()
// 从json下一个子节点进行解析
let dict = u?.toJSON()
let i = Info.deserialize(from: dict, designatedPath: "info")
// 解析数组
let jsonAryString: String? = "[{\"age\":\"20\"}, {\"age\":\"21\"},{\"age\":\"22\"}]"
let infos = [Info].deserialize(from: jsonAryString)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
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
支持枚举类型
swift
enum AnimalType: String, HandyJSONEnum {
case Cat = "cat"
case Dog = "dog"
case Bird = "bird"
}
struct Animal: HandyJSON {
var name: String?
var type: AnimalType?
}
let jsonString = "{\"type\":\"cat\",\"name\":\"Tom\"}"
if let animal = Animal.deserialize(from: jsonString) {
print(animal.type?.rawValue)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HandJSON 和 SwiftyJson 配合使用
- 数组
json
{
"code": 200,
"data": {
"result": [
{
"id": 39,
"img": "pxs3-img-test.oss-cn-shenzhen.aliyuncs.com",
}
]
},
"msg": "SUCCESS"
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
使用 HandJSON 进行解析
swift
// 第一种
let list1 = JSONDeserializer<XDSAnchor>.deserializeModelArrayFrom(json: json["list"].description) ?? []
// 第二种
let list2 = [XDSAnchor].deserialize(from: json["list"].arrayObject) ?? []1
2
3
4
2
3
4
- 字典
json
{
"code": 200,
"data": {
"result": {
"transfer_shop": "2124",
}
},
"msg": "SUCCESS"
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
使用 HandJSON 进行解析
swift
// 第一种
let model = JSONDeserializer<XDSAnchor>.deserializeFrom(json: json.description, designatedPath: "data.result")
// 第二种
let model = XDSAnchor.deserialize(from: json.description, designatedPath: "data.result")1
2
3
4
2
3
4
JXSegmentedView
swift
class XDSHomeController: UIViewController {
// 头部标签数据源
private lazy var segmentedDataSource: JXSegmentedBaseDataSource = {
let sources = JXSegmentedTitleDataSource()
sources.isItemSpacingAverageEnabled = false
sources.itemSpacing = 20
sources.titles = ["热门","关注"]
sources.titleNormalColor = UIColor.textGrayColor
sources.titleSelectedColor = UIColor.black
sources.titleSelectedFont = UIFont.boldSystemFont(ofSize: 17)
return sources
}()
// 头部标签
private lazy var segmentedView: JXSegmentedView = {
let s = JXSegmentedView()
//segmentedViewDataSource一定要通过属性强持有!!!!!!!!!
s.dataSource = segmentedDataSource
//配置指示器
let indicator = JXSegmentedIndicatorImageView()
indicator.image = UIImage(named: "home_item_sel")
indicator.indicatorWidth = 24
indicator.indicatorHeight = 18
s.indicators = [indicator]
s.listContainer = listContainerView
return s
}()
// 底部view
lazy var listContainerView: JXSegmentedListContainerView! = {
return JXSegmentedListContainerView(dataSource: self)
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(segmentedView)
view.addSubview(listContainerView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
segmentedView.frame = CGRect(x: 12, y: statusBarHeight, width: screenWidth - 24, height: 44)
listContainerView.frame = CGRect(x: 0, y: navHeight, width: screenWidth, height: screenHeight - navHeight - tabbarHeight)
}
}
extension XDSHomeController: JXSegmentedListContainerViewDataSource {
func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
if let titleDataSource = segmentedView.dataSource as? JXSegmentedBaseDataSource {
return titleDataSource.dataSource.count
}
return 0
}
func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
return XDSHomeContentView()
}
}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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Reusable
File's Owner的方式加载,需要实现NibOwnerLoadable协议- View的方式加载,需要实现
NibLoadable协议 - xib文件如果有重用,需要实现
NibReusable协议 - 如果是纯代码的Cell,实现
Reusable协议可以达到重用效果,当然注册的时候还是要说明类型
自定义Xib View
- 使用
File's Owner的方式加载,需要实现NibOwnerLoadable协议,并且在init:coder方法中调用loadNibContent()方法swiftclass MyCustomWidget: UIView, NibOwnerLoadable{ required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.loadNibContent() // 必须调用,不然view视图大小不对 } }1
2
3
4
5
6 - 使用 View 的方式加载,需要实现
NibLoadable协议,高度需要在layoutSubview()中调整swiftclass MyCustomWidget: UIView, NibLoadable{ }1
- 使用
UITableViewCell
swift
// 注册
tableView.register(cellType: MySimpleColorCell.self)
// 获取cell
let cell = tableView.dequeueReusableCell(for: indexPath,cellType: MySimpleColorCell.self)
let cell = tableView.dequeueReusableCell(for: indexPath) as MySimpleColorCell
// cell如果使用xib,需要实现协议 NibReusable
class MySimpleColorCell: UITableViewCell, NibReusable { }
// cell如果使用纯代码编写,需要实现协议 Reusable
class MySimpleColorCell: UITableViewCell, Reusable { }1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- UICollectionViewCell
swift
// 注册 cell
collectionView.register(cellType: MyColorSquareCell.self)
// 获取cell
let cell = collectionView.dequeueReusableCell(for: indexPath,cellType: MyColorSquareCell.self)
let cell = collectionView.dequeueReusableCell(for: indexPath) as MyColorSquareCell
// cell如果使用xib,需要实现协议 NibReusable
class MyColorSquareCell: UICollectionViewCell, NibReusable { }
// cell如果使用纯代码编写,需要实现协议 Reusable
class MyColorSquareCell: UICollectionViewCell, Reusable { }1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- UICollectionReusableView
swift
// 注册区头
collectionView.register(supplementaryViewType: CollectionHeaderView.self,ofKind: UICollectionView.elementKindSectionHeader)
// 获取区头
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath) as CollectionHeaderView
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath,viewType: CollectionHeaderView.self)
// 区头如果是xib 需要实现协议 NibReusabl
class CollectionHeaderView: UICollectionReusableView, NibReusable { }1
2
3
4
5
6
7
2
3
4
5
6
7
⚠️注意 UICollectionReusableView 使用xib的时候,虽然系统会帮我们创建xib文件,但是并没有把类和xib文件进行关联,需要我们手动把类名填写上去
ZLPhotoBrowser
ZLPhotoBrowser是一款微信样式的图片选择器,支持预览/相册内拍照及录视频、拖拽/滑动选择,编辑图片/视频,支持多语言国际化等功能。
- 配置信息,如果有时要照片有时要视频需要重新配置
swift
ZLPhotoUIConfiguration.default().languageType(ZLLanguageType.indonesian) // 设置语言
ZLPhotoUIConfiguration.default().languageType = languageType()
ZLPhotoConfiguration.default().allowSelectGif = false
ZLPhotoConfiguration.default().allowSelectImage = false
ZLPhotoConfiguration.default().allowSelectVideo = true
ZLPhotoConfiguration.default().maxVideoSelectCount = 1
ZLPhotoConfiguration.default().maxSelectVideoDuration = 720000
ZLPhotoConfiguration.default().minSelectVideoDuration = 0
ZLPhotoUIConfiguration.default().sheetTranslucentColor = .white
let ps = ZLPhotoPreviewSheet()
ps.selectImageBlock = { [weak self] (model, isfinsh) in
guard let asset = model.first?.asset,
let imgName = asset.value(forKey: "filename") as? String,
let suffix = imgName.components(separatedBy: ".").last?.lowercased(),
suffix.contains("mp4") || suffix.contains("mov") else {
return
}
}
ps.showPhotoLibrary(sender: self)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 直接进入相册选择
swift
let ps = ZLPhotoPreviewSheet()
ps.selectImageBlock = { [weak self] results, isOriginal in
// your code
}
ps.showPhotoLibrary(sender: self)1
2
3
4
5
2
3
4
5
- 只拍照,不录视频,默认是录制视频的
swift
ZLPhotoConfiguration.default().allowRecordVideo = false
ZLPhotoConfiguration.default().allowEditImage = false
let camera = ZLCustomCamera()
camera.takeDoneBlock = { [weak self] image, videoUrl in
guard let img = image else { return }
self?.iconView.image = img
}
weakself.showDetailViewController(camera, sender: nil)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
第三方登录
Facebook获取个人信息
swift
// Facebook 获取个人信息,这里获取的头像大小是50
GraphRequest.init(graphPath: "me", parameters: ["fields": "id, name, email,picture"]).start { connection, result, error in
guard let result = result as? Dictionary<String,Any> else {return}
let json = JSON(result)
let fbid = json["id"].stringValue
let name = json["name"].stringValue
let picturl = json["picture"]["data"]["url"].stringValue
// 这里获取的头像大小是180
GraphRequest(graphPath: fbid + "/picture", parameters: ["type": "large","redirect": "0"], httpMethod: HTTPMethod.get).start { connection, result, error in
print(result)
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- instagram 分享图片
info.plist文件增加Key
xml
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
</array>1
2
3
4
2
3
4
swift
//将图片保存到相册,打开 instagram,取相册中的第一张图片。需要用户同意图片权限。
guard let openUrl = URL(string: "instagram://library?AssetPath=assets-library"){ return }
if UIApplication.shared.canOpenURL(openUrl){
UIApplication.shared.open(openUrl)
}1
2
3
4
5
2
3
4
5