上節(jié)課我們已經(jīng)解鎖了新技能舔痕,選擇圖標湃交。
但是我們?nèi)匀挥行┘毠?jié)有待改進扫倡,目前,在done方法中竟纳,你做了這件事:
let checklist = Checklist(name: textField.text!)
checklist.iconName = iconName
設(shè)置圖標的名稱可以認為是初始化Checklist的一部分撵溃,所以我們寫成下面這樣會更好一些:
let checklist = Checklist(name: textField.text!, iconName: iconName)
打開ListDetailViewController.swift,done方法中把新的這一行代碼替換進去锥累。
僅僅是這樣缘挑,app肯定不會工作,你還需要給Checklist.swift新增一個具有兩個參數(shù)name和iconName的新的init方法
打開Checklist.swift桶略,添加新的init方法:
init(name: String,iconName: String) {
self.name = name
self.iconName = iconName
super.init()
}
注意语淘,是加一個新的诲宇,不要在老的上面改。Checklist現(xiàn)在有三個init方法了:
init(name):僅需要名稱時惶翻,使用這個方法姑蓝。
init(name, iconName):同時需要名稱及圖標名稱時,用這個方法吕粗。
init?(coder):從plist文件中讀取對象時纺荧,用這個方法。
init(name)和init(name, iconName)幾乎是一樣的颅筋。除了參數(shù)有所不同宙暇。
所以我們可以改進一下這個地方,使用init(name)調(diào)用一個將圖標名稱默認為“No Icon”的init(name, iconName)方法议泵。
將init(name)替換為下面的代碼:
convenience init(name: String) {
self.init(name: name, iconName: "No Icon")
}
這里用self.init(name, iconName)代替了super.init()占贫。
因為它將一部分工作移交給了另一個init方法,所以此時init(name)方法被稱為便利初始化先口。
它和init(name, iconName)的功能一模一樣型奥,只是節(jié)省了你需要在多處指定使用“No Icon”的工作。
init(name, iconName)成為了Checklist的指定初始化池充。它是創(chuàng)建一個新的Checklist對象的首要方法桩引,而init(name)僅用于給比較懶的人,比如你和我收夸。
運行app坑匠,確認一下一切工作正常。
練習:給ChecklistItem一個init(text)方法卧惜,或者一個init(text,checked)方法厘灼。
給app整個容
你會使用一些簡單的辦法來提高顏值,比如化個妝什么的咽瓷,而不是手術(shù)級別的设凹。導航控制器和table view默認的樣子已經(jīng)比較好看了,雖然色調(diào)有點單一茅姜。本節(jié)課你將了解如何自定義這些UI元素的外觀闪朱。
雖然現(xiàn)在app外觀有點單調(diào),但是你可以使用一些比較簡單的辦法讓它立馬個性起來钻洒,我們說的就是tint color奋姿。
tint color是UIKit用來表示某種東西可以交互的一個顏色系統(tǒng),比如按鈕上的文字是淺藍色的素标,用戶無論用什么app称诗,看到這種淺藍色,基本就會明白头遭,這個按鈕可以點擊寓免。
改變tint color是非常簡單的癣诱。
打開故事模版,找到文件指示器(File inspector)袜香,就是第一個子頁撕予。
點擊Global Tint就可以打開顏色選擇器了,我們將顏色設(shè)置為Red:4困鸥,Green:169嗅蔬,Blue:235,這樣就得到了比較亮一些的藍色疾就。
小貼士:如果顏色選擇器中僅顯示黑白灰三種顏色澜术,那么你就點擊一個名字叫做Gray Scale Slider的下拉框,在下拉框中選擇為RGB Slider猬腰。
如果對勾符號不用黑色鸟废,而改用tint color那就更加完美了。
為了實現(xiàn)這個目的姑荷,在ChecklistViewController.swift中的configureCheckmark(for:with)方法中盒延,添加一行代碼:
label.textColor = view.tintColor
運行app,是不是看起來感覺不一樣了鼠冕?如果感覺一樣添寺,請把這節(jié)課當作皇帝的新裝就好。
任何一個完整的app懈费,都有會有自己的圖標计露。在本節(jié)課附加的資源中的Icon文件夾中你可以找到各種尺寸的圖標(附件不提供下載,請大家支持正版憎乙,或者自己去網(wǎng)上找些圖標素材練習)票罐,注意一下,圖標的顏色和我們剛才選擇的tint color是一致的泞边。
把這些圖標添加到asset catalog(Assets.xcassets)该押。回憶一下阵谚,我們僅僅是把這些圖標拖入AppIcon中相應(yīng)的位置就好了蚕礼。
app,還有一個加載用的圖片和文件梢什。在app還在讀取時闻牡,顯示一個靜態(tài)圖片可以造成app啟動很快的幻覺,這些都是騙人的把戲绳矩。(其實這里就是app打廣告的好地方,但是本書的作者字里行間都透露著對廣告的鄙視玖翅,所以翼馆。割以。。沒有然后)
Xcode的模版中包含一個叫做LaunchScreen.storyboard的文件应媚,在運行的時候會先加載它严沥。你可以把它做成和app差不多的樣子,但是我們還有一個更簡單的方法中姜。
打開工程設(shè)置界面消玄,在General子頁中,向下滾動丢胚,找到一個叫做App Icons and Launch Images的分節(jié)翩瓜。
在Launch Screen File下拉框中,選擇Main.storyboard携龟。
這樣app就會使用故事模版中的設(shè)計作為啟動時加載的文件兔跌。
啟動時,app會找到初始界面并且將它轉(zhuǎn)換為一個靜態(tài)圖片峡蟋。對我們的app而言坟桅,就是All Lists View Controller。
從工程導航欄中刪除LaunchScreen.storyboard蕊蝗。
然后選擇菜單Product->Clean仅乓。或者在模擬器中刪除掉app蓬戚,再重新運行一次夸楣,這樣就不會有任何殘留下的緩存了。(刪除的方法和真實手機一樣碌更,用鼠標一直按住app的圖標裕偿,然后app圖標會開始晃動)
然后運行app,你就可以看到app一啟動痛单,就顯示出了主界面嘿棘,好像app立即啟動了一樣。
使用合適的啟動界面旭绒,可以使app顯得更加專業(yè)鸟妙。
對于許多app而言,你都可以無腦的使用main storyboard來當啟動界面挥吵,此外重父,你要需要使啟動界面適合所有的設(shè)備,比如6s忽匈,7房午,plus等。
支持所有設(shè)備類型
我們的app應(yīng)該在現(xiàn)有的所有iPhone型號上運行正常丹允,從屏幕最小的iPhone SE到最大的iPhone 7 plus郭厌。table view controller在這方面非常靈活袋倔,它可以自動識別設(shè)備類型并且轉(zhuǎn)換尺寸,無論是變大還是變小都靈活自如折柠。你可以自己在各種類型的模擬器上試試宾娜。
那么我們面臨的問題是什么呢?這里還是有很多東西需要微調(diào)的扇售。
目前為止前塔,我給你看的截圖都是基于iPhone SE的,并且我在自己的界面建造器中也是使用的iPhone SE尺寸進行設(shè)計承冰。但是我們在大屏幕設(shè)備上運行的話會發(fā)生什么事呢华弓?比如我們用iPhone 7 plus模擬器運行一下試試:
圖標不再完美的對齊cell的右側(cè)邊緣了巷懈。再試試輸入點文本上去:你會發(fā)現(xiàn)文本被截短了该抒,因為text field變小了。為什么會發(fā)生這些情況呢顶燕?
當你在界面建造器中為你的app設(shè)計用戶界面的時候凑保,它并不會自動匹配所有的設(shè)備類型,只是匹配你正在使用的模擬器型號涌攻。你需要幫助界面建造器欧引,告訴它面對不同尺寸的屏幕時應(yīng)該如何調(diào)整UI的大小及位置。這就我要介紹給你們的自動布局(Auto Layout)恳谎。
你想要的是芝此,圖像永遠貼在右側(cè)邊緣,總是和詳細信息按鈕保持固定的距離因痛。當屏幕大小發(fā)生變化時婚苹,圖像應(yīng)該自動的調(diào)整自己的位置。
解決辦法就是給image view添加自動布局約束鸵膏,告訴app屏幕邊緣和image view的關(guān)系膊升。
選定Icon Image View。打開畫布底部的Pin Menu谭企,然后按照一下步驟操作:
1廓译、取消選擇Constrain to margins。
2债查、激活頂部和右側(cè)的連線(菜單上方有十字形的四條紅色虛線非区,激活后會變成實線)
3、勾選Width和Height復(fù)選框
4盹廷、For update Frames下拉框選擇為Items of New Constraints征绸。
5、點擊Add 4 Constraints。
(有可能你會看不到For update Frames下拉框歹垫,這樣的話就忽略第四條)
現(xiàn)在image view看起來應(yīng)該是這個樣子:
確認一下剥汤,代表約束的線條是藍色實心線條雄妥。如果它們是橙色或者紅色霸奕,那么你肯定是漏掉了上面的某個步驟涩堤。(重新添加一次約束,或者使用菜單Editor → Resolve Auto Layout Issues → Update Frames暮芭,看看能不能自動更新過來)
最重要的約束是右側(cè)那個,這一條告訴UIKit欲低,image view的右手邊總是以固定距離貼著table view cell的右側(cè)邊緣辕宏。
換而言之,無論當前界面是寬還是窄砾莱,image view總是和詳細信息按鈕的相對位置不變瑞筐。
剩下的三條約束,頂部腊瑟、寬和高也都是必要的聚假,因為所有的視圖都必須有足夠的約束指明它們的位置和大小。
如果你不指明約束闰非,那么界面建造器會按照默認約束處理膘格。但是哪怕你只是添加了一條約束,那么默認約束就失效了财松,你必須把所有的約束都補全瘪贱。
確認約束是否生效,并不是非得在模擬器中運行app辆毡,那樣太消耗時間菜秦,你可以使用View as功能,這個功能在畫布的底部舶掖,我們上一個課程中也使用過它球昨,它可以在界面建造器的內(nèi)部切換iPhone的類型。如果你的約束添加的沒有問題访锻,那么任何尺寸的iPhone上褪尝,它的位置都應(yīng)該是固定的。
接下來期犬,我們來讓text field在比較寬的屏幕中河哑,自動延長。
選定Text Field龟虎,打開Pin menu激活四條紅線:
這個操作會把text field固定到table view cell的四個邊上璃谨。(紅線上下左右4個框里的數(shù)字是幾都沒關(guān)系,這些數(shù)字代表text field和cell四個邊的間距,重要的是4條紅線都需要被激活)
對Add/Edit Item界面的text field也做同樣的操作佳吞。
現(xiàn)在你輸入多長的文本都沒關(guān)系了拱雏,文字會自動向左滾動:
讓我們來輸入一條非常長的文本,這個文本傳遞到其他table view時會發(fā)生什么呢底扳?
對All Lists界面完全沒問題:
使用“Subtitle”cell風格的table view,會自動隨著屏幕調(diào)整寬度衷模。當文本過長時鹊汛,它會自動縮短它們。
但是對于to-do items(待辦事項界面)阱冶,看起來就不是那么漂亮了刁憋。在這個界面了,文本被過早的截短了木蹬。
因為這是一個自定義的cell設(shè)計至耻,你要添加一些約束來避免這種事情發(fā)生。
打開故事模版镊叁,找到Checklist界面并且選定cell內(nèi)的label尘颓。
首先使用Xcode菜單 Editor → Size to Fit Content,給label一個可以自由伸縮的尺寸意系。這樣做后也許會把label變得非常小泥耀,但是沒有關(guān)系。如果不這樣做的話蛔添,下面的步驟就會無法進行痰催。(即使這一操作移動了label也沒關(guān)系)
你想要把label固定在視圖的右側(cè)邊緣,緊挨著詳細信息按鈕迎瞧。我們來添加這個約束夸溶。
打開Pin菜單,取消選擇Constrain to margins凶硅。
激活右側(cè)的紅線缝裁,并且將其中的值修改為0,這樣它就緊貼著詳細信息按鈕了足绅。
將Update Frames設(shè)置為Items of new Constraints(如果看不到這個選項就忽略它)捷绑。點擊Add 1 Constraint,結(jié)束氢妈。
好像有啥東西看起來有點不對粹污。
記住,你總是要添加足夠的約束指定一個視圖的尺寸和位置首量。這里你僅僅添加了關(guān)于右側(cè)邊緣的約束壮吩,這是不夠的进苍。
不要慌!當你添加約束時鸭叙,漏點東西是很正常的觉啊。解決的辦法非常簡單,就是把漏掉的東西添加上就好了沈贝。
還是選中l(wèi)abel杠人,打開Align menu,就是Pin菜單左邊的那個宋下。選中Vertically in Container搜吧。設(shè)置Update Frames為Items of New Constraints(看不到這個就忽略掉)
現(xiàn)在所有代表約束的線都應(yīng)該是藍色了。label在X軸和Y軸上都有了有效的位置杨凑。
??:即使你沒有對label的尺寸指定任何的約束,約束也完美的生效了摆昧,這是為什么呢撩满?
沒有指定尺寸的話,label會根據(jù)它的內(nèi)容绅你,文本和字體來計算它自己應(yīng)該有多大伺帘。這叫做內(nèi)容自適應(yīng)尺寸(應(yīng)該是這么個名詞吧!)
使用內(nèi)容自適應(yīng)調(diào)整的UI組件忌锯,比如label伪嫁,不需要添加寬和高的相關(guān)約束,但是自適應(yīng)調(diào)整生效的前提是之前你必須使用了菜單中的Size to Fit Content選項偶垮。
不幸的是张咳,label現(xiàn)在雖然右對齊了,但是這并不是你想要的似舵,它的左邊必須緊挨著cell的左邊脚猾。
最簡單的辦法就是給左邊同樣添加一條約束,使label緊挨著左側(cè)邊緣砚哗。
但是你不能使用Pin菜單來做這個事龙助,因為這樣會使label和對勾符號連接起來,對勾符號的尺寸依賴于它是否被觸發(fā)顯示在屏幕上蛛芥。你需要使用新的技術(shù)來完成這件事提鸟。
再次選中l(wèi)abel,按住ctrl拖拽label到cell的內(nèi)部任意一個位置上仅淑。放開鼠標称勋,會彈出一個菜單。菜單上的選項依賴于你拖拽的方向漓糙,所以你看到的菜單也許和截圖中的有所不同铣缠。
在彈出菜單上選擇Leading Space to Container Margin,就可以完成這個約束的添加了。
這樣就新增了一條長長的藍色約束線蝗蛙,但是標簽的位置看起來還是有點問題蝇庭。
選擇這條藍色的線,打開尺寸檢查器捡硅,將Constant設(shè)置為30哮内。
現(xiàn)在看起來好多了:
現(xiàn)在label的兩側(cè)邊緣都固定好了,所以它會自動伸縮壮韭,保持和table view cell一致北发。
運行app,現(xiàn)在文本不會過早的被截短了喷屋。
特色功能:本地通知
我希望你還可以跟上我的思路琳拨。我們詳細的討論了關(guān)于視圖控制器(view controller)、導航控制器(navigation controller)屯曹、故事模版(storyboard)狱庇、轉(zhuǎn)場(segues)、表視圖(table view)恶耽、以及數(shù)據(jù)模型(data model)的相關(guān)內(nèi)容密任。
如果你想要成為iOS app開發(fā)的大師的話,你必須精通這些課題偷俭,因為每一個app都用到了它們中間的一個或者幾個浪讳。
在本節(jié)課,你要了解一個擴展的課題:local notifications(本地通知)涌萤,使用iOS 10中的User Notifications框架淹遵。
本地通知允許app在app沒有運行的時候,按照事先的安排形葬,對用戶進行消息通知合呐。
你要新增一個“due date(處理時間)”到ChecklistItem對象中,然后使用本地通知來告訴用戶某條待辦事項的戒指時間笙以。
如果你對這個課題感興趣的話淌实,那么,太好了猖腕。拆祈。。
這節(jié)課的內(nèi)容是這個樣子的:
1倘感、嘗試使用本地通知放坏,觀察它是如何工作的。
2老玛、允許用戶為待辦事項選擇一個處理時間淤年。
3钧敞、創(chuàng)建一個時間選擇器。
4麸粮、對待辦事項使用本地通知溉苛,并且當用戶修改處理時間時,更新本地通知的時間弄诲。
在你了解如何把這些和app融為一體之前愚战,我們先來安排本地通知計劃,看看會發(fā)生什么齐遵。
順便說一下寂玲,本地通知和推送消息是不一樣的(推送消息,也叫遠程通知)梗摇。推送消息允許你的app接收外部事件觸發(fā)的通知拓哟,比如你最喜歡的球隊奪冠了。
本地通知更像是一個鬧鐘:你設(shè)定一個時間伶授,然后它就會發(fā)出蜂鳴聲彰檬。
app僅僅在用戶允許的情況下,可以進行本地通知谎砾,如果用戶不允許,那么通知永遠不會出現(xiàn)捧颅。你需要詢問用戶是否同意消息通知景图,我們就從這里開始入手。
打開AppDelegate.swift碉哑,導入一個新的框架:
import UserNotifications
這樣就告訴了Xcode挚币,我們將要使用User Notifications框架。
在application(didFinishLaunchingWithOptions)方法中添加以下代碼扣典,就添加在return true語句前面:
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert,.sound], completionHandler: {
granted,error in
if granted {
print("We have permission")
} else {
print("Permission deied")
}
})
回憶一下妆毕,application(didFinishLaunchingWithOptions)是在app啟動時被調(diào)用。它是app的準入點贮尖。是你在app啟動后要做某些操作時的最佳代碼位置笛粘。
因為你僅僅是使用一下本地通知,所以在這里用來請求用戶許可是最合適的湿硝。
你告訴了iOS這個app想要發(fā)送類型為“alert”的通知薪前,并且附帶一個聲音效果。稍候关斜,你會將這段代碼移入更加恰當?shù)牡胤健?/p>
用點號開頭的東西
在我們的app中示括,見過了類似于.none, .checkmark, .subtitle以及現(xiàn)在的.alert和.sound。它們是枚舉符號痢畜。
一個枚舉enumeration垛膝,或者簡寫為enum鳍侣,是一種數(shù)據(jù)類型,它由一系列符號和它們對應(yīng)的值的列表組成吼拥。
例如UNAuthorizationOptions枚舉包含以下符號:
.badge
.sound
.alert
.carPlay
你可以把它們整合到一個數(shù)組中倚聚,來定義app發(fā)送給用戶的消息種類。這里你使用語句[.alert,.sound]組合了alert和sound扔罪。
在那里使用了枚舉是非常容易被識別的秉沼,因為它們名稱前面都有一個點。這是一種速寫的方法矿酵,它的完整寫法其實是:
UNAuthorizationOptions.alert,UNAuthorizationOptions.sound
幸運的是Swift是非常聰明的唬复,它可以識別出.alert和.sound是來自UNAuthorizationOptions,這為你省了不少事全肮。
運行app敞咧,這時你應(yīng)該會得到一個許可請求的彈窗。
點擊Allow辜腺,那么下次app就不會再進行詢問了休建。iOS會記住第一次的結(jié)果。
如果你不小心點擊了Don't评疗,也沒關(guān)系测砂,你可以重置模擬器恢復(fù)這個彈窗,或者在app的setting中進行設(shè)置百匆,就和其他app一樣砌些。
中斷app運行,在didFinishLaunchingWithOptions方法中添加以下代碼:
let content = UNMutableNotificationContent()
content.title = "Hello"
content.body = "I am a local notifcation"
content.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
let request = UNNotificationRequest(identifier: "MyNotification", content: content, trigger: trigger)
center.add(request)
這樣就創(chuàng)建了一個新的本地通知加匈。因為你寫了timeInterval: 10存璃,所以這個通知會在app運行10秒后被觸發(fā)。
UNMutableNotificationContent描述了本地通知的內(nèi)容雕拼。就是你在alert消息中設(shè)置的兩個文本信息纵东,以及聲音。
最后啥寇,你將通知添加到UNUserNotificationCenter偎球。這個對象是用于跟蹤所有本地通知并且在時間到了的時候觸發(fā)它們。
運行app辑甜,在app啟動后甜橱,按下Home間,回到手機的主界面(使用模擬器菜單Hardware->Home)
等待10秒栈戳,這也許會是你人生中比較漫長的10秒之一岂傲,然后你會看到一條彈出消息,當然還伴隨著一個提示音子檀。
這就是本地通知镊掖,酷嗎乃戈?