iOS開發之構建Widget,ios構建widget

來源:互聯網
上載者:User

iOS開發之構建Widget,ios構建widget
原文出處: 陳凱 在 jianshu 的部落格(@chenkaiHome)   歡迎分享原創到伯樂頭條

伴隨這iOS 8 系統多達4000項API更新而來同樣還有Today Extension.而對iOS而言,有了Today Extension 開發人員可以很好藉助系統提供的存取點為系統定製的服務,提供自訂的附加功能.這意味著什麼呢?從iOS 7版本嘗試開路到現在iOS 8更新的到來終於向開發人員開放Widget接入,這意味著系統應用和第三方應用都可以通知中樞(Notification Center)裡面實現互動. Notification Center Widget [Via Apple]

其實相對於Android,因其特有開放性Widget外掛程式已經發展了很多年,擁有極高自由定製性,在新版本的Android系統中甚至可以將部分外掛程式擺在鎖屏頁.而Google和各大軟體廠商製作的Widget外掛程式也能很好與系統的整體風格進行無縫的融合,而直到目前iOS 8版本中,Widget也就只是能擺在通知中樞(Notification Center)今天通知欄中而已,相對於Android也聽到很多人把這個作為”iOS不夠開放”一個有力的依據.針對這個問題其實Apple也在iOS Human Interface Guidelines中提到:

iOS 8 中開發人員的中心並不應該發生改變,依然應該是圍繞 app.在 app 中提供優秀互動和有用的功能,現在是,將來也會是 iOS 應用開發的核心任務。而Widget在 iOS 中是不能以單獨的形式存在的,一定是隨著一個應用一起打包提供的。

從這個側面可見,Apple對開放一直持有審慎的態度,開放的目的是力求保證整體體驗完整性,雖然iOS的Widget相比Android自訂性太低,但基於Apple目前的開放程度而言是能夠很有效控制Widget與系統的更好的融合.雖似戴著鐐銬起舞,但卻能捕獲人心.

而從使用者角度來看,在無需開啟應用前提下就可以對訊息進行處理的互動特性,使它在很多情境裡有效提升了使用者操作效率.例如在Widget中快速回複email,即時完成Todo議程等.這種互動更多從更宏觀角度重新定義了訊息,通知中樞(Notification Center)通過擷取使用者上一行為,還可以起到承接下一行為的作用(雖然目前開放API只能做到系統級的行為).點雖小,但這對使用者使用習慣改變卻是巨大的. Widget on hands [Via Yalantis]

有人看到這肯定一定會問為何沒有提到Windows Phone平台?因為無論從通知中樞快捷入口數量還是談到可以互動的點一句話而概之WP的現狀是“一窮二白”,你想作為曾經走過WP7時代使用者根本不知道通知中樞為何物的,而是用了足足兩年時間WP8上才有體現,而那些被其他平台玩膩的希望習以為常通知中樞互動,就像這樣:

WP 通知中樞[Via PCGGroup]

你就像看這張靜態圖片一樣也就是停留只是看看程度而已(除了刪除操作之外),MS針對通知中樞現在最新訊息是未來會支援類似可以通知中樞直接回複簡訊等互動,至於什麼時候能夠等到,誰知道呢.

說了這麼多,迴歸正題.

1.互動

在開始構建Widget之前,如果想對Widget實現技術細節和互動特點有一個完整概覽,我覺得沒有什麼文檔比官方App Extension Programming Guide更值得一讀了.剛開始接觸iOS通知中樞,一直很疑惑為何通知中樞採用兩個不同Tab“今日”和“通知”來對訊息進行分離.其實這和Widget工作機制有關.

Widget是放在“今日”Tab之中,而它工作機制是只有使用者下拉通知中樞時才會去重新整理擷取最新資料,這種做法和Android不同在於,Android更偏向於把整個Widget一直放在後台即時持續的更新.設想一下,如果我們看同樣天氣資訊,Android會持續消耗資源去做一件使用者不會即時預覽資訊,這也就能解釋為何經常看到Android使用者抱怨耗電問題.而對於立即訊息,iOS做法是直接把這些訊息即時歸類到”通知“Tab中.其實這種做法很好解決採用消耗最少資源前提下保證其操作的靈活性.

因為現有Widget一般來說是展現在系統層級的 UI上,所以在App Extension Programming Guide中Apple對Widget互動提出如下明確的要求:

擴充應該保持輕巧迅速,並且專註功能單一,在不打擾或者中斷使用者使用當前應用的前提下完成自己的功能點.

類似一直摯愛Todo應用Clear則互動上堪稱上典範: Clear’s Widget

當然如果動點腦子會發現,Widget開放iOS上實現應用之間Launcher成為了可能,類似早期一直很魔性應用”Launcher”: Launcher’s Widget

可以讓用在 iOS 的通知中樞裡,以類似應用程式捷徑的方式直接快速切換 App 的小工具,其實當初在推出沒多久後,便被 Apple 以”誤用 / 濫用”Widgets 為理由下架,但有意思的就在幾天前3月20日又重新上架.

2.構建

在Widget技術實現細節上,並不打算在本篇把所有技術細節通覽一遍,我只會寫我個人(其實就是初學者)認為值得寫的容易出錯的點或者耗費一些時間找到一些問題的解決方案.

2.1 純程式碼構建

Xcode 6中已經支援Today Extension建立Widget的模板,該模板會預設建立MainInterface.storyboard檔案來構建UI: StoryBoard UI

當然對於一個純程式碼的擁躉而言,肯定直接刪除storyboard檔案採用純程式碼方式來進行構建,刪除完後之後注意需要找到Supporting Files下面的Info.plist中NSExtension欄位做如下兩個操作:

A:直接刪除NSExtensionMainStoryboard欄位

B:添加NSExtensionPrincipalClass欄位 並設為TodayViewController

如下: 修改後

注意當採用Xcode預設範本建立Widget時會自動把ViewController檔案命名設定為“TodayViewController”.當然這個ViewController命名其實是可以修改的,唯一值得注意的修改該ViewController檔案命名後還需要設定NSExtensionPrincipalClass的值與其保持一致即可.不然Widget編譯時間會報找不到對應入口.

2.2 左側間隔

當第一次添加UI元素採用真機來運行Widget會發現,Widget左側到螢幕之間始終會有一段距離的間隔,導致調整布局和差距甚遠,類似這樣: 左側間隔

其實這個問題主要是因為Widget裡面的視圖預設居左居下都會有一定距離的間隔,可以採用如下方式取消間隔,使布局地區填充整個Widget:

取消間隔

這種方式把整個布局填充地區間隔都設定為0,當然更簡潔的方式是你可以直接採用“return UIEdgeInsetsZero;”方式.而關於Widget上布局處理則採用Masonry架構做的相對布局,簡單快捷推薦.當然關於Masonry架構快速上手則不得不推薦閱讀Masonry介紹與使用實踐(快速上手Autolayout).

2.3 整個點擊地區實現

如你所看當使用者拉開Widget時,因為Widget是依賴於應用程式在分發時是跟應用程式一塊打包的,希望點擊Widget布局任何地區都能喚起主應用程式,常用的方式在整個View增加Tap事件訂閱處理:

Tap事件

但這種方式會額外產生一個問題,如果Widget空白地區沒有任何UI元素則無法觸發該事件,那這裡有一個小技巧可以解決改問題,可以整個Widget增加一個透明的ImageView:

設定透明度

初始化時注意把imageview透明度設定為0.01最小值,那麼無論設定其背景色為什麼值肉眼都是不可見的.然後使用Masonry架構布局來填充Widget整個背景如下:

填充整個背景

然後為imageview增加Tap事件訂閱即可:

增加事件訂閱

這樣就能整個Widget地區可點擊效果.另外針對通過Widget中喚起主應用程式方式目前只支援url scheme方式來實現.同時也是Widget向主應用程式反饋資料和互動的渠道之一.

2.4 定時更新機制

Widget自身更新機制當使用者下拉通知中樞(Notification Center)時立即更新資料,但我們仔細研究Widget使用者使用情境時發現,如果使用者鎖屏時間過長,開啟Widget後不做任何操作,這個時候針對一些即時類應用,類似我們天氣中可能涉及到災害預警它要求情境資料一旦產生就要即時展現給使用者,這就需要我們基於Widget自身機制外還要處理這個情境下天氣資料自動更新的問題.

這個時候我們需要構建一個定時更新的NSTimer:

初始化NSTimer

非常簡單,在NSTimer固定更新間隔執行的方法調用就是更新資料方法,當然重點不在這裡,而是觸發和關閉這個NSTimer時機.按照Widget生命週期來說,如果使用者是第一次下拉查看Widget其實就是執行整個ViewController生命週期調用過程,這個並沒有什麼問題,但是還是存在一個特殊情況.系統為了保證Widget上資料是及時更新的,預設會截取上次顯示成功Widget的快照.這個快照會一直儲存到新的資料或UI被更新才回被替換,那這就會帶來一個問題,當你拖拽通知中樞(Notification Center)下拉過於頻繁時,Debug跟蹤代碼執行路徑你會發現整個Widget生命週期執行過程和第一次下拉執行的路徑發生了變化.

第一次下拉執行路徑是viewDidLoad->viewWillAppear,而如果下拉過於頻繁你就會發現代碼執行路徑直接只會執行viewWillAppear方法,這個就是系統預設儲存上次快照而導致的執行路徑上變化.這對我們選擇NSTimer更新時機以及後面會提到的Widget橫豎屏處理都會有影響.

那麼很明顯,為了保證這個定時更新機制能夠無論使用者什麼情況下操作都能起作用,我們需要把NSTimer fire觸發代碼調用放到viewWillAppear方法中來.同理當Widget關閉後在viewDidDisappear方法取消NSTimer invalidate定時更新即可.

2.5 Widget橫屏支援

關於Widget橫屏支援在開發中耽誤一點時間來解決這個問題,在iPhone 6 & Plus上已經橫豎屏直接切換,Widget預設是豎屏,但如果你需求中橫屏UI的布局和豎屏布局完全不同,這個時候你就需要判斷當前Widget橫豎屏狀態來切換對應的布局.

當然一般思路我們都會按照端內處理橫豎屏方式來處理Widget,如果你翻過官方的開發文檔,你會發現在iOS 6.0版本之前UIViewController之間橫豎屏切換,只需要設定shouldAutorotateToInterfaceOrientation函數即可.UIInterfaceOrientation是UIApplication.h標頭檔中定義的枚舉類型,總共有四個方向.在shouldAutorotateToInterfaceOrientation方法中返回相應的結果即可,如果直接返回YES將支援所有方向.而在iOS 6.0版本之後,UIViewController之間橫豎屏切換需要多設定一個supportedInterfaceOrientations函數返回UIInterfaceOrientationMask枚舉類型.除了設定shouldAutorotateToInterfaceOrientation之外,還要將supportedInterfaceOrientations返回的方向與shouldAutorotateToInterfaceOrientation保持一致,否則會在兩個支援不同橫豎屏ViewController中切換時,會出現豎屏變橫屏,橫屏變豎屏的情況.但問題是這種方式是否適用Widget橫屏處理呢?

使用UIDeviceOrientationIsPortrait來判斷:

判斷橫屏方法一

當你執行這段代碼調試時你會發現,orientation方向的值始終都會是UIDeviceOrientationUnknown.如果你點開UIDeviceOrientation枚舉你會看到.它包含了兩個扁平方向UIDeviceOrientationFaceUp和UIDeviceOrientationFaceDown,其實它代表的意思螢幕朝上或朝下平躺兩個方向的判斷.所以當你裝置平躺案頭時.即時你有時已經切換了橫屏你會發現它會返回FaceUp或FaceDown,所以你當你調用UIDeviceOrientationIsPortrait方法時它傳回值其實是沒有意義的,因為裝置目前方向在平躺下Faceup和FaceDown既不是橫屏也不是豎屏.難道沒有更好的方式嘛?

可以採用如下方式能夠完美解決Widget橫豎屏切換狀態判斷的問題:

Widget橫豎屏狀態判斷

其實設定Widget顯示高度時就會發現,高度在橫豎屏狀態切換是不會變化的,但寬度會隨著橫豎屏狀態切換會發生變化,所以判斷螢幕寬度這個思路是可取的.因為橫豎屏UI布局不同,調用時機則可以選擇在viewWillLayoutSubviews或viewDidLayoutSubviews方法中進行.因為這兩個方法都是viewWillAppear方法是必然執行的,這也就自然規避Widget自身因為下拉快照儲存機制導致代碼執行路徑變化導致布局更新的問題.

2.6 Widget國際化

在來說說這個Widget國際化,因為我們用戶端自身已經支援三種不同語言,這就是導致Widget也是需要根據端內語言變化必須有國際化的支援.其實我們端內已經做了一套完整的國際化機制.Widget最好處理方式能夠複用端內機制,而不需要單獨開發支援.iOS 8 新引入的自製 framework 的方式來組織需要重用的代碼,這樣在連結 framework 後 app 和Widget就都能使用相同的代碼. 包含Widget中資料請求和資料記憶其他能夠複用的代碼。

這也是我們一開始打算解決方式,但發現剝離這部分代碼時間周期明顯超過我們預期.所以在國際化處理上我們Widget獨立做了一套國際化處理,它和端內在處理機制上並沒有多大的不同:

Widget國際化處理

當然重點不再於它的實現,你可以發現我們Widget中國際化文字檔Locallizable.string命名加了一個”WG”,這個問題是剛開始開發之初我們一直認為Widget作為端是獨立於主應用程式的.所以當初理解為只有把這個檔案命名為的“Locallizable.string”才是正常的能夠被識別的,但我們調試時發現,Widget打包時會把這些國際化單獨放到PlugIns檔案下,這裡給出一個簡體中文全路徑:

/private/var/mobile/Containers/Bundle/Application/61C637FF-B5BC-432A-ADD5-BA64EBFE98E8/MojiWeather.app/PlugIns/MojiWidget.appex/zh-Hans.lproj

根據這個路徑你會發現檔案時可以找到的,但調試時發現國際化取對應Key的值一直是取不到的,但我們任意非“Locallizable.string”時則是沒有問題的,後來我們發現當我們打包在不同機型上測試這個問題時,如果“Locallizable.string”名稱命名會導致調試時ok,而最終打包上會出現找不到對應key值得問題.這個原因到我寫這篇blog一直沒有找到具體的原因.所以我們給出解決方案是一定要和主應用程式“Locallizable.string”保持不同即可解決.

當然關於Widget中閃現的問題,因為我們Widget存在兩個不同尺寸切換,導致這個問題很明顯,處理方式自然是viewWillLoad方式中做好Widget高度在不同情境高度初始化就可以完美避免.這裡就不做贅述.

如上只是我們解決Widget遇到一些大大小小的問題.解決問題方式雖然沒有給出細節,但思路是有的.有不清楚可以文後評論@我即可.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.