iOS Apprentice中文版-從0開始學iOS開發-第二十六課

上節課我們已經解鎖了新技能,選擇圖標。

但是我們仍然有些細節有待改進,目前,在done方法中,你做了這件事:

  let checklist = Checklist(name: textField.text!)
 checklist.iconName = iconName

設置圖標的名稱可以認為是初始化Checklist的一部分,所以我們寫成下面這樣會更好一些:

let checklist = Checklist(name: textField.text!, iconName: iconName)

打開ListDetailViewController.swift,done方法中把新的這一行代碼替換進去。

僅僅是這樣,app肯定不會工作,你還需要給Checklist.swift新增一個具有兩個參數name和iconName的新的init方法

打開Checklist.swift,添加新的init方法:

init(name: String,iconName: String) {  
        self.name = name
        self.iconName = iconName  
        super.init()
    }

注意,是加一個新的,不要在老的上面改。Checklist現在有三個init方法了:

init(name):僅需要名稱時,使用這個方法。

init(name, iconName):同時需要名稱及圖標名稱時,用這個方法。

init?(coder):從plist文件中讀取對象時,用這個方法。

init(name)和init(name, iconName)幾乎是一樣的。除了參數有所不同。

所以我們可以改進一下這個地方,使用init(name)調用一個將圖標名稱默認為“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)的功能一模一樣,只是節省了你需要在多處指定使用“No Icon”的工作。

init(name, iconName)成為了Checklist的指定初始化。它是創建一個新的Checklist對象的首要方法,而init(name)僅用于給比較懶的人,比如你和我。

運行app,確認一下一切工作正常。

練習:給ChecklistItem一個init(text)方法,或者一個init(text,checked)方法。

給app整個容

你會使用一些簡單的辦法來提高顏值,比如化個妝什么的,而不是手術級別的。導航控制器和table view默認的樣子已經比較好看了,雖然色調有點單一。本節課你將了解如何自定義這些UI元素的外觀。

雖然現在app外觀有點單調,但是你可以使用一些比較簡單的辦法讓它立馬個性起來,我們說的就是tint color。

tint color是UIKit用來表示某種東西可以交互的一個顏色系統,比如按鈕上的文字是淺藍色的,用戶無論用什么app,看到這種淺藍色,基本就會明白,這個按鈕可以點擊。

所有的按鈕都是一個顏色

改變tint color是非常簡單的。

打開故事模版,找到文件指示器(File inspector),就是第一個子頁。

點擊Global Tint就可以打開顏色選擇器了,我們將顏色設置為Red:4,Green:169,Blue:235,這樣就得到了比較亮一些的藍色。

改變故事模版的Global Tint Color

小貼士:如果顏色選擇器中僅顯示黑白灰三種顏色,那么你就點擊一個名字叫做Gray Scale Slider的下拉框,在下拉框中選擇為RGB Slider。

如果對勾符號不用黑色,而改用tint color那就更加完美了。

為了實現這個目的,在ChecklistViewController.swift中的configureCheckmark(for:with)方法中,添加一行代碼:

label.textColor = view.tintColor

運行app,是不是看起來感覺不一樣了?如果感覺一樣,請把這節課當作皇帝的新裝就好。

顏色變的活波了一些

任何一個完整的app,都有會有自己的圖標。在本節課附加的資源中的Icon文件夾中你可以找到各種尺寸的圖標(附件不提供下載,請大家支持正版,或者自己去網上找些圖標素材練習),注意一下,圖標的顏色和我們剛才選擇的tint color是一致的。

把這些圖標添加到asset catalog(Assets.xcassets)。回憶一下,我們僅僅是把這些圖標拖入AppIcon中相應的位置就好了。

app,還有一個加載用的圖片和文件。在app還在讀取時,顯示一個靜態圖片可以造成app啟動很快的幻覺,這些都是騙人的把戲。(其實這里就是app打廣告的好地方,但是本書的作者字里行間都透露著對廣告的鄙視,所以。。。沒有然后)

Xcode的模版中包含一個叫做LaunchScreen.storyboard的文件,在運行的時候會先加載它。你可以把它做成和app差不多的樣子,但是我們還有一個更簡單的方法。

打開工程設置界面,在General子頁中,向下滾動,找到一個叫做App Icons and Launch Images的分節。

在Launch Screen File下拉框中,選擇Main.storyboard。

這樣app就會使用故事模版中的設計作為啟動時加載的文件。

啟動時,app會找到初始界面并且將它轉換為一個靜態圖片。對我們的app而言,就是All Lists View Controller。

從工程導航欄中刪除LaunchScreen.storyboard。

然后選擇菜單Product->Clean。或者在模擬器中刪除掉app,再重新運行一次,這樣就不會有任何殘留下的緩存了。(刪除的方法和真實手機一樣,用鼠標一直按住app的圖標,然后app圖標會開始晃動)

然后運行app,你就可以看到app一啟動,就顯示出了主界面,好像app立即啟動了一樣。

使用合適的啟動界面,可以使app顯得更加專業。

對于許多app而言,你都可以無腦的使用main storyboard來當啟動界面,此外,你要需要使啟動界面適合所有的設備,比如6s,7,plus等。

支持所有設備類型

我們的app應該在現有的所有iPhone型號上運行正常,從屏幕最小的iPhone SE到最大的iPhone 7 plus。table view controller在這方面非常靈活,它可以自動識別設備類型并且轉換尺寸,無論是變大還是變小都靈活自如。你可以自己在各種類型的模擬器上試試。

那么我們面臨的問題是什么呢?這里還是有很多東西需要微調的。

目前為止,我給你看的截圖都是基于iPhone SE的,并且我在自己的界面建造器中也是使用的iPhone SE尺寸進行設計。但是我們在大屏幕設備上運行的話會發生什么事呢?比如我們用iPhone 7 plus模擬器運行一下試試:

table view倒是沒事,但是圖標錯位了

圖標不再完美的對齊cell的右側邊緣了。再試試輸入點文本上去:你會發現文本被截短了,因為text field變小了。為什么會發生這些情況呢?

當你在界面建造器中為你的app設計用戶界面的時候,它并不會自動匹配所有的設備類型,只是匹配你正在使用的模擬器型號。你需要幫助界面建造器,告訴它面對不同尺寸的屏幕時應該如何調整UI的大小及位置。這就我要介紹給你們的自動布局(Auto Layout)。

你想要的是,圖像永遠貼在右側邊緣,總是和詳細信息按鈕保持固定的距離。當屏幕大小發生變化時,圖像應該自動的調整自己的位置。

解決辦法就是給image view添加自動布局約束,告訴app屏幕邊緣和image view的關系。

選定Icon Image View。打開畫布底部的Pin Menu,然后按照一下步驟操作:

1、取消選擇Constrain to margins。

2、激活頂部和右側的連線(菜單上方有十字形的四條紅色虛線,激活后會變成實線)

3、勾選Width和Height復選框

4、For update Frames下拉框選擇為Items of New Constraints。

5、點擊Add 4 Constraints。

給image view添加自動布局

(有可能你會看不到For update Frames下拉框,這樣的話就忽略第四條)

現在image view看起來應該是這個樣子:

確認一下,代表約束的線條是藍色實心線條。如果它們是橙色或者紅色,那么你肯定是漏掉了上面的某個步驟。(重新添加一次約束,或者使用菜單Editor → Resolve Auto Layout Issues → Update Frames,看看能不能自動更新過來)

最重要的約束是右側那個,這一條告訴UIKit,image view的右手邊總是以固定距離貼著table view cell的右側邊緣。

換而言之,無論當前界面是寬還是窄,image view總是和詳細信息按鈕的相對位置不變。

剩下的三條約束,頂部、寬和高也都是必要的,因為所有的視圖都必須有足夠的約束指明它們的位置和大小。

如果你不指明約束,那么界面建造器會按照默認約束處理。但是哪怕你只是添加了一條約束,那么默認約束就失效了,你必須把所有的約束都補全。

確認約束是否生效,并不是非得在模擬器中運行app,那樣太消耗時間,你可以使用View as功能,這個功能在畫布的底部,我們上一個課程中也使用過它,它可以在界面建造器的內部切換iPhone的類型。如果你的約束添加的沒有問題,那么任何尺寸的iPhone上,它的位置都應該是固定的。

接下來,我們來讓text field在比較寬的屏幕中,自動延長。

選定Text Field,打開Pin menu激活四條紅線:

上下左右全部激活

這個操作會把text field固定到table view cell的四個邊上。(紅線上下左右4個框里的數字是幾都沒關系,這些數字代表text field和cell四個邊的間距,重要的是4條紅線都需要被激活)

對Add/Edit Item界面的text field也做同樣的操作。

現在你輸入多長的文本都沒關系了,文字會自動向左滾動:

輸入多長都可以

讓我們來輸入一條非常長的文本,這個文本傳遞到其他table view時會發生什么呢?

對All Lists界面完全沒問題:

內建的cell風格,自動調整了大小

使用“Subtitle”cell風格的table view,會自動隨著屏幕調整寬度。當文本過長時,它會自動縮短它們。

但是對于to-do items(待辦事項界面),看起來就不是那么漂亮了。在這個界面了,文本被過早的截短了。

文本被過早的截短了

因為這是一個自定義的cell設計,你要添加一些約束來避免這種事情發生。

打開故事模版,找到Checklist界面并且選定cell內的label。

首先使用Xcode菜單 Editor → Size to Fit Content,給label一個可以自由伸縮的尺寸。這樣做后也許會把label變得非常小,但是沒有關系。如果不這樣做的話,下面的步驟就會無法進行。(即使這一操作移動了label也沒關系)

你想要把label固定在視圖的右側邊緣,緊挨著詳細信息按鈕。我們來添加這個約束。

打開Pin菜單,取消選擇Constrain to margins。

激活右側的紅線,并且將其中的值修改為0,這樣它就緊貼著詳細信息按鈕了。

將Update Frames設置為Items of new Constraints(如果看不到這個選項就忽略它)。點擊Add 1 Constraint,結束。

將label固定在右側邊緣

好像有啥東西看起來有點不對。

標簽沒有足夠的約束

記住,你總是要添加足夠的約束指定一個視圖的尺寸和位置。這里你僅僅添加了關于右側邊緣的約束,這是不夠的。

不要慌!當你添加約束時,漏點東西是很正常的。解決的辦法非常簡單,就是把漏掉的東西添加上就好了。

還是選中label,打開Align menu,就是Pin菜單左邊的那個。選中Vertically in Container。設置Update Frames為Items of New Constraints(看不到這個就忽略掉)

使label垂直居中

現在所有代表約束的線都應該是藍色了。label在X軸和Y軸上都有了有效的位置。

??:即使你沒有對label的尺寸指定任何的約束,約束也完美的生效了,這是為什么呢?
沒有指定尺寸的話,label會根據它的內容,文本和字體來計算它自己應該有多大。這叫做內容自適應尺寸(應該是這么個名詞吧!)
使用內容自適應調整的UI組件,比如label,不需要添加寬和高的相關約束,但是自適應調整生效的前提是之前你必須使用了菜單中的Size to Fit Content選項。

不幸的是,label現在雖然右對齊了,但是這并不是你想要的,它的左邊必須緊挨著cell的左邊。

最簡單的辦法就是給左邊同樣添加一條約束,使label緊挨著左側邊緣。

但是你不能使用Pin菜單來做這個事,因為這樣會使label和對勾符號連接起來,對勾符號的尺寸依賴于它是否被觸發顯示在屏幕上。你需要使用新的技術來完成這件事。

再次選中label,按住ctrl拖拽label到cell的內部任意一個位置上。放開鼠標,會彈出一個菜單。菜單上的選項依賴于你拖拽的方向,所以你看到的菜單也許和截圖中的有所不同。

在彈出菜單上選擇Leading Space to Container Margin,就可以完成這個約束的添加了。

像這樣

這樣就新增了一條長長的藍色約束線,但是標簽的位置看起來還是有點問題。

選擇這條藍色的線,打開尺寸檢查器,將Constant設置為30。

現在看起來好多了:

現在label的兩側邊緣都固定好了,所以它會自動伸縮,保持和table view cell一致。

運行app,現在文本不會過早的被截短了。

特色功能:本地通知

我希望你還可以跟上我的思路。我們詳細的討論了關于視圖控制器(view controller)、導航控制器(navigation controller)、故事模版(storyboard)、轉場(segues)、表視圖(table view)、以及數據模型(data model)的相關內容。

如果你想要成為iOS app開發的大師的話,你必須精通這些課題,因為每一個app都用到了它們中間的一個或者幾個。

在本節課,你要了解一個擴展的課題:local notifications(本地通知),使用iOS 10中的User Notifications框架。

本地通知允許app在app沒有運行的時候,按照事先的安排,對用戶進行消息通知。

你要新增一個“due date(處理時間)”到ChecklistItem對象中,然后使用本地通知來告訴用戶某條待辦事項的戒指時間。

如果你對這個課題感興趣的話,那么,太好了。。。

這節課的內容是這個樣子的:

1、嘗試使用本地通知,觀察它是如何工作的。

2、允許用戶為待辦事項選擇一個處理時間。

3、創建一個時間選擇器。

4、對待辦事項使用本地通知,并且當用戶修改處理時間時,更新本地通知的時間。

在你了解如何把這些和app融為一體之前,我們先來安排本地通知計劃,看看會發生什么。

順便說一下,本地通知和推送消息是不一樣的(推送消息,也叫遠程通知)。推送消息允許你的app接收外部事件觸發的通知,比如你最喜歡的球隊奪冠了。

本地通知更像是一個鬧鐘:你設定一個時間,然后它就會發出蜂鳴聲。

app僅僅在用戶允許的情況下,可以進行本地通知,如果用戶不允許,那么通知永遠不會出現。你需要詢問用戶是否同意消息通知,我們就從這里開始入手。

打開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啟動時被調用。它是app的準入點。是你在app啟動后要做某些操作時的最佳代碼位置。

因為你僅僅是使用一下本地通知,所以在這里用來請求用戶許可是最合適的。

你告訴了iOS這個app想要發送類型為“alert”的通知,并且附帶一個聲音效果。稍候,你會將這段代碼移入更加恰當的地方。

用點號開頭的東西

在我們的app中,見過了類似于.none, .checkmark, .subtitle以及現在的.alert和.sound。它們是枚舉符號。

一個枚舉enumeration,或者簡寫為enum,是一種數據類型,它由一系列符號和它們對應的值的列表組成。

例如UNAuthorizationOptions枚舉包含以下符號:

.badge
.sound
.alert
.carPlay

你可以把它們整合到一個數組中,來定義app發送給用戶的消息種類。這里你使用語句[.alert,.sound]組合了alert和sound。

在那里使用了枚舉是非常容易被識別的,因為它們名稱前面都有一個點。這是一種速寫的方法,它的完整寫法其實是:

UNAuthorizationOptions.alert,UNAuthorizationOptions.sound

幸運的是Swift是非常聰明的,它可以識別出.alert和.sound是來自UNAuthorizationOptions,這為你省了不少事。

運行app,這時你應該會得到一個許可請求的彈窗。

請求用戶允許通知

點擊Allow,那么下次app就不會再進行詢問了。iOS會記住第一次的結果。

如果你不小心點擊了Don't,也沒關系,你可以重置模擬器恢復這個彈窗,或者在app的setting中進行設置,就和其他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)

這樣就創建了一個新的本地通知。因為你寫了timeInterval: 10,所以這個通知會在app運行10秒后被觸發。

UNMutableNotificationContent描述了本地通知的內容。就是你在alert消息中設置的兩個文本信息,以及聲音。

最后,你將通知添加到UNUserNotificationCenter。這個對象是用于跟蹤所有本地通知并且在時間到了的時候觸發它們。

運行app,在app啟動后,按下Home間,回到手機的主界面(使用模擬器菜單Hardware->Home)

等待10秒,這也許會是你人生中比較漫長的10秒之一,然后你會看到一條彈出消息,當然還伴隨著一個提示音。

這就是本地通知,酷嗎?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容