WWDC 2018 Session 213: CarPlay Audio and Navigation Apps
檢視更多 WWDC 18 相關文章請前往 老司機x知識小集xSwiftGG WWDC 18 專題目錄
作者:Lefe_x
CarPlay 從出現至今,逐步開放了它的 API,讓第三方 APP 可以輕鬆地接入,這樣當你在開車的時候就可以享受到不一樣的開車體驗。就在今年(2018),蘋果允許第三方導航 APP 使用 CarPlay,這樣就可以把第三方的導航資料同步到 CarPlay 上,為此在 iOS12 中新增了 CarPlay framework,是不是很酷。與此同時蘋果優化了 CarPlay 對 Audio APP 的支援。在這個 session 中,蘋果主要從以下三方面進行講述:
- 音訊 APP 中 CarPlay 的效能提升
- 導航 APP 中新增的 CarPlay 框架
- Guidance
音訊 APP 中 CarPlay 的效能提升
在開始之前,我們需要了解下 CarPlay 的基本特性,比如觸控式螢幕、旋轉旋鈕和觸控板輸入、左右切換按鈕、夜間模式和螢幕大小等。這些基本的特性和我們使用手機一樣,通過觸控式螢幕和 CarPlay 進行互動。其實關鍵點是可以把手機上的資料傳輸到 CarPlay 螢幕上,由於 CarPlay 提供了模版式的 UI,這樣就不需要適配各種車型。 目前 CarPlay 中主要支援了以下功能:
- 通過 Automaker 來檢視天氣預報,收音機,警告等;
- 通過 Messaging 進行傳送接收訊息;
- 通過 VoIP calling 進行通話;
- 通過 Audio 來播放音樂聽廣播等;
- 通過 Navigation 進行導航。
而我們今天的主角是 Audio 和 Navigation。
關於 CarPlay 中的音訊 APP,官方以一個名叫 Srirocka 的 APP 來進行講解。在開發一款音訊 APP 時,我們可以讓它支援 CarPlay,只需要使用 MediaPlayer 這個框架中的 API 即可,它可以在所有的 CarPlay 系統中良好的執行,我們只需要提供給 CarPlay 需要的資料即可。
CarPlay 中主要的 API 有三部分,如圖所示。MPPlayableContent
負責展示內容,比如播放某個專輯需要顯示專輯中的音訊列表。顯示的資料需要通過 MPPlayableContentManager
來設定他的 dataSource 和 delegate,而這些和 UITableView 非常像;MPNowPlayingInfoCenter
負責顯示正在播放的音訊資訊,比如音訊名字,作者,時長等資訊;MPRemoteCommandCenter
指令中心,負責接收指令,比如在 CarPlay 中點選了暫停按鈕,那麼在手機 APP 中需要執行暫停操作。
具體程式碼可以參考:
// 設定 MPPlayableContentManager 的 data source 和 delegate,為 // MPPlayableContentManager 提供資料[MPPlayableContentManager sharedContentManager].dataSource = self;
[MPPlayableContentManager sharedContentManager].delegate = self;
// 通過 MPNowPlayingInfoCenter 設定正在播放的音樂的基本資訊[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = @{
MPMediaItemPropertyAlbumTitle : @"草原"
};
// 響應遠端指令MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
commandCenter.playCommand.enabled = YES;
[commandCenter.playCommand addTarget:self action:@selector(playCommendActuon)];
複製程式碼
以上這些 API 在 iOS12 之前已經提供給了開發者,而在 iOS12 中主要是做了一些優化:
- 提升了
MPPlayableContent
的效能; - 提升了啟動速度;
- 提升了動畫流暢度;
- 提升了 CarPlay 與 APP 的互動性。
蘋果建議開發者僅僅在需要的時候呼叫 MPPlayableContentManager
的 reloadData
方法,以及可以使用 beginUpdates
和 endUpdates
來同時重新整理多個資料。
蘋果特別強調,開發者需要注意的一些場景,比如連線到 CarPlay 的 iPhone 被密碼鎖定,弱網情況等。開發者需要預處理一些要播放的內容,比如當要播放下一首音樂的時候,需要預先載入,這樣可以有效避免網路異常時出現的錯誤。可以在下面這個方法來處理預先載入的音訊。
- (void)beginLoadingChildItemsAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void (^)(NSError * _Nullable))completionHandler{
completionHandler(nil);
}複製程式碼
導航 APP 中新增的 CarPlay 框架
在蘋果今年釋出的 iOS12 中,CarPlay 可以支援第三方導航,比如谷歌地圖。而在 Xcode 10 中可以找到 CarPlay Framework
。你可以在導航 APP 中自定義介面來顯示導航資訊。蘋果提供了很多模版來顯示不同的檢視,開發者需要做的就是把需要展示的資料交給模版,這樣 CarPlay 就會顯示你所定義的資料。這種靈活性可以讓你專注於 CarPlay 的體驗而不需要花費力氣適配所有支援 CarPlay 的車型。你只需花費一點點精力即可讓你的導航 APP 擁有 CarPlay 能力。
在學習 CarPlay 框架的時候,我們需要了解這個框架中各個類的作用,為方便理解,小編(@Lefe_x)做了一個圖:
其實看完上面的圖,我們可以知道 CarPlay 這個框架所做的事就是顯示 UI 到 CarPlay 螢幕上,而這些 UI 都提供了對應的模版,只要按著這些模版建立不同的 UI 即可適配所有的車型。
我們下面跟著程式碼來看看 CarPlay 的使用:
首先需要實現 CPApplicationDelegate
,這個代理主要用來監聽與 CarPlay 連線成功和斷開連線;監聽使用者點選 Alert 的事件。
而這一切需要 CPMapTemplate 來承載整個地圖介面,它是一個根模版。CPMapTemplate
的作用可以用來管理 Pin 手勢,顯示導航提示,顯示導航條或地圖按鈕。如下圖所示:
// CarPlay 與 APP 連線成功後的回撥- (void)application:(nonnull UIApplication *)application didConnectCarInterfaceController:(nonnull CPInterfaceController *)interfaceController toWindow:(nonnull UIWindow *)window {
self.interfaceController = interfaceController;
self.carWindow = window;
UIViewController *rootVC = [UIViewController new];
window.rootViewController = rootVC;
// 建立 rootTemplate,CPMapTemplate 主要用來處理手勢,顯示導航提示,引導 CPMapTemplate *rootTemplate = [self createRootTemplate];
[self.interfaceController setRootTemplate:rootTemplate animated:NO];
}// CarPlay 與 APP 端開連結後的回撥- (void)application:(nonnull UIApplication *)application didDisconnectCarInterfaceController:(nonnull CPInterfaceController *)interfaceController fromWindow:(nonnull UIWindow *)window {
}// 建立 CPMapTemplate- (CPMapTemplate *)createRootTemplate{
CPMapTemplate *template = [[CPMapTemplate alloc] init];
CPBarButton *categorySearchButton = [[CPBarButton alloc] initWithType:CPBarButtonTypeImage handler:^(CPBarButton * _Nonnull barButton) {
[self displayFavoriteCategories];
}];
categorySearchButton.image = [UIImage imageNamed:@"Favorites"];
CPBarButton *trafficButton = [[CPBarButton alloc] initWithType:CPBarButtonTypeImage handler:^(CPBarButton * _Nonnull barButton) {
;
}];
trafficButton.image = [UIImage imageNamed:@"traffic"];
// 導航上新增了兩個按鈕 template.trailingNavigationBarButtons = @[trafficButton, categorySearchButton];
return template;
}複製程式碼
CPGridTemplate 是一個網狀的模版,類似於 UICollectionView
。最多隻顯示 8 個按鈕,它適合顯示多行多列的選單。
// 建立 CPGridTemplate,它會有多個 CPGridButton- (void)displayFavoriteCategories{
CPGridButton *parksButton = [[CPGridButton alloc] initWithTitleVariants:@[@"Parks"] image:[UIImage imageNamed:@"Parks"] handler:^(CPGridButton * _Nonnull barButton) {
[self searchForNearbyParks];
}];
CPGridButton *beachesButton = [[CPGridButton alloc] initWithTitleVariants:@[@"beaches"] image:[UIImage imageNamed:@"beaches"] handler:^(CPGridButton * _Nonnull barButton) {
}];
CPGridButton *forestsButton = [[CPGridButton alloc] initWithTitleVariants:@[@"forests"] image:[UIImage imageNamed:@"forests"] handler:^(CPGridButton * _Nonnull barButton) {
}];
CPGridButton *desertsButton = [[CPGridButton alloc] initWithTitleVariants:@[@"deserts"] image:[UIImage imageNamed:@"deserts"] handler:^(CPGridButton * _Nonnull barButton) {
}];
NSArray *buttons = @[parksButton, beachesButton, forestsButton, desertsButton];
CPGridTemplate *template = [[CPGridTemplate alloc] initWithTitle:@"Favorites" gridButtons:buttons];
[self.interfaceController pushTemplate:template animated:YES];
}複製程式碼
CPListTemplate 和 UITableView 很相似,它由一個或多個分組組成,而每一個分組使用 CPListSection
表示,CPListSection
中存放的是 CPListItem
,可以設定它的標題,圖片,副標題。它適合用於顯示列表型別的 UI,猶如 UITableView
。
- (void)displayNearbyParks:(NSArray<
SearchResult *>
*)results{
NSMutableArray *listItems = [NSMutableArray array];
for (int i = 0;
i <
results.count;
i++) {
SearchResult *item = results[i];
[listItems addObject:[[CPListItem alloc] initWithText:item.name detailText:item.address image:item.image]];
} CPListSection *section = [[CPListSection alloc] initWithItems:listItems];
CPListTemplate *listTemplate = [[CPListTemplate alloc] initWithSections:@[section]];
listTemplate.title = @"Parks";
listTemplate.delegate = self;
[self.interfaceController pushTemplate:listTemplate animated:YES];
}複製程式碼
CPSearchTemplate 用來搜尋目的地,並顯示搜尋結果。
CPVoiceControlTemplate 一個語音控制的模版,用於語音搜尋樣式。
Guidance
當使用者選擇一個目的地開啟導航後,會彈出一個導航預覽,使用者確認後開始導航,直到導航結束。下面這張圖就是整個導航的流程:
當使用者選擇好目的地後,這時 CarPlay 會顯示一個預覽,使用者如果點選“確定”,導航便開始。具體可以檢視程式碼:
導航開始執行後,下面這個代理方法會執行。我們需要在 CPMapTemplateDelegate
中處理。這個時候需要開啟 CPNavigationSession
它表示一次導航會話,可以通過 CPNavigationSession
對這次的導航進行操作,比如取消本次導航。
- (void)mapTemplate:(CPMapTemplate *)mapTemplate startedTrip:(CPTrip *)trip usingRouteChoice:(CPRouteChoice *)routeChoice{
[mapTemplate hideTripPreviews];
// 當使用者選擇一個路線後將執行 CPNavigationSession *session = [mapTemplate startNavigationSessionForTrip:trip];
[session pauseTripForReason:CPTripPauseReasonLoading];
}複製程式碼
可以通過 CPMapTemplateDelegate
代理方法來控制顯示當前導航的狀態。
- (BOOL)mapTemplate:(CPMapTemplate *)mapTemplate shouldShowNotificationForManeuver:(CPManeuver *)maneuver {return YES;
}- (BOOL)mapTemplate:(CPMapTemplate *)mapTemplate shouldUpdateNotificationForManeuver:(CPManeuver *)maneuver withTravelEstimates:(CPTravelEstimates *)travelEstimates {
return YES;
}- (BOOL)mapTemplate:(CPMapTemplate *)mapTemplate shouldShowNotificationForNavigationAlert:(CPNavigationAlert *)navigationAlert{
return YES;
}複製程式碼
總結
總的來說,CarPlay 今年最大的改動就是可以在導航 APP 中使用 CarPlay,提升了 Audio APP 在 CarPlay 中的效能。如果你正在做導航型別的 APP,可以嘗試支援 CarPlay。