一、API篇
1.接口无法访问
百度地图、自定义API无法访问,远程调试结果显示404
原因是cordova 5.x的版本增加了“Content-Security-Policy”用于解决安全访问的问题。默认情况下,只能访问本机资源。
解决方法:
1.添加白名单插件,在项目目录下执行
ionic plugin add cordova-plugin-whitelist
2.在index.html头部增加
<meta http-equiv="Content-Security-Policy"
content="script-src * 'unsafe-eval'; connect-src * 'unsafe-eval';
object-src 'self'; style-src * 'unsafe-inline'; img-src *" >
2.无法跨域访问
服务端设置(PHP)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Credentials:true');
3.POST请求提交自动变成Options请求
当我们使用浏览器调试的时候,发现POST请求会自动变成Options请求,然后调用任意接口都提示不能跨域访问,即使服务端已经设置允许跨域访问。
解决方法:
.config(function ( $stateProvider, $urlRouterProvider, $ionicConfigProvider, $httpProvider) {
// Use x-www-form-urlencoded Content-Type
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
}
这个问题只出现在浏览器调试中,实际在手机里运行不需要这个配置。
4. ionic 里使用 iframe 可能遇到的问题
无法访问外部url的问题–两个步骤解决:
iframe的src属性用ng-src属性替代,并指明绑定对象: ng-src=”{{targetUrl}}”
在controller里,调用sce:sce:scope.targetUrl = $sce.trustAsResourceUrl(url)
高度无法最大化的问题–两个步骤解决:
ion-content 属性里添加 scroll=”true” overflow-scroll=”true”,参考
iframe content scroll issue #1151
iframe的style里添加 min-height: 100%,参考
Fill content container
Ionic与第三方的Wap网页交互
采用Windows.Open的方法,打开一个浏览器,不带地址栏,全屏的窗口,显示第三方的WebApp页面。
代码如下:
// event类型有以下几种类型
// loadstart - 开始加载
// loadstop - 完成加载
// loaderror - 加载出错
// exit - 窗口退出
var ref = window.open(encodeURI(url), '_blank','location=no');
ref.addEventListener('loadstart', function(event) {
if (event.url.match("mobile/close")) {
ref.close();
}
});
当浏览器内的url地址包含 mobile/close页面的时候,就会关闭当前的浏览器。
具体可以参考
http://cordova.apache.org/docs/en/3.0.0/cordova/inappbrowser/inappbrowser.html
二、调试篇
1.本地调试
电脑上本地调试,用ionic serve即可在浏览器中调试
2.远程调试
1.在手机上运行debug版软件,在电脑上调试程序
2.在启动手机上的APP后,在谷歌浏览器(其实360也行)上输入chrome://inspect/#devices,可以进入调试界面(如果出不来,请翻墙)
3.单击inspect,可以进入当前显示的页面调试,调试方法和和在浏览器上一致。
远程调试可以快速定位,在浏览器上没有发现的问题,方便调试手机API接口
三、应用篇
1.导航置底设置
好不容易按教程一步步建立了tabs样例工程,却发现安卓机上这个tab导航在顶部,浏览器和ios这个导航在顶部。
解决方案:
在app.js里添加以下代码
.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.platform.ios.tabs.style('standard');
$ionicConfigProvider.platform.ios.tabs.position('bottom');
$ionicConfigProvider.platform.android.tabs.style('standard');
$ionicConfigProvider.platform.android.tabs.position('bottom');
$ionicConfigProvider.platform.ios.navBar.alignTitle('center');
$ionicConfigProvider.platform.android.navBar.alignTitle('center');
$ionicConfigProvider.platform.ios.backButton.previousTitleText('').icon('ion-ios-arrow-thin-left');
$ionicConfigProvider.platform.android.backButton.previousTitleText('').icon('ion-android-arrow-back');
$ionicConfigProvider.platform.ios.views.transition('ios');
$ionicConfigProvider.platform.android.views.transition('android');
2.百度地图开发
坑1:使用了插件angular-BMap
使用了插件angular-BMap
这个插件功能并不完善,好多功能都没有,如果要使用需要继续开发(如果你有时间,有兴趣,有能力可以fork后继续开发)
坑2:使用了百度地图实例代码,地图不显示
var map = new BMap.Map("container"); // 创建地图实例
var point = new BMap.Point(116.404, 39.915); // 创建点坐标
map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和地图级别
其实只是在模拟手机的浏览器下不显示,在浏览器上不要选择任何手机型号,就能显示,而实际我在自己的手机上build后,也是能正常显示的。具体原因没有深究,如果有知道的朋友欢迎来信。
坑3:GPS位置偏移
使用$cordovaGeolocation.getCurrentPosition()获得的坐标,在百度地图上位置偏移。原因是GPS坐标和百度地图坐标并不是完全对应的,需要使用百度地图提供的GPS坐标转换接口进行转换
function posToAddrByBaidu(lat, long){
var Ak = 'yourAK'; //你应用的AK
var getUrl = 'http://api.map.baidu.com/geocoder/v2/?'+ 'ak=' + Ak +'&location='
+lat+ ',' + long + '&output=json&pois=0';
$http.get(getUrl)
.success(function (data) {
if (data['status'] == '0') {
$scope.appeal.location = data.result.formatted_address;
} else {
return '定位失败';
}
}).error(function () {
alert("网络问题");
});
}
坑4 infowindow里面的a链接只能跳转一次
BMap中创建的infowindow,如果里面带有a链接,第一次跳转后,第二次进去就无法跳转了。这个问题目前不知道怎么解决,如果有人知道请告诉我,谢谢。
四、常见错误
1. 编译错误
a. Execution failed for task ‘:app:mergeDebugResources’ OR Execution failed for task ‘:Application:processDebugResources’
> com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'C:\Users\admin\AppData\Local\Android\sdk\build-tools\23.0.3\aapt.exe'' finished with non-zero exit value 1
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
Error: Error code 1 for command: cmd with args: /s,/c,"D:\myApp\platforms\android\gradlew cdvBuildDebug -b D:\myApp\platforms\android\build.gradle -Dorg.gradle.daemon=true -Pandroid.useDeprecatedNdk=true"
检查一下,看看是不是哪里的资源文件命名不规范,文件名称不规范就可能导致该问题.
或者执行以下命令:
C:\>D:\myApp\platforms\android\gradlew cdvBuildDebug -b D:\myApp\platforms\android\build.gradle -D org.gradle.daemon=true -Pandroid.useDeprecatedNdk=true" --info --debug > d:\debug.info
然后从d:\debug.info文件中查找详细的错误信息。
2.ionic build android 没有反应
检首次Build的时候 android平台,会安装gradle工具,但是因为网络的问题,很可能安装的非常的慢到无法忍受。那么很有可能是因为你的Gradle的工具会去下载platforms/android/cordova和CordovaLib的maven的库,这个时候需要修改build.gradle文件的repositories库中,mavenCentral()修改成为阿里云的库
repositories {
flatDir {
dirs 'libs'
}
//mavenCentral()
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
}
常识与技巧篇
- list 有延迟,可以在ion-content处使用 overflow-scroll=”true”尝试
- 在上用ng-click上是没效果的
- 标签内的事件会在整个label内被触发,点哪都触发
- 快捷修改背景色style=”background-color: #212326;”
- 能用ng-if就用ng-if,ng-if的效率比ng-show和ng-hide高
- 直接在ion-list中的ion-item中并不能触发ng-click事件,可以在item中的元素上再套一层div
- 可以用ng-class=”{‘important’: post.important}”配合css 根据列表元素显示不同的效果
- 获取日期用filter,varpostdate=filter,varpostdate=filter(‘date’)(date, ‘yyyy-MM-dd HH:mm:ss’);
- 列表中的元素不能写成 id : 4,应写成 id : “4”,注意在创建id变量的时候也需要转成string,如:
var id = InfoListService.getListLength()+1+"";
- 使用log进行log输出,为什么用log进行log输出,为什么用log而不是console.log呢?可以看看这个
- 在安卓上的体验比较差,动画有延迟?可以试试ionic集成的crosswalk
- controllers和services 的文件名可能会重合,但是他们意义差不多,可以将controllers中的文件名小写,对应的services中的文件名大写进行区分,或者加后缀xxxControler,xxxService
- 安装cordova插件的时候用ionic plugin add …的方式添加,这样会在package.json中添加这个插件的条目,如果有人clone了你的项目想在本地运行,可以用ionic state restore它会根据cordovaPlugins条目安装对应的插件。如果直接用cordova plugin add 安装则不会更新package.json。
- 上传base64编码的时候如果提示413错误,是因为文件过大导致的,可以在nodejs中设置bodyparser的文件限制:
var bodyParser = require('body-parser');
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
- img 中 base64编码的图片无法显示?在源码中发现angular添加了unsafe标签?需要在白名单中添加data:image
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|content|file|assets-library):|data:image\//);
- 有时候pm2运行有问题,重启一下即可
Q&A篇
Q: 在 iOS 下使用 cordova-plugin-file-transfer 下载中文名文件失败,提示 Could not create target file
A: encodeURI(“包含霸气的中文文件名的 URI”)
Q: 应用需要存储较大量数据,原始格式是 json ,存 sqlite 数据库嫌麻烦。
A: lokiJS ,类 mongodb 的 js 内存数据库,配合为 ionic 打造的插件做持久化存储。
Q: 不同 Android 手机上出现字体错位之类的奇怪问题。
A: 使用 Crosswalk 消除不同安卓机上 WebView 的差别,顺便还能提升应用性能。
Q: 在实机上使用 livereload 功能时出现空白、连接失败等情况。
A: 实机上的 livereload 本质是用手机访问电脑上的网站,检查手机和电脑之间的网络连接是否通畅。
Q: gitignore 默认排除了 plugins 文件夹,团队其他人 clone 了项目后缺少插件,一个一个装太麻烦。
A: ionic platform add/remove xxx 以及 ionic plugin add/remove xxx
的时候,Ionic CLI 都在 package.json 中保存了项目的状态。clone 完后可以使用 ionic state restore
命令快速恢复
Q: 在哪里查看 Ionic 带的所有图标?
A: http://ionicons.com
Q: 在 ionic platform add xxx
时卡住
A: 挂 VPN ,或者丢着睡一觉(不确定是不是网络原因,就遇过两次没深究)
Q: tel:xxxxx sms:xxxxxx mailto:xxxxxx geo:xxxxxx 一类的链接不能唤起其他应用。
A: 在 config.xml 中加入:
<access origin="*"/>
<access origin="tel:*" launch-external="yes"/>
<access origin="sms:*" launch-external="yes"/>
<access origin="mailto:*" launch-external="yes"/>
<access origin="geo:*" launch-external="yes"/>
Q: 跟上 Q 一样,加了还 TM 不行!
A: 再在 config.xml 中加入:
<allow-intent href="tel:*"/>
<allow-intent href="sms:*"/>
<allow-intent href="mailto:*"/>
<allow-intent href="geo:*"/>
Q: Android 中调用其他应用打开 applicationDirectory 下的文件时提示路径不存在, iOS 可以。
A: 两个系统策略不一样, Android 中有这个需求简单的办法是参考该页中的 Android 文件系统布局,把文件从 Private 目录复制到 Public 目录下再做操作。
Q: 对 Android 进行远程调试。
A: 打开 Chrome ,地址栏输入 chrome://inspect
Q: 对 iOS 进行远程调试
A: 打开 Safari -> 开发 -> 手机名 -> 应用名
Q: Ionic 的 Modal 是什么鬼?不能给它设定状态么?!
A: 超级弱逼的模态框,因为 uirouter 的限制,给它转状态非常不方便。确定只需要一个页面就能完成的操作才用他。下一 Q 提供个解决办法。
Q: 替代 Modal 的方案
A: 在 $state.go 前记录下当前的 view ,然后禁止下一个 view 记录 backView ,就不会显示后退按钮( Android 硬件后退也不行 )。在需要关闭时,后来加入导航栈的任意 view 中设置 backView 为记录下来的 view ,然后 back 。
// go 的时候
var backHistoryId = $ionicHistory.currentHistoryId();
var backViewId = $ionicHistory.currentView().viewId;
$ionicHistory.nextViewOptions({
disableBack: true,
disableAnimate: true
});
$state.go('my-awesome-modal', {backViewId: backViewId});
// back 的时候
var backHistoryId = $ionicHistory.currentHistoryId();
var backView = $ionicHistory.viewHistory().histories[backHistoryId].stack.filter(function (v) {
return v.stateId === $stateParams.backViewId;
})[0];
$ionicHistory.backView(backView);
$ionicHistory.goBack();
Q: ionic serve 或在实机调试时开启了 livereload 功能时的跨域问题
A:道理还是因为这两种状态下, APP 实际是在访问电脑上的一个网站,任何指向其他地方的链接都是跨域。实机不开 livereload 则不存在这个问题。
简单的方法就是用实机调试且不开 livereload 。
复杂点的比如设置 Ionic 自带的代理服务器,参考链接。需要详细了解这个问题也可以看一遍。
Q: 如何在某个界面中去掉导航栏?
A: 如果某个界面上不想要导航栏,可以简单地在最顶端的标签中添加hide-nav-bar=”true”
如何在ionic中加载本地图片?
A: 对于css文件夹中的样式文件中如果要调用本地的图片的话,从该css文件所在的文件夹开始算,例如www/css/style.css要加../,否则在浏览器中可以正常显示,在设备上不行,结构如下所示:
.login-page {
background:url(../img/signup_bg.png);
background-size: cover;
background-repeat: no-repeat;
}
但是对于在页面中定义的图片路径,从www路径开始算,否则浏览器中可显示,但设备上不行,img文件夹和index.html在一级,如:
<img src="img/commander.jpg">
Q: 如何在ionic中嵌入网页代码?
A: 使用ng-bind-html这个类,不过它会过滤原始html的标签,我们可以引入sce模块,用sce模块,用sce.trustAsHtml()方法信任我们获取的网页
Q: 如何将template加载到某个tab或某个sidemenu项目下?
A:<ion-nav-view name="menuContent">
可以指定name,然后在子状态中使用该name,ionic就知道该把该状态的template渲染到哪边了。例如:
// signup page
.state('auth.signup', {
url: '/signup',
views: {
'auth-signup': {
templateUrl: 'templates/auth-signup.html',
controller: 'SignUpCtrl'
}
}
})
另有一个tabs中声明该auth-signup:
<ion-tab title="Sign Up" icon-on="ion-ios-personadd"
icon-off="ion-ios-personadd-outline" href="#/auth/signup">
<ion-nav-view name="auth-signup"></ion-nav-view>
</ion-tab>
Q: 运行serve命令时ionic报错?
A:
ionic $ An uncaught exception occured and has been reported to Ionic
看看你是不还有一个终端在运行着serve呢?
Q: 用docker跑ionic的时候,不能把地址绑定到0.0.0.0怎么处理?
A:可以用ionic serve -all的方法解决
Q: 加载页面的时候会看到双括号一闪而过?
A: angularjs
在使用双括号的时候,第一个加载的页面,也就是应用中的index.html,其未被渲染好的模版可能会被用户看到。用ng-bind就不会遇到这个问题。造成这种现象的原因是,浏览器需要首先加载HTML页面,渲染它,然后Angular才有机会把它解释成你期望看到的内容。不过好消息是,在大多数的模版中你依然可以使用双括号.但是对于index.html页面中的数据绑定操作,建议使用ng-bind
。 ng-bind
使用方式如下: <p ng-bind="greeting"></p>
Q: 更新了数据,如何让界面更新呢?
A: 可以用广播,注意broadcast和broadcast和emit的区别
Q: 如何实现IonicView中card上面有一列分割线的效果?
A: 在css里定义
#info-up {
border-top: 4px solid #f06336;
}
Q: controller.js和service.js文件越来越大怎么办?
A:所有的控制器不必都放在controllers.js这一个文件中,可以新建controllers文件夹,
然后把每个controller都建一个.js文件,同理services和utils等都是.但注意要在index.html中head部分声明.但是为了避免他们相互覆盖,第一个加载的js中模块中要加[…],其他都不需要。如:
// File : /js/directives/mainDirective.js
angular.module('app.directives',[]);
// File : /js/directives/myGreatDirective.js
angular.module('app.directives')
.directive('myGreatDirective', function(){
return {
//...
}
});
// File : /js/directives/myBetterDirective.js
angular.module('app.directives')
.directive('myBetterDirective', function(){
return {
//...
}
});
...
看angularjs-code-organization了解更多,嗯这篇文章写的还不是best practice,因为你还得记着自己把[]写到那个模块里了,统一地写在app.js中即可,在app.js最下面加上类似:
angular.module('fcws.controllers',['ionic', 'fcws.services']);
angular.module('fcws.services', []);
可以达到和上面一样的效果,而且可以统一管理.
Q: 如何寻找优秀的范例代码?
A:目前有些ionic 的app没有进行代码混淆,至少ionic官方的ionic view没有进行代码混淆,下载他们的app,文件名改成zip,解压,所有的 www文件都在assets文件夹中,相当于开源了有木有,看看那些最优秀的practice。看中哪些优秀的app,下下来,如何在googleplay上下载?把googleplay应用的地址贴到apps.evozi中。
Q: 如何显示相对时间?
A:如几分钟前,几天前等,可以用momentjs,看这篇教程
Q:发布应用的时候如果遇到翻译错误即MissingTranslation怎么办?
A:暂时的解决方法是,不进行翻译校正, 在 /platforms/android/build.gradle 中的android {}节中加入:
lintOptions {
disable 'MissingTranslation'
disable 'ExtraTranslation'
}
Q: 如何在列表右下方添加时间等信息?
A:span
可以用来将时间之类的附加信息显示到列表右边,如下面会将创建时间显示在name的右边:
<ion-item class="item item-avatar-left " ng-repeat="message in messages">
<img src="../../img/commander.jpg">
<span class="item-note">{{message.create_at}}</span>
<h2 >{{message.name}}</h2>
<p > {{message.content}}</p>
</ion-item >
Q:如何回到上一页面?
A:用$ionicHistory
这个模块,引入该模块后使用goBack([backCount])
,backCount指定回去多少个页面(-1代表回去一个页面),默认为-1
Q:如何关闭应用?
A:
ionic.Platform.exitApp();
Q: 在安卓设备上如何让title居中?
A: 在headerbar中添加align-title=”center”,如:
<ion-header-bar class="bar-positive" align-title="center">
<h1 class="title">{{username}}</h1>
</ion-header-bar>
不过这个设置对ion-view无效,亲测,如果要统一让所有navbar上的title居中(包括上面的headerbar),可以在config里设置,如:
.config(function($stateProvider, $urlRouterProvider,$ionicConfigProvider) {
$ionicConfigProvider.navBar.alignTitle('center');
...
如果要让某一个view title居中,可以用ionicNavBarDelegate,参考[ionic官方文档](http://ionicframework.com/docs/api/service/ionicNavBarDelegate,参考[ionic官方文档](http://ionicframework.com/docs/api/service/ionicNavBarDelegate/)
Q: 如何让在sidemenu中的headerbar能够显示头像等其他信息?
A: 解决方案是去掉headerbar,添加一个avatar到sidemenu content中,如:
<ion-side-menu side="left">
<ion-content class="bar-positive">
<ion-list>
<ion-item class="item item-avatar item-positive" href="#">
<img src="img/commander.jpg">
<h2 class=" light">
<i class="icon ion-ios-star"></i>{{title}}
</h2>
<a>{{username}}</a>
</ion-item>
Q: ionic的subheader挡住了内容区域怎么办?
A: 解决方案是给加类has-subheader,同理也可以加has-header。如下:
<ion-content class="has-header has-subheader">
Q:对于需要添加数据的list,在添加数据后页面不能及时刷新造成卡顿怎么办?
A:可以使用$ionicScrollDelegate.resize();
在添加数据后手动进行重新刷新,记得添加依赖
Q:ionic如何处理回退按钮?例如询问用户是否真的要退出应用
A:可以在app.js的.run方法中增加对硬件回退按钮的注册处理,这里我在大部分页面都想注册该事件,除去有二级历史页面的我单独判断了下,注意增加依赖。
$ionicPlatform.registerBackButtonAction(function(e) {
var current_state_name = $state.current.name;
if(current_state_name !== 'sidemenu.post'
&& current_state_name !== 'sidemenu.contact_town' &&
current_state_name !== 'sidemenu.contact_people'){
$ionicPopup.confirm({
title: '退出应用',
template: '您确定要退出xxxx吗?'
}).then(function (res) {
if (res) {
//ionic.Platform.exitApp();
navigator.app.exitApp();
} else {
console.log('You are not sure');
}
});
e.preventDefault();
return false;
}else{
navigator.app.backHistory();
}
},100);
Q:ionic如何实现对每个请求都添加认证信息或认证失败自动重新登录?
A:在应用的注册或者登录部分,不记名token响应了这个请求并且这个token被存储到本地存储中。当你向后端请求一个服务时,你需要把这个token放在头部中。你可以在app.js的.config方法中使用AngularJS的拦截器实现这个。每次请求都会被拦截并且会把认证头部和值放到头部中,同理如果服务器端响应401或403,跳转到重新登录页面.
$httpProvider.interceptors.push(function ($q, $location, User, $rootScope) {
return {
'request': function (config) {
config.headers = config.headers || {};
if (User.getToken()) {
config.headers.Authorization = 'Bearer ' + User.getToken();
}
return config;
},
'responseError': function (response) {
if (response.status === 401 || response.status === 403) {
//如果之前登陆过
if (User.getToken()) {
$rootScope.$broadcast('unAuthenticed');
}
}
return $q.reject(response);
}
};
});
Q:ionic如何实现搜索框内的全部清除按钮?
A:在label中的input不能嵌入按钮,因为ionic对于label中的tap事件会进行重定向到input上。解决方案是将label替换成span或div。如下面的搜索框,注意ng-model需要是一个对象才能置空,变量不行:
<span class="item-input-wrapper">
<i class="icon ion-ios-search placeholder-icon"></i>
<input type="search" placeholder="请输入姓名前缀" ng-model="search.key">
<i class="icon ion-close-circled placeholder-icon" style="vertical-align: middle;"
on-tap="clearSearch()" ng-if="search.key.length"></i>
</span>