分享一些自己Swift项目中用到的tips(持续更新)
1.巧用系统协议
extension Optional where Wrapped == String {
public var nilIfEmpty: String? {
guard let value = self else { return nil }
return value.isEmpty ? nil : value
}
}
扩展了Optional协议,添加了nilIfEmpty属性,添加判断:当当前字符串为空时返回nil
举个
普通青年的字符判断
guard let mobile = self.mobileTextField.text, !mobile.isEmpty else {
self.showErrorHUD("手机号码不能为空")
return
}
文艺青年的字符判断
guard let mobile = self.mobileTextField.text.nilIfEmpty else {
self.showErrorHUD("手机号码不能为空")
return
}
再如
extension URL: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
guard let url = URL(string: value) else {
preconditionFailure("url transform is failure")
}
self = url
}
}
通过扩展URL实现ExpressibleByStringLiteral协议,让url有了字面量赋值方式
举个
let url = "https://www.baidu.com"
let request = URLRequest(url: url)
2.规范Segue跳转
如果你的项目使用的是StoryBoard来进行开发的话肯定对Segue跳转不会陌生,但是随之而来的问题就是为了区别不同的segue而存在的identifier会以字符串的形式存在于各处,一处修改后需要所有地方同步修改,不然就会导致crash。那么有没有一种规范化的声明和调用的方式呢
protocol KYXSegueable {
associatedtype CustomSegueIdentifier: RawRepresentable
func performCustomSegue(_ segue: CustomSegueIdentifier, sender: Any?)
func customSegueIdentifier(forSegue segue: UIStoryboardSegue) -> CustomSegueIdentifier?
}
extension KYXSegueable where Self: UIViewController, CustomSegueIdentifier.RawValue == String {
func performCustomSegue(_ segue: CustomSegueIdentifier, sender: Any?) {
performSegue(withIdentifier: segue.rawValue, sender: sender)
}
func customSegueIdentifier(forSegue segue: UIStoryboardSegue) -> CustomSegueIdentifier? {
guard let identifier = segue.identifier, let customSegueIndentifier = CustomSegueIdentifier(rawValue: identifier) else {
return nil
// fatalError("Cannot get custom segue indetifier for segue: \(segue.identifier ?? "")")
}
return customSegueIndentifier
}
}
我们定义了一个KYXSegueable的协议,关联了一个RawRepresentable类型,并定义了两个和系统类似的方法用于跳转和获取identifier,有关RawRepresentable可以进入Api看一下,遵循它的对象具有rawValue的属性和功能,我们最常见的枚举就遵循这类协议
那么如何使用呢
class KYXHomeViewController: KYXBaseViewController, KYXSegueable {
enum CustomSegueIdentifier: String {
case loginSegue
case coinListSegue
case vehicleListSegue
case dataSwitchSegue
case addDeviceSegue
case tripSegue
}
......
控制器实现KYXSegueable协议,并声明其所需的遵循RawRepresentable协议的关联类型,没错,就是一组枚举了,我们可以在这边定义我们跳转的segue
调用
self.performCustomSegue(.coinListSegue, sender: nil)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueId = CustomSegueIdentifier(rawValue: segue.identifier ?? "") else {
return
}
switch segueId {
case .vehicleListSegue:
let vehicleListController = segue.destination as? KYXMyVehicleListViewController
vehicleListController?.originalNavBarColor = self.navigationController?.navigationBar.titleTextAttributes?[NSAttributedStringKey.foregroundColor] as? UIColor
case .tripSegue:
let tripListController = segue.destination as? KYXTripListTableViewController
tripListController?.recentTrip = self.homeModel?.trip
default:
break
}
}
3.更好地使用tableView-组织Section & Row
open class KYXSection: NSObject {
open var rows: [KYXRow] = []
open var data: Any?
public convenience init(rows: [KYXRow]) {
self.init()
self.rows = rows
}
}
open class KYXRow: NSObject {
open var cellIdentifier: String = ""
open var data: Any?
open var rowHeight: CGFloat = UITableViewAutomaticDimension // 返回具体行高或UITableViewAutomaticDimension
public convenience init(_ cellIdentifier: String) {
self.init()
self.cellIdentifier = cellIdentifier
self.rowHeight = UITableViewAutomaticDimension
}
public convenience init(_ cellIdentifier: String, data: Any?) {
self.init()
self.cellIdentifier = cellIdentifier
self.data = data
self.rowHeight = UITableViewAutomaticDimension
}
public convenience init(cellIdentifier identifier: String, data: Any?, rowHeight: CGFloat) {
self.init()
self.cellIdentifier = identifier
self.data = data
self.rowHeight = rowHeight
}
}
如何使用
第一步,组织Section
private func configureSections(_ homeModel: HomeModel) {
self.sections.removeAll()
//热点新闻
if let news = homeModel.news, news.count > 0 {
let hotNewsSection = KYXSection(rows: [KYXRow(cellIdentifier: CellID.hotNews.rawValue, data: homeModel.news, rowHeight: 80)])
self.sections.append(hotNewsSection)
}
//驾驶得分
if let trip = homeModel.trip {
let driveGradeSection = KYXSection()
var subtitle = "近七天均值"
if let trip = homeModel.trip, !trip.hasData {
subtitle = "近七天暂无行程"
}
let driveHaederRow = KYXRow(cellIdentifier: CellID.hader.rawValue, data: ["title": "驾驶评分", "subtitle": subtitle], rowHeight: 38)
let gradeRow = KYXRow(cellIdentifier: CellID.driveGrade.rawValue, data: trip, rowHeight: 138)
driveGradeSection.rows.append(contentsOf: [driveHaederRow, gradeRow])
self.sections.append(driveGradeSection)
}
if let banners = homeModel.banners, banners.count > 0 {
let bannerSection = KYXSection()
let bannerRow = KYXRow(cellIdentifier: CellID.banner.rawValue, data: homeModel.banners, rowHeight: (self.view.bounds.width)/4)
bannerSection.rows.append(bannerRow)
self.sections.append(bannerSection)
}
if let ranks = homeModel.ranks, ranks.count > 0 {
let rankingSection = KYXSection()
let rankingHaederRow = KYXRow(cellIdentifier: CellID.hader.rawValue, data: ["title": "安全驾驶排名", "subtitle": "每周日24时结算"], rowHeight: 38)
let rankingRow = KYXRow(cellIdentifier: CellID.rank.rawValue, data: homeModel.ranks, rowHeight: 166)
rankingSection.rows.append(contentsOf: [rankingHaederRow, rankingRow])
self.sections.append(rankingSection)
}
}
..........
TableView代理方法
func numberOfSections(in tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sections[section].rows.count
}
更优雅的Cell类型判断
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = self.sections[indexPath.section].rows[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: row.cellIdentifier, for: indexPath)
let data = row.data
switch row.cellIdentifier {
case CellID.banner.rawValue:
let bannerCell = cell as? HomeBannerTableViewCell
bannerCell?.configureCell(data as? [ServiceManageModel])
case CellID.section.rawValue:
cell.backgroundColor = UIColor.white
case CellID.funcList.rawValue:
let cell = cell as? HomeFunctionListTableViewCell
cell?.configureCell(services: data as? [HomeService], index: indexPath.row)
case CellID.driveGrade.rawValue:
let cell = cell as? HomeDriveGradeTableViewCell
cell?.configureCell(model: data as? HomeTrip)
case CellID.rank.rawValue:
let rankCell = cell as? HomeRankingTableViewCell
rankCell?.configureCell(models: data as? [HomeRank])
case CellID.hotNews.rawValue:
let newsCell = cell as? HomeHotNewsTableViewCell
newsCell?.configureCell(models: data as? [HomeNews])
default:
return cell
}
return cell
}
当然这个方案还有优化的空间,比如将Row的cellidentifier属性用枚举来定义,判断和初始化的时候就不用操作rawValue了
4.简洁声明多个变量
对于一些相互有关联的变量,相比于在每行中声明一个,还有一种更简洁美观的方式:
var (top, left, width, height) = (0.0, 0.0, 100.0, 50.0)
//rect.width = width
5.Notification的管理
类似于Segue, 也可以通过枚举来规范管理项目中的Notification
extension NotificationCenter {
static func post(name: KYXNotification, object: Any?, userInfo: [AnyHashable: Any]?) {
NotificationCenter.default.post(name: NSNotification.Name.init(name.rawValue), object: object, userInfo: userInfo)
}
static func addObserver(_ observer: Any, selector: Selector, name: KYXNotification, object: Any?) {
NotificationCenter.default.addObserver(observer, selector: selector, name: NSNotification.Name.init(name.rawValue), object: object)
}
}
定义所需的通知名
public enum KYXNotification: String {
case loginTimeout
case loginSuccess
case logoutSuccess
}
使用
NotificationCenter.post(name: .loginSuccess, object: nil, userInfo: nil)
NotificationCenter.addObserver(self, selector: #selector(didLoginSuccess), name: .loginSuccess, object: nil)
Protocol + Enum + Extension组成了Swift中的三巨头 ,灵活运用它们可以产生很多有趣的东西