iOS Apprentice中文版-從0開始學(xué)iOS開發(fā)-第三十七課

要格式化日期洽洁,你將使用DateFormatter對象团秽。 你在上一個教程中看過這個類疯趟。 它將Date對象封裝的日期和時間轉(zhuǎn)換為人類可讀的字符串澎剥,同時考慮到用戶的語言和區(qū)域設(shè)置锡溯。

在上一個教程中,你每次要將Date轉(zhuǎn)換為字符串時哑姚,都會創(chuàng)建一個DateFormatter的新實(shí)例祭饭。 不幸的是,創(chuàng)建DateFormatter對象是一個比較費(fèi)時的事叙量。 換句話說倡蝙,初始化這個對象需要很長時間。 如果你這么做绞佩,你的app會變慢(并且更多的消耗手機(jī)電池)悠咱。

更好的辦法是只創(chuàng)建一次DateFormatter對象蒸辆,然后反復(fù)調(diào)用它。 就是直到應(yīng)用程序?qū)嶋H需要之前析既,我們不會創(chuàng)建DateFormatter對象躬贡。 這個原理被稱為延遲加載(lazy loading),它是開發(fā)iOS應(yīng)用程序的一個非常重要的模式眼坏。 可以極大程度的避免系統(tǒng)開銷拂玻。

此外,我們只會創(chuàng)建一個DateFormatter的實(shí)例宰译。 下次需要使用DateFormatter時檐蚜,我們不會創(chuàng)建一個新的實(shí)例,而是重新使用現(xiàn)有的實(shí)例沿侈。

你將使用一個私有的全局常量闯第。 這是一個常駐于LocationDetailsViewController類(全局global)之外的常量,但它僅在LocationDetailsViewController.swift文件(私有private)中可見缀拭。

打開LocationDetailsViewController.swift咳短,在import和class語句之間添加以下代碼:

private  let dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .medium
    formatter.timeStyle = .short
    return formatter
}()

這段代碼是什么意思?你創(chuàng)建了一個名為dateFormatter的常量蛛淋,它的類型是DateFormatter咙好。這個常量是私有(private)的,在LocationDetailsViewController.swift文件之外你無法使用它褐荷。

你同時給dateFormatter了一個初始值勾效,但是等于號的后面并不是一個值,而是由一對花括號括起來的代碼叛甫,說明這是一個閉包(closure)层宫。

通常,你創(chuàng)建一個新的對象是像下面這個樣子:

private let dateFormatter = DateFormatter()

但是要初始化日期格式其监,僅僅要創(chuàng)建一個DateFormatter實(shí)例是不夠的卒密,你還要設(shè)置這個實(shí)例的dateStyle和timeStyle屬性。

創(chuàng)建一個對象并且同時設(shè)置它的屬性棠赛,你可以通過閉包的方式實(shí)現(xiàn):

private  let dateFormatter: DateFormatter = {
    //這里寫上設(shè)置屬性的代碼
    return formatter
}()

閉包內(nèi)部是創(chuàng)建和初始化新的DateFormatter對象的代碼哮奇,然后將它們放入dateFormatter并且返回。

注意末尾的一對圓括號睛约,這是必須的鼎俘。

??: 如果你忘記了末尾的這對圓括號(),Swift會認(rèn)為你是想要把閉包本身分配給dateFormatter辩涝,換而言之贸伐,dateFormatter的值將是一段代碼,而不是實(shí)際的DateFormatter對象怔揩。
這對圓括號的作用就是執(zhí)行閉包中的代碼捉邢,并且將返回DateFormatter對象給到dateFormatter常量脯丝。

使用閉包來同時創(chuàng)建并且設(shè)置對象是非常常見的技巧,你在Swift編程中會經(jīng)常遇到這種情況伏伐。

在Swift中宠进,全局變量始終以惰性的方式創(chuàng)建,這就是說創(chuàng)建和設(shè)置DateFormatter對象的代碼將不會立即執(zhí)行藐翎,而是在應(yīng)用程序中第一次使用dateFormatter全局常量時材蹬,才會執(zhí)行這段代碼。

而我們使用dateFormatter的地方吝镣,就是在format(date)方法中堤器。

我們來創(chuàng)建format(date)方法,注意末贾,它應(yīng)該在class的內(nèi)部闸溃,不要寫到外面去了:

func format(date: Date) -> String {
        return dateFormatter.string(from:date)
    }

是不是看上去很簡單?它僅僅是向DateFormatter請求結(jié)果拱撵,并且把結(jié)果放到一個字符串里辉川。

練習(xí):你怎么確認(rèn)date formatter確實(shí)就是只被創(chuàng)建了一次呢?

答案:添加一個print()方法裕膀,就在閉包中的return formatter這一行前面。這個打印內(nèi)容在調(diào)試區(qū)域中勇哗,應(yīng)該只出現(xiàn)一次昼扛。

運(yùn)行app。在模擬器的調(diào)試菜單中選擇Apple Location欲诺。等到地址信息可見的時候抄谐,點(diǎn)擊Tag Location按鈕。

你會看到坐標(biāo)扰法,地址和日期標(biāo)簽都會顯示出相應(yīng)的值了:

等等蛹含,Address標(biāo)簽好像不太對勁...

我們之前將這個標(biāo)簽設(shè)置為多行顯示的模式了,記得嗎塞颁,但是table view對此還一無所知浦箱,所以它就不給你好好顯示。

打開LocationDetailsViewController.swift祠锣,添加下面的方法進(jìn)去酷窥,注意,下面的注釋是必須的伴网,否則不會生效蓬推。

// MARK: - UITableViewDelegate
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.section == 0 && indexPath.row == 0 {
            return 88
        } else if indexPath.section == 2 && indexPath.row == 2 {
            addressLabel.frame.size = CGSize(width: view.bounds.size.width - 115, height: 10000)
            addressLabel.sizeToFit()
            addressLabel.frame.origin.x = view.bounds.size.width - addressLabel.frame.size.width - 15
            return addressLabel.frame.size.height + 20
        } else {
            return 44
        }
    }

當(dāng)table view讀取cell的時候會調(diào)用這個委托方法。你可以利用它來通知table view每個cell的高度是多少澡腾。

通常沸伏,所有的cell高度都是相同的糕珊,如果你需要改變cell的高度的話,你只需要簡單的設(shè)置cell的高度屬性就可以了(通過storyboard中的Row Height屬性或者tableView.rowHeight屬性)毅糟。

對于我們這個tableView红选,它的cell具備三種不同的高度:

1、最上面的Description cell留特。你已經(jīng)在storyboard中設(shè)置了它的高度為88纠脾。

2、Address cell蜕青。這個cell的高度是動態(tài)的苟蹈。它取決于得到的address字符串多大。

3右核、其他cell慧脱。都是標(biāo)準(zhǔn)的44點(diǎn)高度。

tableView(heightForRowAt)方法中的if語句對應(yīng)于上述三種情況贺喝。我們來詳細(xì)看一下Address Label的情況:

//1
addressLabel.frame.size = CGSize(width: view.bounds.size.width - 115, height: 10000)
//2
            addressLabel.sizeToFit()
//3
            addressLabel.frame.origin.x = view.bounds.size.width - addressLabel.frame.size.width - 15
//4
            return addressLabel.frame.size.height + 20

這里用了一點(diǎn)小技巧來調(diào)整UILabel的大小菱鸥,使得其中的文本適合cell 的寬度(使用word-wrapping),然后你使用了新計算出的高度躏鱼,來決定這個cell的高度氮采。

frame屬性的類型是CGRect,用于描述視圖的位置和大小染苛。

CGRect是一個結(jié)構(gòu)(struct)鹊漠,定義了一個矩形。這個矩形的起點(diǎn)坐標(biāo)(X茶行,Y)為CGPoint值躯概,高度和寬度為CGSize值。

所有的UIView對象畔师,以及它們的子類比如UILabel娶靡,都有frame屬性。改變這個屬性看锉,就可以改變它們的大小和位置姿锭。

我們來逐句看下代碼:

1、改變label的寬度為正好比界面的寬度少115點(diǎn)伯铣,這樣在iPhone SE上就正好是200點(diǎn)寬度艾凯。

這條代碼同時使得高為10000。這樣就足夠容納任何長度的字符串了懂傀。

因為你改變了frame屬性趾诗,所以現(xiàn)在UILable中的多行文本會以換行的形式來適應(yīng)label的寬度。因為你已經(jīng)在viewDidLoad()中對標(biāo)簽的文本進(jìn)行了設(shè)置。

2恃泪、使標(biāo)簽適應(yīng)文本的大小郑兴,你必須使label自動適應(yīng)文本的大小,否則每次這個cell都會是10000的高度贝乎。為了達(dá)到這個目的可以使用菜單中的Size to Fit情连,也可以使用方法sizeToFit()。

3览效、調(diào)用sizeToFit()會移除掉label右側(cè)和底部的多余的空間却舀。它同時也可能會改變label的寬度,以便label內(nèi)部的文本盡可能和和label貼近锤灿,所以label的x位置可能會變得不再正確挽拔。

所以我們需要重新擺放它的位置,正好和界面邊緣有15點(diǎn)的空隙但校。我們通過改變frame的origin.x屬性來實(shí)現(xiàn)這個目的螃诅。

4、然后你在label的高度上加上20點(diǎn)的余量(頂部10點(diǎn)和底部10點(diǎn))状囱,就是最后cell的高度了术裸。

??:如果你覺得用這種方式來制定多行文本的大小太可怕了,我完全同意你的意見亭枷,但是重要的是袭艺,這種方法非常有效。
也許你想知道叨粘,能不能用自動布局來解決這個問題猾编,答案是肯定的,你可以使用自動布局來自動計算address cell的高度宣鄙,使用所謂的自定義大小的table view cell來自動計算address cell的高度袍镀。
然而默蚌,對多行文本的label使用自動布局會很麻煩冻晤。我覺得還是手動計算來的簡單些。

運(yùn)行app绸吸,現(xiàn)在地址信息應(yīng)該能夠正常顯示了鼻弧,即使是在iPhone 6或者7上:

Frame and bounds(邊框和范圍)

在上面的代碼中,有這樣一段:

addressLabel.frame.size = CGSize(width: view.bounds.size.width - 115, height: 10000)

你使用了視圖的范圍來計算address標(biāo)簽的邊框锦茁。邊框和范圍的類型都是CGRect攘轩,這種類型描述了一個矩形。那么邊框和范圍的區(qū)別是什么呢码俩?

邊框表述的是一個視圖在它的父視圖中的大小和位置度帮。如果你想把一個150*50的label放到X:100,Y:30的位置,那么它的邊框就是(100笨篷,30瞳秽,150,50)率翅。把一個視圖從一個位置移動到另一個位置练俐,你需要改變它的frame屬性。

范圍是描述視圖內(nèi)部的大小冕臭。在范圍中X和Y始終是(0,0)腺晾,寬度和高度則和邊框一致。對于上面的例子而言辜贵,它的范圍就是(0悯蝉,0,150念颈,50)泉粉。

當(dāng)你用自動布局為一個視圖添加約束的時,這些約束通常是由視圖的邊框計算得出的榴芳,同時嗡靡,如果你一個視圖具有約束,你就不應(yīng)該手動去調(diào)整它的邊框或者范圍窟感,這會把一切都弄糟讨彼。

分類選擇器(The category picker)

當(dāng)用戶點(diǎn)擊Category(分類)cell時,app會展示一個列表顯示分類的名稱:

這是一個新的界面柿祈,所以你需要創(chuàng)建一個新的視圖控制器哈误。這和上個課程中的圖標(biāo)選擇界面很像。所以我下面會講快一些躏嚎。

添加一個新的文件蜜自,命名為CategoryPickerViewController.swift.

刪掉該文件中的原有內(nèi)容,替換為下面的代碼:

import UIKit

class CategoryPickerViewController: UITableViewController {
    var selectedCategoryName = ""
    
    let categories = [
        "No Category",
        "Apple Store",
        "Bar",
        "Bookstore",
        "Club",
        "Grocery Store",
        "Historic Buliding",
        "House",
        "Icecream Vendor",
        "Landmark",
        "Park"]
    
    var selectedIndexPath = IndexPath()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        for i in 0 ..< categories.count {
            if categories[i] == selectedCategoryName {
                selectedIndexPath = IndexPath(row: i,section: 0)
                break
            }
        }
    }
    
    //MARK: - UITableViewDataSource
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return categories.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let categoryName = categories[indexPath.row]
        cell.textLabel!.text = categoryName
        if categoryName == selectedCategoryName {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none
        }
        return cell
    }
    
    //MARK - UITableViewDelegate
    
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.row != selectedIndexPath.row {
            if let newCell = tableView.cellForRow(at: indexPath) {
                newCell.accessoryType = .checkmark
            }
            if let oldCell = tableView.cellForRow(at: selectedIndexPath) {
                oldCell.accessoryType = .none
            }
            selectedIndexPath = indexPath
        }
    }
}

這里沒有新的東西卢佣。你創(chuàng)建了一個table view controller重荠,用來展示分類的名稱。它有table view數(shù)據(jù)源以及委托方法虚茶。數(shù)據(jù)源從categories數(shù)組中讀取數(shù)據(jù)戈鲁。

唯一值得注意的事情是實(shí)例變量selectedIndexPath。當(dāng)這個界面打開時嘹叫,它會在目前被選擇的分類的旁邊顯示一個對勾符號婆殿。具體在哪一條上顯示,取決于轉(zhuǎn)場時selectCategoryName屬性罩扇。

當(dāng)用戶點(diǎn)擊某一行婆芦,你需要把對勾符號從之前的行上移除,并且在新選定的這一行上顯示。

為了直線這個目的消约,你需要知道目前被選定的是哪一行癌压。你不能用selectCategoryName來判斷,因為它是一個字符串荆陆,不是一個行號滩届。因此,你首先要找到當(dāng)前被選定的這一行的行號或者indexPath被啼。

你可以在viewDidLoad()中做這件事帜消。你歷遍categories數(shù)組并且用selectCategoryName和數(shù)組中每一個對象做比較。如果比對成功浓体,你就創(chuàng)建一個indexPath對象泡挺,并且存儲到selectedIndexPath變量中,然后中斷循環(huán)命浴。

現(xiàn)在你知道了行號娄猫,就可以在另一行被點(diǎn)擊時,移除當(dāng)前行的對勾符號了生闲,我們是在tableView(didSelectRowAt)中實(shí)現(xiàn)了這個目的媳溺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碍讯,隨后出現(xiàn)的幾起案子悬蔽,更是在濱河造成了極大的恐慌,老刑警劉巖捉兴,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝎困,死亡現(xiàn)場離奇詭異,居然都是意外死亡倍啥,警方通過查閱死者的電腦和手機(jī)禾乘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虽缕,“玉大人始藕,你說我怎么就攤上這事”顺瑁” “怎么了鳄虱?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵弟塞,是天一觀的道長凭峡。 經(jīng)常有香客問我,道長决记,這世上最難降的妖魔是什么摧冀? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上索昂,老公的妹妹穿的比我還像新娘建车。我一直安慰自己,他們只是感情好椒惨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布缤至。 她就那樣靜靜地躺著,像睡著了一般康谆。 火紅的嫁衣襯著肌膚如雪领斥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天沃暗,我揣著相機(jī)與錄音月洛,去河邊找鬼。 笑死孽锥,一個胖子當(dāng)著我的面吹牛嚼黔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惜辑,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唬涧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盛撑?” 一聲冷哼從身側(cè)響起爵卒,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撵彻,沒想到半個月后钓株,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陌僵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年轴合,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碗短。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡受葛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出偎谁,到底是詐尸還是另有隱情总滩,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布巡雨,位于F島的核電站闰渔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铐望。R本人自食惡果不足惜冈涧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一茂附、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧督弓,春花似錦营曼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狂塘,卻和暖如春蒜危,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睹耐。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工辐赞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硝训。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓响委,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窖梁。 傳聞我的和親對象是個殘疾皇子赘风,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容