[Cordova] 移動App 的 ios11 和 iPhoneX 適配
Apple每次退出新尺寸的iphone都會掀起一番適配風波,這次沒有下巴但有劉海的iPhoneX更是如此,網傳橫屏下的適配動畫更是令不少人汗顏。
其實對於Native App來說,適配並不算困難(當然追求酷炫效果另算),官方文件有詳細的說明,而對於Web App來說,主要還是依靠開啟webview的Native App來適配,而這篇文章主要討論的是Cordova App要如何適配iPhoneX.
沒有適配的效果:
1、以前上架的應用,目標編譯平臺ios10,上面的劉海和下面的橫杆自動被黑邊覆蓋
2、目標平臺ios11,重新編譯的,上下除了黑邊還有白邊
適配步驟如下
更新cordova外掛
每次大版本更新和適配,首先都是更新 Cordova 建立,確認使用的 cordova 外掛有是否有新的改進, 例如 cordova-plugin-splashscreen, cordova-plugin-statusbar 等
更新 HTML viewport meta
meta 標籤中 新增 viewport-fit=cover,這是 ios 11 新增的設定,可以讓頁面全屏展示。
<meta name="viewport" content="initial-scale=1, width=device-width, height=device-height, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover" >
不過我測試的結果和上圖一樣,仍然有黑邊有白邊
更新啟動圖
<platform name="ios">
<splash src="res/screen/ios/[email protected]~universal~anyany.png" />
</platform>
效果如下:
全屏是全屏了,但是還是有白邊
更換舊的 UIWebview 為 WKWebview
更換 webview 只要新增 cordova-plugin-wkwebview-engine 外掛
更換完,還是有點問題,底部有空白,頁面高度不是100%。但是如果換成
於是,參考 cordova-plugin-ionic-webview
修改 cordova-plugin-wkwebview-engine\src\ios\CDVWKWebViewEngine.m
,新增一點程式碼:
// re-create WKWebView, since we need to update configuration
WKWebView* wkWebView = [[WKWebView alloc] initWithFrame:self.engineWebView.frame configuration:configuration];
//------------added begin-------------
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
if (@available(iOS 11.0, *)) {
[wkWebView.scrollView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
}
#endif
//------------added end----------------
wkWebView.UIDelegate = self.uiDelegate;
self.engineWebView = wkWebView;
然後就可以全屏了
Ajax 請求全部失敗了
更換完 WKWebview,發現所有的請求都失敗了。
這是因為 WKWebview 的安全機制:
1、file://
協議下(app內頁面index.html是 file://
協議瀏覽的),訪問 http/https資源是屬於跨域,服務端必須實現CORS 跨域支援才可以。
可是我已經吧服務端實現過CORS了啊,為啥不行?原來是CORS實現不完整,預檢請求(Options)沒通過
本人公司因為某些原因,改服務端的辦法不可取。
2、即使服務端實現了跨域,應用發出的請求頭“Origin”是空值。
所幸的是,Oracle 開發了一個 Cordova 外掛 cordova-plugin-wkwebview-file-xhr,解決了上面提到的2個問題。
本外掛的原理是攔截所有 webview 的請求(XMLHttpRequest),用原生的方式向伺服器請求,然後把結果交給 webview。
不過外掛預設只攔截 https 請求,如果需要攔截 http 請求,需要增加配置:
<preference name="InterceptRemoteRequests" value="all" />
圖片顯示不出來了
真機上,圖片又顯示不出來了。
本來以為是圖片下載失敗,除錯發現,圖片已經下載下來了,但是 WKWebview 顯示不出已經下載好的本地圖片。
然而,模擬器確是正常的。
Google 了一大圈,發現如果圖片檔案存在的是 Tmp 目錄下,也就是 file:///var/mobile/Applications/Container/Data/<GUID of app>/tmp/
目錄,則真機上可以顯示圖片,其他目錄就不行。
那就換成 Tmp 目錄。
注:Cordova 可以使用 cordova.file.tempDirectory
這個常量來得到 Tmp 目錄的具體路徑。
“劉海”適配
Safe-Area,安全區域是 iPhone X 畸形全面屏的產物。雖說是全面屏,但是頂部多了感測器(劉海)和底部 home 鍵橫線,為了讓你的應用在 iPhone X 上 面完美執行,你需要將可視元素擴充套件至填充整個展示視窗(螢幕)上,同時,你也需要保證如按鈕、tab bar 等可互動控制元件,以及一些至關重要的資訊不會因為螢幕的圓角而被裁掉,或者被手機的「劉海」和虛擬「Home鍵」遮住。
利用 蘋果提供的 CSS 常量 env(safe-area-inset-*)
,可以將重要的介面元素避開劉海、圓角和橫線。
Safe-Area 的適配,詳細方法自己百度。
最終結果如下:
其他要注意的
如果用的是 Sencha Touch
框架會對 index.html 頁面新增2次 <meta name="viewport">
所以我們無需在 index.html
中手動增加 viewport-fit="cover"
,而是在框架程式碼中的一處加上 viewport-fit="cover"
,並且我們需要刪掉框架程式碼中的一處 ,否則 env(safe-area-inset-*)
這些 CSS 常量無效(即都是0畫素)。
- 要刪除的地方:
檔案touch\src\core\Ext-more.js
,刪除下面的內容:
/*if (navigator.standalone) {
addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
}
else {
addMeta('viewport', 'initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, minimal-ui');
}
addMeta('apple-mobile-web-app-capable', 'yes');
addMeta('apple-touch-fullscreen', 'yes');*/
不嫌麻煩,可以把 touch 下 sencha-touch.js、sencha-touch-all.js、sencha-touch-all-debug.js、sencha-touch-debug.js 四個檔案也改一下
- 要修改的地方:
找到檔案MyApp/.sencha/app/microloader/production.js
,找到
addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no');
改為
addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover');
不嫌麻煩,可以把 microloader 下 development.js、testing.js 也改一下
如果是 ExtJS6 Modern
因為 sencha app build native/ios
構建(打包)的結果裡面有個 ios.json
,這個是 應用的清單檔案(裡面包含了要載入的js和css檔案列表),如下圖:
app在啟動時 index.html
內的引導程式碼,會先 ajax(XMLHttpRequest) 請求 ios.json
,而後根據清單內容才載入 app.js
和 app.css
等檔案。
但是,因為 WKWebview 的安全機制, index.html
無法請求 file:///..../www/ios.json
,指令碼錯誤如下:
XMLHttpRequest cannot load file 'file:///..../www/ios.json'
我們之前不是加了 cordova-plugin-wkwebview-file-xhr
外掛,攔截 webview 請求,利用原生方式請求了嗎?為什麼還不行呢?這是因為 cordova.js
在 ios.json
清單檔案中,也就是說 index.html
在載入 ios.json
的時候,cordova 外掛還沒有生效。
那麼,解決辦法就是,設法先載入 cordova.js
,等外掛生效後,再來載入 ios.json
以及後面的 app.js
、app.css
。
修改步驟:
1) 在 index.html 的 <head></head>
中加入
<script type="text/javascript" src="cordova.js"></script>
注意放在 <script id="microloader" data-app="...." type="text/javascript" src="bootstrap.js"></script>
之前
如下:
2) 將 cordova.js
從最終 build 後的 ios.json
清單檔案中移出
修改 MyApp\.sencha\app\app.defaults.json
把這段去掉:
注:如果是 phonegap,應該刪除這裡
這樣,build 之後的 ios.json
中就沒有 cordova.js
了。
3) 等 cordova 外掛生效後,才允許 index.html
載入 ios.json
修改 MyApp\.sencha\app\Boot.js
,將原來的 fetch
函式改名為 _fetch
,然後新加一個 fetch
函式:
// 新加一個 `fetch` 函式
fetch: function() {
var me = this,
args = arguments;
if(Ext.platformTags.cordova) { // 如果是 cordova
document.addEventListener('deviceready', function() { // 等 cordova 外掛生效後,才載入
me._fetch.apply(me, args);
}, false);
}
else {
me._fetch.apply(me, args);
}
},
// 原來的 `fetch` 函式改名為 `_fetch`
_fetch: function(url, complete, scope, async) {
async = (async === undefined) ? !!complete : async;
var xhr = new XMLHttpRequest(),
result, status, content, exception = false,
readyStateChange = function () {
if (xhr && xhr.readyState == 4) {
status = (xhr.status === 1223) ? 204 :
如下圖: