По результатам опроса из первого топика продолжаю серию статей об Ionic Framework (далее IF). Сегодня поговорим о работе с камерой устройства и в дополнение рассмотрим работу с localStorage. В качестве основы возьмем приложение из прошлого топика и добавим нужную функциональность.
- Добавление ngCordova
- Подключение плагина для работы с камерой
- Создание сервиса для работы с камерой
- Редактирование шаблонов
- Добавление маршрутов, контроллеров и фабрик
- Заключение
Добавление ngCordova
Для работы установленных плагинов необходимо добавить модуль ngCordova. Ставится он просто (для начала обновим наши инструменты, ведь прошло много времени с момента публикации первой статьи...):
sudo npm i -g cordova ionic bower
bower install ngCordova
Далее ngCordova подключается в index.html:
<script src="lib/ngCordova/dist/ng-cordova.min.js"></script>
и в зависимости приложения (app.js):
angular.module('starter', ['ionic', 'ngCordova', 'starter.controllers', 'starter.services'])
иначе паровозик не сможет… Желательно также подключить еще пару плагинов для работы с ФС устройства:
ionic plugin add cordova-plugin-file
ionic plugin add cordova-plugin-file-transfer
По идее можно было бы подключить только file-transfer, т.к. вместе с собой он также тянет и file, но иногда такой способ чреват появлением непонятных багов…
Подключение плагина для работы с камерой
Для того чтобы IF смог получить доступ к камере устройства нужно добавить в проект Cordova плагин реализующий этот функционал:
ionic plugin add cordova-plugin-camera
Плагин предоставляет единственный метод:
navigator.camera.getPicture(SuccessCallback, ErrorCallback, Options);
Где SuccessCallback и ErrorCallback это наши функции-обработчики в которых мы вольны писать все что вздумается, а вот объект Options гораздо интереснее для рассмотрения:
- quality (Number): Качество сохраняемого изображения, в интервале от 0 до 100, где 100 — режим без сжатия. По умолчанию значение 50.
- destinationType(Number): Формат возвращаемого значения. По умолчанию FILEURI. Настройка определяется типами navigator.camera.DestinationType:
Camera.DestinationType = { DATA_URL : 0, // Строка в формате base64 FILE_URI : 1, // URI файла NATIVE_URI : 2 // URI пути зависящий от платформы (assets-library:// для iOS и content:// для Android) };
- sourceType (Number): Источник изображения. По умолчанию CAMERA. Настройка определяется типами navigator.camera.PictureSourceType:
Camera.PictureSourceType = { PHOTOLIBRARY : 0, CAMERA : 1, SAVEDPHOTOALBUM : 2 };
- allowEdit (Boolean): Включает режим простого редактирования перед передачей в функцию.
- encodingType (Number): Формат файла. По умолчанию «у вас JPEG». Настройка определяется типами navigator.camera.EncodingType:
Camera.EncodingType = { JPEG : 0, // JPEG PNG : 1 // PNG };
- targetWidth (Number): Ширина. Без комментариев. Для масштабирования. Лучше использовать в связке с targetHeight с коэффициентом.
- targetHeight (Number): Высота.
- mediaType (Number): Тип мультимедиа контента. По умолчанию PICTURE. Работает при PictureSourceType равным PHOTOLIBRARY или SAVEDPHOTOALBUM. Настройка определяется типами nagivator.camera.MediaType:
Camera.MediaType = { PICTURE: 0, // Только изображения. VIDEO: 1, // Только видео (ВСЕГДА ВОЗВРАЩАЕТ FILE_URI) ALLMEDIA : 2 // Все типы };
- correctOrientation (Boolean): Поворот изображения для компенсации ориентации устройства во время съемки.
- saveToPhotoAlbum (Boolean): Сохранение в альбом после съемки.
- popoverOptions: iOS-only параметр определяющий местонахождение popover на iPad. Настройка определяется в CameraPopoverOptions.
- cameraDirection (Number): Камера для использования (фронтальная или задняя). По умолчанию BACK. Настройка определяется типами navigator.camera.Direction:
Camera.Direction = { BACK : 0, // Use the back-facing camera FRONT : 1 // Use the front-facing camera };
Вот в принципе и все что можно рассказать…
Создание сервиса для работы с камерой
Давайте для нашего удобства сделаем службу чтобы работа с камерой была комфортной. Для этого предлагаю реализовать фабрику с объектом promise который будет обрабатывать события камеры с помощью наших callback'ов. Откройте файл services.js (в прошлом топике мы уже сделали там фабрику городов) и приведите его к такому виду:
angular.module('starter.services', [])
.factory('Cities', function() {
// ...
}).factory('Cam', function($q) {
return {
getPic: function(opt) {
var q = $q.defer();
navigator.camera.getPicture(function(res) {
q.resolve(res);
}, function(err) {
q.reject(err);
}, opt);
return q.promise;
}
};
});
Итак, в этой настройке мы, используя Angular'овский $q.defer(), возвращаем promise, которому впоследствии передадим обработчики событий с камеры (SuccessCallback, ErrorCallback) и объект настройки (Options). Не закрывайте файл, мы к нему еще вернемся!
Редактирование шаблонов
Откройте файл tabs.html и добавьте еще один tab. У меня получился такой:
<ion-tab title="Options" icon-off="ion-gear-a" icon-on="ion-gear-a" href="#/tab/opt">
<ion-nav-view name="tab-opt"></ion-nav-view>
</ion-tab>
Теперь в папке с шаблонами создадим еще один шаблон (например tab-opt.html) и сделаем небольшую форму настройки пользователя приложения, например такую:
<ion-view view-title="Настройки">
<ion-content class="padding">
<div class="list-item">
<img class="full-image" ng-src="{{user.ava || './img/ionic.png'}}">
<label class="item item-input">
<i class="icon ion-person placeholder-icon"></i>
<input type="text" placeholder="Имя пользователя" ng-model="user.name" ng-keyup="setUser();" />
</label>
<label class="item item-input item-select">
<div class="input-label">
Город
</div>
<select ng-model="user.city" ng-change="setUser();">
<option ng-repeat="city in cities" ng-value="city.id">{{city.name}}</option>
</select>
</label>
<button class="button button-full button-positive" ng-click="getPic(1);">
С камеры
</button>
<button class="button button-full button-calm" ng-click="getPic(0);">
Из библиотеки
</button>
</div>
</ion-content>
</ion-view>
Как видно, к полям мы привязали модель пользователя и при изменении значения поля html, в модели будет изменяться соответствующее поле объекта. Так же на события когда клавиша отпущена (кстати, тут можно использовать debounce) и на изменение выбора города, была сделана привязка обработчика setUser, это все мы опишем на следующем шаге. Не терпится запустить и посмотреть? Но мы еще не настроили маршрутизацию, контроллеры и работу с localStorage, так что придется еще немного подождать…
Добавление маршрутов, контроллеров и фабрик
Займемся для начала маршрутами. Откройте app.js и добавьте маршрутизацию для нашего нового таба:
.state('tab.opt', {
url: '/opt',
views: {
'tab-opt': {
templateUrl: 'templates/tab-opt.html',
controller: 'OptCtrl'
}
}
})
Отлично, теперь перейдем в controllers.js и добавим нужный контроллер:
.controller('OptCtrl', function($scope, $ionicPopup, Cities, Cam, LS) {
$scope.cities = Cities.all();
$scope.user = LS.getIt('user') || {};
$scope.showAlert = function(title, text) {
$ionicPopup.alert({
title: title,
template: text
});
};
$scope.setUser = function() {
LS.setIt('user', $scope.user);
};
$scope.getPic = function(source) {
var opt = {
sourceType: source
};
Cam.getPic(opt).then(function(res) {
$scope.user.ava = res;
$scope.setUser();
}, function(err) {
$scope.showAlert("Ошибка", err);
});
}
})
Опа, что за LS в зависимостях контроллера!? Ничего не бойтесь, это всего лишь метод для работы с localStorage, и настало время его реализовать (services.js):
.factory('LS', function() {
return {
getIt: function(item) {
var it = false;
try {
it = JSON.parse(localStorage.getItem(item));
return it;
} catch(e) {
console.err(e);
return it;
}
},
setIt: function(item, obj) {
try {
localStorage.setItem(item, JSON.stringify(obj));
return true;
} catch(e) {
console.err(e);
return false;
}
}
};
})
И надо бы добавить еще городов, добавьте свой:
{
id: 555312,
name: 'Иваново',
desc: 'Город невест',
emblem: 'https://upload.wikimedia.org/wikipedia/commons/8/8f/Coat_of_Arms_of_Ivanovo_oblast.png'
}
Вы сейчас наверное думаете «к чему столько телодвижений?». А представьте себе, что пользователь настроил город (автоматически сохранив в localStorage) и при повторном запуске приложения его будет сразу перекидывать на погоду в родном городе, круто же! Финальный штрих, изменим два наших контроллера, написанных в прошлой статье:
.controller('CityCtrl', function($scope, Cities, LS) {
$scope.cities = Cities.all();
$scope.user = LS.getIt('user') || {};
if ($scope.user.city) { window.location.href = '#/tab/city/'+$scope.user.city; }
})
.controller('CityDetailCtrl', function($scope, $http, $stateParams, $ionicPopup, $ionicLoading) {
$scope.data = {};
$scope.id = $stateParams.id;
$scope.showAlert = function(title, text) {
$ionicPopup.alert({
title: title,
template: text
});
};
$scope.refresh = function() {
$ionicLoading.show();
$http.get('http://api.openweathermap.org/data/2.5/forecast/daily?id='+$scope.id+'&appid=2de143494c0b295cca9337e1e96b00e0')
.success(function(data, status, headers, config) {
$scope.data = data;
$ionicLoading.hide();
$scope.$broadcast('scroll.refreshComplete');
})
.error(function(data, status, headers, config) {
$ionicLoading.hide();
$scope.showAlert(status, data);
$scope.$broadcast('scroll.refreshComplete');
});
};
$scope.refresh();
})
Мы добавили условие, чтобы пользователя перекидывало на настроенный город, а также незаметно указали в зависимостях компонент $ionicLoading для показа окна ожидания во время запроса. Надеюсь ничего не пропустил, все готово, вы знаете что делать (ionic serve --lab, если забыли):
Заключение
В принципе на текущий момент работать с помощью IF становится всё удобнее и проще, поэтому я смело могу сказать что это один из лучших фреймворков для прототипирования, и даже для production приложений. Дерзайте! В следующей статье хочу описать создание чата с помощью socket.io и работу с ещё кое-чем интересным. Если у вас появились трудности, вопросы или предложения для следующих статей — милости прошу в ЛС!
Комментарии (20)
Blumfontein
18.11.2015 08:08+1Полгода назад пробовали писать приложение на IF. Получилось не очень. Лаги андроида даже с Crosswalk не побороли. На iOS заметно подлагивали переходы между $state (хотя тут мы грешим на частое использование SVG) и не работали пуши в iOS8. На WinPhone и вовсе было слайдшоу. Все-таки Ангуляр тяжеловат для мобил.
m0sk1t
18.11.2015 09:58Лаги и баги, к сожалению — вечные спутники гибридных приложений… Поэтому я и акцентировал внимание на то что IF прекрасно подойдёт для прототипирования)
torrie
18.11.2015 10:50+1Последние версии IF на последних мобилках ведут себя очень вкусно. Но стоит пересесть на тот же iphone 4 как всё вдруг становится грустным.
IF привлекает в первую очередь скоростью и простотой разработки, а также боевым комплектом готового стафа(+поддержка cordova конечно же).
Я бы предложил вместо IF посмотреть в сторону несколько более сложных фреймворков, которые делают нативный гибрид. Например, nativescript и его аналоги.
Конечно, на базе таких представителей не сделать приложение с суперкрутым дизайном, но для типовых проектов/прототипов/первых версий пойдет на ура.
john_samilin
А чем он лучше, скажем, Titanium?
m0sk1t
Не знаю, так и не удалось запустить Titanium с полгода-год назад…
john_samilin
Я вам скажу чем хуже. Прежде всего, он не умеет работать с интентами, сервисами, создавать активити, меню, а так же не знает ничего о системных единицах измерений (dp, dpi, sp)
m0sk1t
Знайте что я, как и любой другой пользователь хабра, с удовольствием бы прочитал введение в Titanium аналогичное моему по IF. Не затруднит ли вас написать tutorial? Интересно узнать как JS общается с Native элементами!
P.S. Прочитав тут 30 статей про Titanium я, к сожалению, увидел много боли в постах и комментариях… Например тут
john_samilin
Конечно не затруднит, я как раз систематизирую свои знания по нему, однако не могу обнадежить вас по поводу сроков.
В приведенной ссылке сравнивается большое с холодным, ведь js-фреймворк ни за что не сравнится с нативной разработкой; кроме того, многие отрицательные моменты оттуда уже не актуальны:
* interface builder есть давно, это alloy, и там есть даже data binding
* цвет placeholder'ов имеется в 5 релизе
* память после уничтожения activity освобождается в том же 5 релизе
мне самому был очень интересен IF еще с первого релиза, и у них есть много классных фич вроде удаленной сборки ipa без мака, ionic push/ionic creator, но вот реально, это все для разработчика хорошо, а надо чтобы было хорошо для клиента
torrie
Грустно, что за титаниум надо платить. Сперва платим за титаниум, потом за гугл, потом за ios.
Плюс ещё надо тратить деньги на обучение сотрудников тому же титаниуму(хоть там и js, но это же не стандартные функции). Чего нет в IF, где Angular един, а IF выступает мобильным «бутстрапом» с кучей примеров, тулзов, фич и коммюнити.
john_samilin
не надо если не хотите аналитикой пользоваться. сам SDK бесплатный
а обучение ангуляру стоит тех же самых временных и, как следствие, финансовых затрат
torrie
Angular продвигает сам большой брат.
john_samilin
Но это же не аргумент. До сих пор я ни в одном гибридном фреймворке, за исключением IF, его не встречал
BIanF
А я вот буквально сегодня выбирал между IF и Phonegap + Onsen UI. И мне бы хотелось сравнение этих фреймворков. Как я понял, по факту это форки Кордовы, просто IF реализует ещё некоторые контроллы? Остановился я на Phonegap. Не смог найти инфу про удалённую сборку и WP в IF.
m0sk1t
Скорее не форки а обертки, ибо можно писать как
так и
Эффект тот же. Просто у IF немного другая архитектура JS проекта, шаблоны и т.д. (Angular же)
Т.е. в скором времени можно ожидать)
torrie
cordova всё это умеет и даже большее