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中的三巨头 ,灵活运用它们可以产生很多有趣的东西


未完,待续...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355