iPad 多工 Spilt View & Size Class

guojin08發表於2018-03-11

[https://www.cnblogs.com/smileEvday/p/SpiltView_SizeClass.html]

一、多工簡介

iOS 9 以後iPad新增了多工的支援,主要形式有三種:

  • Slide Over (側邊快捷開啟)
  • Spilt View (多工分屏)
  • Picture in Picture (畫中畫)

1. Picture in Picture

使用系統AVKit或者AVFoundation庫提供的新的API替換掉老的 MPMoviePlayerViewController, MPMoviePlayerViewController,做一些調整即可。

2. Slide Over

Slide Over支援起來比較簡單,程式基本上不需要做任何程式碼級別的適配。

Tips: App互動設計的時候,應該避免採用螢幕右側向左滑這一類的互動。因為iOS9以後系統攔截了該事件用來觸發Slide Over & Spilt View。
例如網易新聞iPad端的評論劃出功能

3. Spilt View

多工分屏功能,我們今天主要探討的內容,後續章節有詳細描述。


二、是否要支援多工分屏?

1. 是否需要適配

引用官方的說法

Adopt Slide Over and Split View unless you have a specific reason not to. From a customer’s perspective, an iOS 9 app that doesn’t adopt Slide Over and Split View feels out of place.

簡單翻譯一下:“最好新增Slide Over & Spilt View的適配,除非你有足夠的原因。不做新特性的適配會讓使用者覺得你的程式out了”

當然也有例外的情況,如果你的程式屬於以下兩種,那麼不需要去做多工的適配:

  • 以拍照為主要功能的程式
  • 需要全屏互動的程式,例如需要使用到感測器的遊戲類應用

2. 如何禁用多工分屏

要禁掉App的多工分屏支援,只需要在App的info.plist中新增一個“UIRequiresFullScreen”的key,設定value為“YES”即可。

到這兒了,如果你的不需要支援多工分屏的話那麼就後面的內容就可以跳過了。


三. 適配前需要明確的幾個點

1. 轉變觀念

首先有一個觀念我們得轉變過來,同一時刻執行在前臺的App不再限制為一個。如果使用者進入了多工分屏模式的話,那麼就會有兩個App同時執行在前臺。

Both Apps in Spilt View are running in the foreground.

雖然兩個App同時執行在前臺時,地位卻不一樣,Primary(老大,通常在左邊)和Secondary(老二,通常指的是右邊的應用)。

只有老大:

  • 擁有狀態列的控制許可權
  • 可以處理外接螢幕的顯示(通常使用UIScreen實現)
  • 可以顯示畫中畫視窗
  • 可以佔據2/3寬的螢幕,而老二最多隻能佔據螢幕寬度的一半

避免使用UIScreen的bounds來處理App應該的展示區域,最好使用UIWindow的bounds來代替。

因為在多工分屏下UIScreen還是那個Screen,只不過App的keyWindow不再時時刻刻充滿UIScreen了,顯示在什麼位置,顯示多大面積,全部取決於使用者使用裝置的姿勢。

2. 面臨的問題

前面提到過了,當進入多工分屏模式以後,將會有兩個App同時執行在前臺。試想有兩個App同時需要使用CPU,GPU,記憶體,I/O及其它的硬體資源,要想保持良好的使用者體驗,就需要我們對自己的App做很多效能調優的方面的工作。

關於效能調優方面的知識,可以參考:Adopting Multitasking Enhancements on iPad

Every iOS app—even one that opts out of using multitasking features—needs to operate as a good citizen in iOS 9.
Now, even full-screen apps don’t have exclusive use of screen real estate, the CPU, memory, or other resources.

同時蘋果基於保證使用者體驗的角度,對於支援做了硬體層面的限制如下:

前面提到了,Primary App比Secondary App擁有諸多優勢,但是有一點是一樣的:

當系統收到記憶體警告時,無論是Primary App還是Secondary App均可能被Kill掉


四. 如何適配Spilt View

如果遵循iOS 8引入的新的UI最佳實踐,那麼適配多工適配將會是一件很容易的實情。可是問題關鍵問題就出在了這個“如果”上。

iOS 8推出以後蘋果提的最多的“Adaptivity”,以及新引入了Size Class體系,並提出了讓我們忘記裝置方向的概念,所以的這一切都是在為了我們能夠方便的實現App的佈局。

通常開發App的UI框架的陳舊加上互動設計只考慮橫豎屏(甚至只考慮一個方向)導致了我們適配Spilt View的難度比較大。

1. 適配Spilt View的幾點要求

  • Xcode 7 及之後編譯
  • 使用iOS 9 及之後的SDK
  • 使用"LaunchScreen.storyboard"代替launch.png之類的圖片,完成啟動畫面定製。Xcode7之後新建工程會自動幫忙建立該檔案並設定Info.plist,對於已存在的老工程需要我們手動建立該檔案,並在Info.plist中做相應配置。

    PS. 蘋果要求LaunchScrenn.storyboard中必須使用Autolayout佈局,還在用全手寫佈局的朋友們,該考慮切換到Auto Layout佈局了。

  • 支援四個方向

2. 多工分屏模式切換時發生了什麼

在多工分屏模式時,App展示的尺寸完全取決於使用者,可能會佔螢幕的3/10, 5/10, 7/10等,再加上豎屏時候那兩個奇葩的比例。一個App要完全支援iPad的多工分屏,如果使用硬編碼的方式,那麼需要考慮5套佈局,有沒有想死的感覺。

先彆著急,接著往下看。

當多工分屏模式下,使用者操作應用之間的分割區改變兩個應用的顯示比例時,系統會同時呼叫兩個App的“applicationWillResignActive”方法。然使用者完成操作的時候系統會再同時呼叫連個App的“applicationDidEnterBackground”方法。

關於如何相應App狀態變換,請看文件:Strategies for Handling App State Transitions

與此同時,系統會通過以下兩個方面告知我們分屏狀態的改變:

  • Window尺寸的變化
  • RootVC 的Size Class的變化

這兩個點配合起來才能完成多工分屏顯示模式切換的響應。

因為多工分屏顯示模式的變化並不總是伴隨著Size Class的變化,例如從3/10 --> 5/10的變換的時候水平方向的Size Class一直都是Compac模式,但是尺寸(寬度)卻發生了變化。

3. 如何響應Window尺寸變化

當Window尺寸變化時通常伴隨著VC的viewDidLayoutSubViews活著View的layOutSubViews方法的呼叫,我們可以在這些方法裡面重新計算位置,完成UI佈局的重新整理。

還有一個更好的方法就是Auto Layout,我們在程式一開始佈局的時候通過指定元素之間的約束來描述佈局,這樣在View的尺寸變化的時候系統會根據我們制定的約束條件自動完成佈局的重新整理。

4. 如何響應Size Class的變化

我是程式碼黨,介面佈局大部分都是靠程式碼實現,Storyboard幾乎不用。在一開始我查詢資料的時候一搜Size Class出來的都是教人怎麼在Storyboard中完成介面佈局的文章。還以為Size Class就是專門為Storyboard設計的。

後面在搜尋終於被我發現了一些端倪,iOS 8中 引入了兩個Protocol:UITraitEnvironment 和 UIContentContainer,以及一個類UITraitCollection

其中UITraitEnvironment協議的定義如下:

/*! Trait environments expose a trait collection that describes their environment. */
@protocol UITraitEnvironment <NSObject>
@property (nonatomic, readonly) UITraitCollection *traitCollection NS_AVAILABLE_IOS(8_0);

/*! To be overridden as needed to provide custom behavior when the environment's traits change. */
- (void)traitCollectionDidChange:(nullable UITraitCollection *)previousTraitCollection NS_AVAILABLE_IOS(8_0);
@end

可以看到該協議定義了一個traitCollection的屬性,還有一個用來通知traitCollection改變的方法。系統中我們可以想到的UI類都實現了這個協議,包括: UIScreen, UIWindow, UIViewController, UIPresentationController, 以及UIView.

UIContentContainer協議則定義了幾個VC級別的用來響應TraitCollection變化的方法,UIViewControllerUIPresentationController都實現了該協議。通過該協議定義的方法我們可以在Size Class變化的時候做一些動畫。

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator NS_AVAILABLE_IOS(8_0);

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator NS_AVAILABLE_IOS(8_0);

UITraitCollection類則定義了一些屬性用來描述裝置特性,如下所示:

    horizontalSizeClass
    verticalSizeClass
    displayScale
    userInterfaceIdiom
    forceTouchCapability

到這兒我們終於看見了SizeClass的身影了,而Size Class的定義如下:

typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) {
    UIUserInterfaceSizeClassUnspecified = 0,
    UIUserInterfaceSizeClassCompact     = 1,
    UIUserInterfaceSizeClassRegular     = 2,
} NS_ENUM_AVAILABLE_IOS(8_0);

iOS 系統把UI的顯示模式抽象為三種:Unspecified(對應StoryBoard中的any),CompactRegular

非多工分屏下,常見裝置的Size Class如下:

可以看出在非多工分屏模式下,iPad 無論在橫屏和豎屏下寬,高都是Regular。
進入多工分屏模式以後Size Class如下:

搞懂了Size Class的知識以後,我們再看看Size Class變換的時候我們可以做些什麼呢?

  • 改變SubViews的尺寸和位置
  • 新增或者移除subView
  • 新增,移除或者修改約束(注:約束只能修改constant)
  • 改變UILabel,TextField,Text view等的字型大小


五. Demo

我寫了一個適陪Spilt View多工分屏的Demo,地址:SpiltViewDemo,App的內容很簡單,介面上只包含兩個元素一個ImageView用來展示圖片,一個UITextView用來展示文字描述。
截圖如下:

在Regular模式下圖文結構為左右結構,當進入到Compact模式時切換為上下結構。

核心的程式碼如下:

#pragma mark -
#pragma mark Size Class Related

- (void)updateConstraintsForSizeClass:(UIUserInterfaceSizeClass)newSizeClass
{
    NSArray *currentConstraints = [self constraintsForSizeClass:self.traitCollection.horizontalSizeClass];
    NSArray *newConstraints = [self constraintsForSizeClass:newSizeClass];
    [self.view removeConstraints:currentConstraints];
    [self.view addConstraints:newConstraints];
    
    if (newSizeClass == UIUserInterfaceSizeClassRegular) {
        _imageIV.image = [UIImage imageNamed:@"aodi.jpg"];
        _textView.font = [UIFont systemFontOfSize:24];
        _textView.text = _aodiDes;
    }
    else {
        _imageIV.image = [UIImage imageNamed:@"aotuo.jpg"];
        _textView.font = [UIFont systemFontOfSize:16];
        _textView.text = _aodiDes;
    }
    
    [self.view updateConstraintsIfNeeded];
}

- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
        
    [self updateConstraintsForSizeClass:newCollection.horizontalSizeClass];
    
}

可以看到只是簡單的在willTransitionToTraitCollection:withTransitionCoordinator:方法中檢測Size Class的變化,然後跟新約束系統即可。

當然實際應用在適配Spilt View的時候工作量可能遠比這個多,但是原理都是一樣的。

實際應用適配Spilt View除了技術上的支援,我想更多地是互動和視覺的調整,在多工模式切換的時候如何更合理的調整元素的擺放位置以及互動的方式。


六. 總結 & 思考

1. 向下相容

Size Class是iOS 8才引入的概念,如果你也像我一樣使用手寫的方式寫介面,且你的應用還需要支援iOS 7,那麼需要小心行事。

如果你的應用使用Storyboard的方式使用Size Class,那麼恭喜你只要你符合以下幾點要求,那麼系統會自動幫你做向下相容。

  • 使用Xcode6及以後編譯
  • 豎直方向的Size Class不是Compact模式

Important: Compatibility occurs at build time, not at run time.

2. 關於效能

在最後還是要提一下App的效能,作為一個iOS上的好公民,我們需要多花一些精力來做App的效能調優,用好Profile工具,仔細查詢並優化App在CPU,GPU,memory,I/O等方面的佔用。

3. 及時跟進iOS的新技術

及時跟進iOS的新技術,這樣在出現新特性的時候才能快速方便的接入。仔細想想從iOS 6的Auto Layout, iOS8 的Size Class,再到iOS 9推出的Spilt View,整個發展的遞進式的。

4. 參考資料

Adopting Multitasking Enhancements on iPad
Size Classes Design Help
Strategies for Handling App State Transitions
UITraitEnvironment
UIContentContainer
UITraitCollection
Building Adaptive Apps with UIKit


注:smileEvday保留本文的一切權利,轉載請著名原文出處
  本文所有內容僅代表個人觀點,如有有不對的地方,歡迎指出。


相關文章