鍍金池/ 教程/ iOS/ 自定義 Formatters
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

自定義 Formatters

我們希望有一種快速的一次性的解決方案,可以把數(shù)據(jù)格式化為一種易讀的格式。Foundation 框架中的就有 NSFormatter 可以很好地勝任這個(gè)工作。另外,在 Mac 上,Appkit 已經(jīng)內(nèi)建了 NSFormatter 的支持。

內(nèi)建格式器

Foundation 框架中的 NSFormatter 是一個(gè)抽象類,它有兩個(gè)已經(jīng)實(shí)現(xiàn)的子類:NSNumberFormatterNSDateFormatter?,F(xiàn)在我們先跳過(guò)這些,來(lái)實(shí)現(xiàn)我們自己的子類。

如果你想了解更多的相關(guān)知識(shí),我推薦閱讀 NSHipster。

介紹

NSFormatter 除了拋出錯(cuò)誤,其它什么事也不做。我還不知道有人想要用這個(gè),當(dāng)然如果它對(duì)你有用,就去用它吧。

因?yàn)槲覀儾幌矚g錯(cuò)誤,我們?cè)诖藢?shí)現(xiàn)一個(gè) NSFormatter 的子類,它可以把 UIColor 實(shí)例轉(zhuǎn)換成可讀的名字。例如,以下代碼可以返回字符串“Blue”:

KPAColorFormatter *colorFormatter = [[KPAColorFormatter alloc] init];
[colorFormatter stringForObjectValue:[UIColor blueColor]] // Blue

NSFormatter 的子類化有兩個(gè)方法需要實(shí)現(xiàn):stringForObjectValue:getObjectValue:ForString:errorDescription:。我們先開始介紹第一個(gè)方法,因?yàn)檫@個(gè)方法更常用。第二個(gè)方法,就我所知,經(jīng)常用于 OS X 上,并且通常不是很有用,我們將稍后介紹。

初始化

首先,我們需要做些初始化的工作。由于沒(méi)有事先定義好的字典可以把顏色映射至名字,這些工作將由我們來(lái)完成。為了簡(jiǎn)化,這些工作將在初始化方法中完成:

- (id)init;
{
    return [self initWithColors:@{
        [UIColor redColor]: @"Red",
        [UIColor blueColor]: @"Blue",
        [UIColor greenColor]: @"Green"
    }];
}

這里的 colors 是一個(gè)以 UIColor 實(shí)例為鍵,英語(yǔ)名為值的字典。大家可以自行地去實(shí)現(xiàn) initWithColors: 方法。當(dāng)然你也可以自行實(shí)現(xiàn),或者直接前往 Github repo 獲得答案。

格式化對(duì)象值

由于我們這里只可以格式化 UIColor 實(shí)例對(duì)象,于是在方法 stringForObjectValue: 中的第一件事就是判斷傳入的參數(shù)類型是否是 UIColor 類。

- (NSString *)stringForObjectValue:(id)value;
{
    if (![value isKindOfClass:[UIColor class]]) {
        return nil;
    }

    // To be continued...
}

在判斷參數(shù)合法后,我們可以實(shí)現(xiàn)真正的邏輯了。我們的格式器中包含一個(gè) UIColor 對(duì)象為鍵,顏色名為值的字典。因此,我們只需要以 UIColor 對(duì)象為鍵找到對(duì)應(yīng)的值:

- (NSString *)stringForObjectValue:(id)value;
{
    // Previously on KPAColorFormatter

    return [self.colors objectForKey:value];
}

以上代碼是一個(gè)盡可能簡(jiǎn)單的實(shí)現(xiàn)。一個(gè)更高級(jí)(有用)的格式器應(yīng)該是在我們的顏色字典中沒(méi)有找到匹配的顏色時(shí),返回一個(gè)最接近的顏色。大家可以自行實(shí)現(xiàn),或是你不想花費(fèi)太多功夫,可以前往 Github repo

反向格式化

我們的格式器也應(yīng)該支持反向格式化,即把字符串轉(zhuǎn)成實(shí)例對(duì)象。這是通過(guò) getObjectValue:forString:errorDescription: 方法實(shí)現(xiàn)。在 OS X 上,在使用 NSCell 時(shí)會(huì)經(jīng)常用到這個(gè)方法。

NSCell 有一個(gè) objectValue 屬性。默認(rèn)情況下,NSCell 會(huì)用 objectValue 的描述,但是它也可以選擇用一個(gè)格式器。在用 NSTextFieldCell 時(shí),用戶可以輸入值,作為程序員,我們可能期望 objedctValue 可以根據(jù)根據(jù)輸入的字符串轉(zhuǎn)成一個(gè) UIColor 實(shí)例。例如,用戶如果輸入“Blue”,我們需要返回一個(gè) [UIColor blueColor] 實(shí)例的引用。

實(shí)現(xiàn)反向格式化分為兩部分:一部分為當(dāng)格式器可以成功地把字符串轉(zhuǎn)成 UIColor 實(shí)例,另一部分當(dāng)其不能成功轉(zhuǎn)換。第一部分代碼如下:

- (BOOL)getObjectValue:(out __autoreleasing id *)obj 
             forString:(NSString *)string 
      errorDescription:(out NSString *__autoreleasing *)error;
{
    __block UIColor *matchingColor = nil;
    [self.colors enumerateKeysAndObjectsUsingBlock:^(UIColor *color, NSString *name, BOOL *stop) {
        if([name isEqualToString:string]) {
            matchingColor = color;
            *stop = YES;
        }
    }];

    if (matchingColor) {
        *obj = matchingColor;
        return YES;
    } // Snip

這里可以做一些優(yōu)化,但是我們先不去做這些。以上方法會(huì)遍歷我們顏色字典里的每一個(gè)對(duì)象 ,當(dāng)一個(gè)顏色名字找到時(shí),則會(huì)返回其對(duì)應(yīng)關(guān)聯(lián)的 UIColor 實(shí)例對(duì)象的引用,同時(shí)返回 YES 告知調(diào)用者我們已經(jīng)成功地把字符串轉(zhuǎn)成了一個(gè) UIColor 實(shí)例對(duì)象。

現(xiàn)在處理第二部分:

if (matchingColor) {
    // snap
} else if (error) {
    *error = [NSString stringWithFormat:@"No known color for name: %@", string];
}

return NO;

這里,我們?nèi)绻荒苷业揭粋€(gè)匹配的顏色,我們會(huì)檢測(cè)調(diào)用者是否需要錯(cuò)誤信息,如果需要,則把錯(cuò)誤通過(guò)引用返回。這里檢查錯(cuò)誤很重要。如果你不這樣做,程序就會(huì) crash。同時(shí),我們也會(huì)返回 NO,告知調(diào)用者這次轉(zhuǎn)換失敗。

本地化

到現(xiàn)在,我們已經(jīng)建立了一個(gè)完全功能的 NSFormatter 的子類,當(dāng)然這只是對(duì)于生活在美國(guó)的英語(yǔ)使用者而言有用。

但相比全世界 71.3 億人,那才 3.19 億?;蛘哒f(shuō),你還有 96% 的潛在用戶。當(dāng)然你可以說(shuō):這些潛在用戶絕大部分都不是 iPhone 或 Mac 使用者,這么做有什么意思呢?這么想你就太掃興了。

NSNumberFormatterNSDateFormatter 都有一個(gè) locale 屬性,它是 NSLocale 實(shí)例對(duì)象。我們現(xiàn)在來(lái)擴(kuò)展格式器以支持本地化,讓它可以根據(jù) local 屬性來(lái)返回對(duì)應(yīng)翻譯的名字。

翻譯

首先,我們需要翻譯顏色名字字符串。有關(guān) genstring 與 *.lprojs 超出了本文的范圍。有很多文章討論這點(diǎn)。好了,不需要其它工作了,快要結(jié)束了。

本地化的格式化

接下來(lái)是本地化功能的實(shí)現(xiàn)。在獲取翻譯的字符串后,我們需要更新 stringForObejectValue: 方法。以前已經(jīng)使用過(guò) NSLocalizedString 的人可能已經(jīng)早早的把每一個(gè)字符串都用 NSLocalizedString 替換了。但是我們不會(huì)這么做。

我們現(xiàn)在處理的是一個(gè)動(dòng)態(tài)的 local,而 NSLocalizedString 只會(huì)查找當(dāng)前默認(rèn)的語(yǔ)言的翻譯。在99%的情況下,這種默認(rèn)的行為是你所想要的,但是我們會(huì)用格式化器的 locale 屬性來(lái)動(dòng)態(tài)查詢語(yǔ)言。

以下是 stringForObjectValue: 的新的實(shí)現(xiàn):

- (NSString *)stringForObjectValue:(id)value;
{
    // Previously on... don't you hate these? I just watched that 20 seconds ago!

    NSString *languageCode = [self.locale objectForKey:NSLocaleLanguageCode];
    NSURL *bundleURL = [[NSBundle bundleForClass:self.class] URLForResource:languageCode 
                                                              withExtension:@"lproj"];
    NSBundle *languageBundle = [NSBundle bundleWithURL:bundleURL];
    return [languageBundle localizedStringForKey:name value:name table:nil];
}

上面的代碼還有可以重構(gòu)改進(jìn)的地方,但因?yàn)榘汛a都放在同一個(gè)地方可以方便閱讀,所以請(qǐng)大家多多包涵了。

首先,我們通過(guò) locale 屬性查找相應(yīng)的語(yǔ)言,之后通過(guò) NSBundle 找到對(duì)應(yīng)的語(yǔ)言代碼。最后,我們會(huì)讓 bundle 對(duì)英語(yǔ)名稱進(jìn)行翻譯。如果找不到對(duì)應(yīng)的翻譯,則會(huì)返回 name: 方法的參數(shù)(即英語(yǔ)名稱)。如上即是 NSLocalizedString 的具體實(shí)現(xiàn)。

本地化的反向格式化

同樣,我們也可以把顏色名稱轉(zhuǎn)成 UIColor 實(shí)例對(duì)象,當(dāng)然,我認(rèn)為這樣做是不值得的。我們當(dāng)前的實(shí)現(xiàn)適用于99%的情況。另外1%的情況是在 Mac 的 NSCell 上使用,而且你允許用戶輸入一個(gè)你試圖解析的顏色的名字,這所需要做的要比簡(jiǎn)單的 子類化 NSFormatter 復(fù)雜很多?;蛟S,你不應(yīng)該允許你的用戶通過(guò)文本輸入顏色值。NSColorPanel 在這里是一個(gè)更好的解決方案。

屬性化字符串

到目前為止,我們的格式器都按我們預(yù)期的工作。接下來(lái)讓我們做一個(gè)完全沒(méi)用的功能,只是示范一下我們可以這么做,你懂的。

格式器同時(shí)支持屬性化字符串。要不要支持它取決于你特定的應(yīng)用與其用戶界面。因此,你最好把這個(gè)功能做成可配置。

以下代碼就是將文本顏色設(shè)置為當(dāng)前正在格式化的顏色:

- (NSAttributedString *)attributedStringForObjectValue:(id)value 
                                 withDefaultAttributes:(NSDictionary *)defaultAttributes;
{
    NSString *string = [self stringForObjectValue:value];

    if  (!string) {
        return nil;
    }

    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:defaultAttributes];
    attributes[NSForegroundColorAttributeName] = value;
    return [[NSAttributedString alloc] initWithString:string attributes:attributes];
}

首先,我們?nèi)缰耙粯犹幚碜址缓髾z查格式化是否成功。然后我們把默認(rèn)的屬性值與前面設(shè)置的顏色屬性結(jié)合后,最終返回屬性化字符串。很容易,是嗎?

便捷

因?yàn)槌跏蓟瘍?nèi)建的格式器太慢了,所以通常需要對(duì)外給你的格式器提供一個(gè)便利的類方法。這個(gè)格式器應(yīng)該用默認(rèn)值與當(dāng)前的本地化環(huán)境。以下是格式器的實(shí)現(xiàn):

+ (NSString *)localizedStringFromColor:(UIColor *)color;
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        KPAColorFormatterReusableInstance = [[KPAColorFormatter alloc] init];
    });

    return [KPAColorFormatterReusableInstance stringForObjectValue:color];
}

除非你的格式器像 NSNumberFormatterNSDateFormatter 一樣做一些瘋狂的事情 ,你可能不需要因?yàn)樾阅軉?wèn)題這么做。但是這樣做也可以讓使用格式器簡(jiǎn)單許多。

總結(jié)

我們的顏色格式器現(xiàn)在可以把一個(gè) UIColor 實(shí)例格式成一個(gè)可讀的名字或是反過(guò)來(lái)也行。當(dāng)然還有放多有關(guān) NSFormatter 的事情沒(méi)有涉及。特別是在 Mac 上,因?yàn)樗?NSCell 相關(guān),你可以用更多高級(jí)的特性。例如當(dāng)用戶在編輯的時(shí),你可以對(duì)字符串做一些檢測(cè)。

我們的格式器還可以做更多自定義的事情。例如,在沒(méi)查找到一個(gè)你需要的顏色名字時(shí),我們可以返回給你最相近的顏色名字。有時(shí),你可能需要我們的格式器有一個(gè) Boolean 屬性來(lái)控制該功能。或許我們的屬性化字符串的格式化不是你想要的,并且應(yīng)該支持更多自定義操作。

就此,我們完成了一個(gè)非??煽康母袷狡鳌K械拇a(伴有 OS X 示例)都放在了 Github 上, 并且你也可以在 CocoaPods 上看到。如果你應(yīng)用需要此功能,可以將 "KPAColorFormatter" 放在你的 Podfile 中,開始使用它吧。

上一篇:自定義控件下一篇:MVVM 介紹