伴隨這iOS 8 系統(tǒng)多達(dá)4000項API更新而來同樣還有Today Extension.而對iOS而言外构,有了Today Extension 開發(fā)者可以很好借助系統(tǒng)提供的接入點為系統(tǒng)定制的服務(wù),提供自定義的附加功能.這意味著什么呢程储?從iOS 7版本嘗試開路到現(xiàn)在iOS 8更新的到來終于向開發(fā)者開放Widget接入,這意味著系統(tǒng)應(yīng)用和第三方應(yīng)用都可以通知中心(Notification Center)里面實現(xiàn)交互.
Notification Center Widget [Via Apple]
其實相對于Android,因其特有開放性Widget插件已經(jīng)發(fā)展了很多年椿争,擁有極高自由定制性,在新版本的Android系統(tǒng)中甚至可以將部分插件擺在鎖屏頁.而Google和各大軟件廠商制作的Widget插件也能很好與系統(tǒng)的整體風(fēng)格進(jìn)行無縫的融合,而直到目前iOS 8版本中鹦筹,Widget也就只是能擺在通知中心(Notification Center)今天通知欄中而已夜畴,相對于Android也聽到很多人把這個作為"iOS不夠開放"一個有力的依據(jù).針對這個問題其實Apple也在iOS Human Interface Guidelines中提到:
iOS 8 中開發(fā)者的中心并不應(yīng)該發(fā)生改變拖刃,依然應(yīng)該是圍繞 app.在 app 中提供優(yōu)秀交互和有用的功能,現(xiàn)在是贪绘,將來也會是 iOS 應(yīng)用開發(fā)的核心任務(wù)兑牡。而Widget在 iOS 中是不能以單獨的形式存在的,一定是隨著一個應(yīng)用一起打包提供的。
從這個側(cè)面可見税灌,Apple對開放一直持有審慎的態(tài)度均函,開放的目的是力求保證整體體驗完整性,雖然iOS的Widget相比Android自定義性太低菱涤,但基于Apple目前的開放程度而言是能夠很有效控制Widget與系統(tǒng)的更好的融合.雖似戴著鐐銬起舞苞也,但卻能捕獲人心.
而從用戶角度來看,在無需打開應(yīng)用前提下就可以對消息進(jìn)行處理的交互特性,使它在很多場景里有效提升了用戶操作效率.例如在Widget中快速回復(fù)email粘秆,即時完成Todo日程等.這種交互更多從更宏觀角度重新定義了消息,通知中心(Notification Center)通過獲取用戶上一行為如迟,還可以起到承接下一行為的作用(雖然目前開放API只能做到系統(tǒng)級的行為).點雖小,但這對用戶使用習(xí)慣改變卻是巨大的.
Widget on hands [Via Yalantis]
有人看到這肯定一定會問為何沒有提到Windows Phone平臺?因為無論從通知中心快捷入口數(shù)量還是談到可以交互的點一句話而概之WP的現(xiàn)狀是“一窮二白”攻走,你想作為曾經(jīng)走過WP7時代用戶根本不知道通知中心為何物的殷勘,而是用了足足兩年時間WP8上才有體現(xiàn),而那些被其他平臺玩膩的希望習(xí)以為常通知中心交互陋气,就像這樣:
WP 通知中心[Via PCGGroup]
你就像看這張靜態(tài)圖片一樣也就是停留只是看看程度而已(除了刪除操作之外),MS針對通知中心現(xiàn)在最新消息是未來會支持類似可以通知中心直接回復(fù)短信等交互劳吠,至于什么時候能夠等到,誰知道呢.
說了這么多巩趁,回歸正題.
1.交互
在開始構(gòu)建Widget之前痒玩,如果想對Widget實現(xiàn)技術(shù)細(xì)節(jié)和交互特點有一個完整概覽淳附,我覺得沒有什么文檔比官方App Extension Programming Guide更值得一讀了.剛開始接觸iOS通知中心,一直很疑惑為何通知中心采用兩個不同Tab“今日”和“通知”來對消息進(jìn)行分離.其實這和Widget工作機(jī)制有關(guān).
Widget是放在“今日”Tab之中蠢古,而它工作機(jī)制是只有用戶下拉通知中心時才會去刷新獲取最新數(shù)據(jù)奴曙,這種做法和Android不同在于,Android更偏向于把整個Widget一直放在后臺實時持續(xù)的更新.設(shè)想一下草讶,如果我們看同樣天氣信息洽糟,Android會持續(xù)消耗資源去做一件用戶不會實時預(yù)覽信息,這也就能解釋為何經(jīng)常看到Android用戶抱怨耗電問題.而對于即時消息堕战,iOS做法是直接把這些消息實時歸類到”通知“Tab中.其實這種做法很好解決采用消耗最少資源前提下保證其操作的靈活性.
因為現(xiàn)有Widget一般來說是展現(xiàn)在系統(tǒng)級別的 UI上坤溃,所以在App Extension Programming Guide中Apple對Widget交互提出如下明確的要求:
擴(kuò)展應(yīng)該保持輕巧迅速,并且專注功能單一嘱丢,在不打擾或者中斷用戶使用當(dāng)前應(yīng)用的前提下完成自己的功能點.
類似一直摯愛Todo應(yīng)用Clear則交互上堪稱上典范:
Clear's Widget
當(dāng)然如果動點腦子會發(fā)現(xiàn)薪介,Widget開放iOS上實現(xiàn)應(yīng)用之間Launcher成為了可能,類似早期一直很魔性應(yīng)用"Launcher":
Launcher's Widget
可以讓用在 iOS 的通知中心里越驻,以類似應(yīng)用程序捷徑的方式直接快速切換 App 的小工具汁政,其實當(dāng)初在推出沒多久后,便被 Apple 以"誤用 / 濫用"Widgets 為理由下架缀旁,但有意思的就在幾天前3月20日又重新上架.
2.構(gòu)建
在Widget技術(shù)實現(xiàn)細(xì)節(jié)上记劈,并不打算在本篇把所有技術(shù)細(xì)節(jié)通覽一遍,我只會寫我個人(其實就是初學(xué)者)認(rèn)為值得寫的容易出錯的點或者耗費一些時間找到一些問題的解決方案.
2.1 純代碼構(gòu)建
Xcode 6中已經(jīng)支持Today Extension創(chuàng)建Widget的模板并巍,該模板會默認(rèn)創(chuàng)建MainInterface.storyboard文件來構(gòu)建UI:
StoryBoard UI
當(dāng)然對于一個純代碼的擁躉而言目木,肯定直接刪除storyboard文件采用純代碼方式來進(jìn)行構(gòu)建,刪除完后之后注意需要找到Supporting Files下面的Info.plist中NSExtension字段做如下兩個操作:
A:直接刪除NSExtensionMainStoryboard字段
B:添加NSExtensionPrincipalClass字段 并設(shè)為TodayViewController
如下:
修改后
注意當(dāng)采用Xcode默認(rèn)模板創(chuàng)建Widget時會自動把ViewController文件命名設(shè)置為“TodayViewController”.當(dāng)然這個ViewController命名其實是可以修改的履澳,唯一值得注意的修改該ViewController文件命名后還需要設(shè)置NSExtensionPrincipalClass的值與其保持一致即可.不然Widget編譯時會報找不到對應(yīng)入口.
2.2 左側(cè)間隔
當(dāng)?shù)谝淮翁砑覷I元素采用真機(jī)來運行Widget會發(fā)現(xiàn)嘶窄,Widget左側(cè)到屏幕之間始終會有一段距離的間隔怀跛,導(dǎo)致調(diào)整布局和效果圖差距甚遠(yuǎn)距贷,類似這樣:
左側(cè)間隔
其實這個問題主要是因為Widget里面的視圖默認(rèn)居左居下都會有一定距離的間隔,可以采用如下方式取消間隔吻谋,使布局區(qū)域填充整個Widget:
取消間隔
這種方式把整個布局填充區(qū)域間隔都設(shè)置為0忠蝗,當(dāng)然更簡潔的方式是你可以直接采用“return UIEdgeInsetsZero;”方式.而關(guān)于Widget上布局處理則采用Masonry框架做的相對布局,簡單快捷推薦.當(dāng)然關(guān)于Masonry框架快速上手則不得不推薦閱讀Masonry介紹與使用實踐(快速上手Autolayout).
2.3 整個點擊區(qū)域?qū)崿F(xiàn)
如你所看當(dāng)用戶拉開Widget時,因為Widget是依賴于應(yīng)用程序在分發(fā)時是跟應(yīng)用程序一塊打包的漓拾,希望點擊Widget布局任何區(qū)域都能喚起主應(yīng)用程序,常用的方式在整個View增加Tap事件訂閱處理:
Tap事件
但這種方式會額外產(chǎn)生一個問題阁最,如果Widget空白區(qū)域沒有任何UI元素則無法觸發(fā)該事件,那這里有一個小技巧可以解決改問題,可以整個Widget增加一個透明的ImageView:
設(shè)置透明度
初始化時注意把imageview透明度設(shè)置為0.01最小值,那么無論設(shè)置其背景色為什么值肉眼都是不可見的.然后使用Masonry框架布局來填充Widget整個背景如下:
填充整個背景
然后為imageview增加Tap事件訂閱即可:
增加事件訂閱
這樣就能整個Widget區(qū)域可點擊效果.另外針對通過Widget中喚起主應(yīng)用程序方式目前只支持url scheme方式來實現(xiàn).同時也是Widget向主應(yīng)用程序反饋數(shù)據(jù)和交互的渠道之一.
2.4 定時更新機(jī)制
Widget自身更新機(jī)制當(dāng)用戶下拉通知中心(Notification Center)時立即更新數(shù)據(jù)骇两,但我們仔細(xì)研究Widget用戶使用場景時發(fā)現(xiàn)速种,如果用戶鎖屏?xí)r間過長,打開Widget后不做任何操作低千,這個時候針對一些即時類應(yīng)用配阵,類似我們天氣中可能涉及到災(zāi)害預(yù)警它要求場景數(shù)據(jù)一旦產(chǎn)生就要實時展現(xiàn)給用戶,這就需要我們基于Widget自身機(jī)制外還要處理這個場景下天氣數(shù)據(jù)自動更新的問題.
這個時候我們需要構(gòu)建一個定時更新的NSTimer:
初始化NSTimer
非常簡單,在NSTimer固定更新間隔執(zhí)行的方法調(diào)用就是更新數(shù)據(jù)方法棋傍,當(dāng)然重點不在這里救拉,而是觸發(fā)和關(guān)閉這個NSTimer時機(jī).按照Widget生命周期來說,如果用戶是第一次下拉查看Widget其實就是執(zhí)行整個ViewController生命周期調(diào)用過程瘫拣,這個并沒有什么問題亿絮,但是還是存在一個特殊情況.系統(tǒng)為了保證Widget上數(shù)據(jù)是及時更新的,默認(rèn)會截取上次顯示成功Widget的快照.這個快照會一直保存到新的數(shù)據(jù)或UI被更新才回被替換,那這就會帶來一個問題,當(dāng)你拖拽通知中心(Notification Center)下拉過于頻繁時麸拄,Debug跟蹤代碼執(zhí)行路徑你會發(fā)現(xiàn)整個Widget生命周期執(zhí)行過程和第一次下拉執(zhí)行的路徑發(fā)生了變化.
第一次下拉執(zhí)行路徑是viewDidLoad->viewWillAppear,而如果下拉過于頻繁你就會發(fā)現(xiàn)代碼執(zhí)行路徑直接只會執(zhí)行viewWillAppear方法派昧,這個就是系統(tǒng)默認(rèn)保存上次快照而導(dǎo)致的執(zhí)行路徑上變化.這對我們選擇NSTimer更新時機(jī)以及后面會提到的Widget橫豎屏處理都會有影響.
那么很明顯,為了保證這個定時更新機(jī)制能夠無論用戶什么情況下操作都能起作用拢切,我們需要把NSTimer fire觸發(fā)代碼調(diào)用放到viewWillAppear方法中來.同理當(dāng)Widget關(guān)閉后在viewDidDisappear方法取消NSTimer invalidate定時更新即可.
2.5 Widget橫屏支持
關(guān)于Widget橫屏支持在開發(fā)中耽誤一點時間來解決這個問題,在iPhone 6 & Plus上已經(jīng)橫豎屏直接切換斗锭,Widget默認(rèn)是豎屏,但如果你需求中橫屏UI的布局和豎屏布局完全不同失球,這個時候你就需要判斷當(dāng)前Widget橫豎屏狀態(tài)來切換對應(yīng)的布局.
當(dāng)然一般思路我們都會按照端內(nèi)處理橫豎屏方式來處理Widget岖是,如果你翻過官方的開發(fā)文檔,你會發(fā)現(xiàn)在iOS 6.0版本之前UIViewController之間橫豎屏切換实苞,只需要設(shè)置shouldAutorotateToInterfaceOrientation函數(shù)即可.UIInterfaceOrientation是UIApplication.h頭文件中定義的枚舉類型豺撑,總共有四個方向.在shouldAutorotateToInterfaceOrientation方法中返回相應(yīng)的結(jié)果即可,如果直接返回YES將支持所有方向.而在iOS 6.0版本之后黔牵,UIViewController之間橫豎屏切換需要多設(shè)置一個supportedInterfaceOrientations函數(shù)返回UIInterfaceOrientationMask枚舉類型.除了設(shè)置shouldAutorotateToInterfaceOrientation之外,還要將supportedInterfaceOrientations返回的方向與shouldAutorotateToInterfaceOrientation保持一致聪轿,否則會在兩個支持不同橫豎屏ViewController中切換時,會出現(xiàn)豎屏變橫屏猾浦,橫屏變豎屏的情況.但問題是這種方式是否適用Widget橫屏處理呢?
使用UIDeviceOrientationIsPortrait來判斷:
判斷橫屏方法一
當(dāng)你執(zhí)行這段代碼調(diào)試時你會發(fā)現(xiàn)陆错,orientation方向的值始終都會是UIDeviceOrientationUnknown.如果你點開UIDeviceOrientation枚舉你會看到.它包含了兩個扁平方向UIDeviceOrientationFaceUp和UIDeviceOrientationFaceDown,其實它代表的意思屏幕朝上或朝下平躺兩個方向的判斷.所以當(dāng)你設(shè)備平躺桌面時.即時你有時已經(jīng)切換了橫屏你會發(fā)現(xiàn)它會返回FaceUp或FaceDown金赦,所以你當(dāng)你調(diào)用UIDeviceOrientationIsPortrait方法時它返回值其實是沒有意義的音瓷,因為設(shè)備目前方向在平躺下Faceup和FaceDown既不是橫屏也不是豎屏.難道沒有更好的方式嘛?
可以采用如下方式能夠完美解決Widget橫豎屏切換狀態(tài)判斷的問題:
Widget橫豎屏狀態(tài)判斷
其實設(shè)置Widget顯示高度時就會發(fā)現(xiàn),高度在橫豎屏狀態(tài)切換是不會變化的,但寬度會隨著橫豎屏狀態(tài)切換會發(fā)生變化,所以判斷屏幕寬度這個思路是可取的.因為橫豎屏UI布局不同摆霉,調(diào)用時機(jī)則可以選擇在viewWillLayoutSubviews或viewDidLayoutSubviews方法中進(jìn)行.因為這兩個方法都是viewWillAppear方法是必然執(zhí)行的徘溢,這也就自然規(guī)避Widget自身因為下拉快照保存機(jī)制導(dǎo)致代碼執(zhí)行路徑變化導(dǎo)致布局更新的問題.
2.6 Widget國際化
在來說說這個Widget國際化,因為我們客戶端自身已經(jīng)支持三種不同語言,這就是導(dǎo)致Widget也是需要根據(jù)端內(nèi)語言變化必須有國際化的支持.其實我們端內(nèi)已經(jīng)做了一套完整的國際化機(jī)制.Widget最好處理方式能夠復(fù)用端內(nèi)機(jī)制,而不需要單獨開發(fā)支持.iOS 8 新引入的自制 framework 的方式來組織需要重用的代碼,這樣在鏈接 framework 后 app 和Widget就都能使用相同的代碼. 包含Widget中數(shù)據(jù)請求和數(shù)據(jù)記憶其他能夠復(fù)用的代碼。
這也是我們一開始打算解決方式珊楼,但發(fā)現(xiàn)剝離這部分代碼時間周期明顯超過我們預(yù)期.所以在國際化處理上我們Widget獨立做了一套國際化處理,它和端內(nèi)在處理機(jī)制上并沒有多大的不同:
Widget國際化處理
當(dāng)然重點不再于它的實現(xiàn)度液,你可以發(fā)現(xiàn)我們Widget中國際化文本文件Locallizable.string命名加了一個"WG",這個問題是剛開始開發(fā)之初我們一直認(rèn)為Widget作為端是獨立于主應(yīng)用程序的.所以當(dāng)初理解為只有把這個文件命名為的“Locallizable.string”才是正常的能夠被識別的厕宗,但我們調(diào)試時發(fā)現(xiàn)邓了,Widget打包時會把這些國際化單獨放到PlugIns文件下,這里給出一個簡體中文全路徑:
/private/var/mobile/Containers/Bundle/Application/61C637FF-B5BC-432A-ADD5-BA64EBFE98E8/MojiWeather.app/PlugIns/MojiWidget.appex/zh-Hans.lproj
根據(jù)這個路徑你會發(fā)現(xiàn)文件時可以找到的,但調(diào)試時發(fā)現(xiàn)國際化取對應(yīng)Key的值一直是取不到的媳瞪,但我們?nèi)我夥恰癓ocallizable.string”時則是沒有問題的骗炉,后來我們發(fā)現(xiàn)當(dāng)我們打包在不同機(jī)型上測試這個問題時,如果“Locallizable.string”名稱命名會導(dǎo)致調(diào)試時ok蛇受,而最終打包上會出現(xiàn)找不到對應(yīng)key值得問題.這個原因到我寫這篇blog一直沒有找到具體的原因.所以我們給出解決方案是一定要和主應(yīng)用程序“Locallizable.string”保持不同即可解決.
當(dāng)然關(guān)于Widget中閃現(xiàn)的問題句葵,因為我們Widget存在兩個不同尺寸切換,導(dǎo)致這個問題很明顯兢仰,處理方式自然是viewWillLoad方式中做好Widget高度在不同場景高度初始化就可以完美避免.這里就不做贅述.
文/chenkai(簡書作者)
原文鏈接:http://www.reibang.com/p/0efd62ee033a
著作權(quán)歸作者所有乍丈,轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),并標(biāo)注“簡書作者”把将。