Swift Tips

分享一些自己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
    }


当然这个方案还有优化的空间,比如将Rowcellidentifier属性用枚举来定义,判断和初始化的时候就不用操作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中的三巨头 ,灵活运用它们可以产生很多有趣的东西


未完,待续...

你可能感兴趣的:(Swift Tips)