Pull to refresh
0
Alconost
Localization in 70+ languages & video production

Часто задаваемые вопросы по фреймворку WatchKit

Reading time 16 min
Views 17K
Original author: Soheil Azarpour
Для разработчиков Apple Watch — не просто нашумевший гаджет. Теперь им приходится осваивать Watch Kit, чтобы создавать и адаптировать свои приложения для «умных» часов. По мере знакомства с этой технологией, конечно же, возникает множество вопросов.

Мы в компании Alconost перевели обширный FAQ по фреймворку WatchKit. Будем рады, если труд наших технических переводчиков окажется для вас полезным. Для некоторых вопросов четких решений пока нет: в таких случаях следует полагаться на здравый смысл, советы специалистов и обоснованные предположения. Эта технология все еще в значительной мере находится в разработке, а следовательно, может изменяться. Тем не менее, ответы на многие животрепещущие вопросы уже найдены. Приглашаем вас под кат!



Основные вопросы


Что такое WatchKit и как это работает?


WatchKit — это фреймворк компании Apple для создания гибридных приложений для часов Apple Watch, в состав которого входит Xcode 6.2.

WatchKit разделяет приложение на две части:
  • часы Apple Watch содержат лишь ресурсы интерфейса пользователя, такие как раскадровка и каталог активов. Даже обрабатывая вводимую пользователем информацию, часы Apple Watch самостоятельно не исполняют код. Иными словами, часы работают как тонкий клиент;
  • iPhone содержит весь код и выполняет его как программное расширение — например, как расширения приложений Today или Action.



Примечательно то, что такой обмен информацией между часами Apple Watch и iPhone происходит автоматически и незаметно.

Вы работаете как обычно, а WatchKit осуществляет весь обмен данными по беспроводному каналу. Что касается кода, который вы пишете, то ваши вью и аутлеты подключаются локально, даже при том, что расположены на отдельном устройстве. Очень удобно!

 

Можно ли разрабатывать приложения для часов Apple Watch на языке Swift?


Да, приложения для часов Apple Watch можно разрабатывать как на языке Objective-C, так и на языке Swift (примечание переводчика: кстати, мы в Alconost перевели часть документации по этому языку), а можно использовать и сразу два языка. В качестве примера компания Apple опубликовала два проекта для среды WatchKit:

Кроме того, «Пособие по WatchKit», «Серия практических видеозанятий по WatchKit» и книга «Учебные материалы по фреймворку WatchKit» написаны исключительно на языке Swift.

Apple предоставляет документацию по фреймворку WatchKit на языках Objective-C и Swift.

 

Можно ли создавать собственные циферблаты для часов?


Нет, пользовательские циферблаты для часов пока не поддерживаются.

Изменение циферблатов для часов пока не поддерживается!

Сколько часов Apple Watch можно подключить к одному устройству iPhone?


Подключение монопольное, поэтому единовременно к одному устройству iPhone можно подключить только одни часы Apple Watch.

Можно ли подключить часы Apple Watch к планшету iPad?


Нет. Сейчас часы Apple Watch можно подключать только к iPhone.

Может ли приложение на смартфоне iPhone «разбудить» свое расширение WatchKit и приложение в часах?


Нет. Расширение WatchKit может лишь запросить у системы запуск сопутствующего приложения для устройства iPhone, и оно сделает это в фоновом режиме. Сейчас работа в обратном направлении не поддерживается.

Могут ли сторонние приложения делать телефонные звонки с приложения для часов?


Нет. Не существует открытого API, который позволял бы инициировать телефонный звонок напрямую из расширения WatchKit. Поскольку сопутствующее приложение для iPhone также нельзя вынести на приоритетный уровень, система игнорирует все телефонные звонки или запросы openURL: с сопутствующего приложения смартфона.

Можно ли получить доступ к датчику пульса и другим датчикам часов из приложения для часов?


Нет. Программного интерфейса для доступа к аппаратным сенсорам на часах Apple Watch пока нет.

В чем разница между кратким, длительным, статичным и динамичным уведомлениями?


  • Краткое уведомление (Short-Look) подается системой. Как и в случае уведомительного баннера на устройстве iPhone, контролировать краткие уведомления нельзя. Краткое уведомление показывает пиктограмму, название приложения и строку заголовка уведомления. Пришедшее уведомление отображается в «кратком» виде. Если пользователь поднимает запястье, то после небольшой паузы краткое уведомление сменяется длительным уведомлением.
  • Длительное уведомление (Long-Look) может быть либо статичным, либо динамичным:

статичное уведомление содержит единственную текстовую строку, которая заполняется автоматически с помощью информационного наполнения уведомления. Можно создать сцену для статичного уведомления в раскадровке приложения для часов, но в плане кастомизации можно лишь поменять цвет разделяющей полосы и заголовка;
динамичное уведомление требует разбивки на подклассы WKUserNotificationInterfaceController. Оно инстанцируется из раскадровки и позволяет определить собственный интерфейс. Следует иметь в виду, что интерфейс динамичного уведомления отображается не всегда. Например, если аккумулятор часов садится, для экономии заряда система может решить показывать интерфейс статичного уведомления вместо динамичного, поскольку статичные уведомления потребляют меньше энергии.

Под капотом



Выбор соответствующей схемы

Как проверить представление «быстрый просмотр» или уведомление с помощью симулятора?


Для запуска в симуляторе для каждого «быстрого просмотра» и уведомления нужна собственная схема сборки. Просто выбираете соответствующую схему, собираете и запускаете.

Можно ли разместить элементы интерфейса один поверх другого?


Нет. По умолчанию элементы интерфейса не могут размещаться один поверх другого. Однако это ограничение можно обойти. Например, можно использовать группу WKInterfaceGroup и установить фоновое изображение для того, что представляет собой элемент управления, поверх которого нужно добавить другой. Затем внутри этой группы можно добавить необходимые текстовые строки, кнопки и т. д.

Можно ли настроить свойство CALayer элементов интерфейса?


Нет. У элементов интерфейса часов Apple Watch нет свойства CALayer, поскольку они не происходят ни от класса UIView, ни от класса CALayer.

Можно ли разделить на подклассы те классы, которые есть во фреймворке WatchKit?


Ничто не мешает разбить на подклассы какой-либо класс во фреймворке WatchKit, но может случиться так, что использовать эту возможность не получится. На подклассы можно разбить некоторые классы, такие как WKInterfaceController и WKUserNotificationInterfaceController, — и использовать их в раскадровке.

Однако раскадровка приложения часов не позволяет изменять класс элементов интерфейса. Нельзя динамично создавать элементы интерфейса и вставлять или удалять их как отображаемые подэлементы; можно лишь скрыть или показать те элементы интерфейса, которые уже есть в раскадровке.

Можно ли комбинировать контроллеры интерфейса на базе страницы и на базе перемещения?


Можно, но с некоторыми ограничениями.

Если у вас иерархический интерфейс, то интерфейс на базе страниц можно представить с помощью модулей. Если какая-либо из страниц является иерархическим интерфейсом, тогда отображается его корневой каталог, и добавить в его стек ничего не получится.

Если у вас интерфейс на базе страницы, иерархический интерфейс можно представить с помощью модулей, но отображаться будет лишь контроллер корневого интерфейса, и добавить в его стек ничего не получится.

Есть ли в часах Apple Watch эквиваленты для классов UIActivityIndicator и UIAlertController?


Нет, но вместо класса UIAlertController можно модально отобразить пользовательский WKInterfaceController.

Индикатор действий можно обойти, добавив последовательность изображений для создания необходимой анимации, — или просто отобразить текстовую строку с соответствующим текстом. Как это делается, можно посмотреть в примере приложения Lister, предоставленном корпорацией Apple: в режиме «быстрого взгляда» одну круговую анимацию приложения часов представляют 360 изображений!


Можно ли использовать Core Graphics для динамичного создания изображений, а затем использовать их в приложении для часов? Могут ли они кэшироваться на часах?


Да, это возможно, но любые изображения с использованием Core Graphics должны составляться на iPhone как часть расширения. Как только графический контекст Core Graphics переведен в экземпляр класса UIImage, его можно разместить на часах с помощью метода addCachedImage(_:name:) из класса WKInterfaceDevice.

Можно ли использовать пользовательские представления в часах Apple Watch и настраивать элементы интерфейса вне рамок открытого API часов?


Нет. Пользовательские представления использовать нельзя. WatchKit поддерживает лишь некоторые собственные элементы интерфейса. Эти элементы нельзя разбивать на подклассы или настраивать вне рамок их открытого API. Есть следующие элементы интерфейса: WKInterfaceLabel, WKInterfaceButton, WKInterfaceImage, WKInterfaceGroup, WKInterfaceSeparator,WKInterfaceTable, WKInterfaceSwitch, WKInterfaceMap, WKInterfaceSlider и WKInterfaceTimer.

Как часы Apple Watch обмениваются информацией с устройством iPhone?


Для обмена информацией со связанным устройством iPhone часы Apple Watch оптимальным образом используют технологии Bluetooth LE и Wi-Fi. Реализация такого обмена информацией не прозрачна ни для пользователей, ни для разработчиков.

 

Можно ли использовать часы Apple Watch в автономном режиме?


Да. После активации автономного режима можно включить Bluetooth и Wi-Fi для обмена данными и использования часов.


Включение Wi-Fi и Bluetooth после активации автономного режима

Что происходит с приложением, когда часы Apple Watch не могут обмениваться информацией со связанным устройством iPhone?


Попросту говоря, приложение не будет работать, а если оно уже работает, то работа будет приостановлена.

В расширении WatchKit на текущий контроллер интерфейса будет вызван метод didDeactivate(), и можно будет сделать необходимую очистку. В статусной строке часов высветится красная пиктограмма устройства iPhone, указывая на потерю связи, а интерфейс останется на экране, но не будет интерактивен. Пользователи могут либо восстановить связь, либо выйти из приложения.

Как приложение часов обменивается информацией с сопутствующим приложением на устройстве iPhone?


Делается это по-разному. Самый распространенный способ — когда приложение часов записывает или обновляет данные в совместно используемом контейнере, а затем уведомляет приложение на смартфоне. После этого приложение на смартфоне может извлечь изменения из совместно используемого контейнера.

Другой способ заключается в том, чтобы перенести данные в приложение устройства iPhone через словарь, но такой метод может быть инициирован лишь приложением часов. Для этого в расширении WatchKit существует отдельный API: вызывается метод openParentApplication(userInfo:reply:) класса WKInterfaceController, как показано в следующем примере:
// Notify companion iPhone app of some changes in the shared container.

let kSharedContainerDidUpdate = "com.rayWenderlich.shared-container.didUpdate"

let requestInfo: [NSObject: AnyObject] = [kSharedContainerDidUpdate: true]

WKInterfaceController.openParentApplication(requestInfo) { (replyInfo: [NSObject : AnyObject]!, error: NSError!) -> Void in

// Handle the reply from the companion iPhone app...

}

В словаре userInfo вы просто переносите метку или некоторые данные для сопутствующего приложения iPhone, с которыми необходимо совершить действия. Чтобы получить такое сообщение, сопутствующее приложение устройства iPhone должно исполнить application(_:handleWatchKitExtensionRequest:reply:) в своем делегате приложения:
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {

let kSharedContainerDidUpdate = "com.rayWenderlich.shared-container.didUpdate"

if let isUpdate = userInfo[kSharedContainerDidUpdate] as? Bool {

// Process request, then call reply block

reply(...)

}

}

Если работа сопутствующего приложения смартфона приостановлена или прекращена, система запустит его в фоновом режиме. В зависимости от цели сообщения сопутствующее приложение iPhone может вывести что-нибудь в блоке ответа, который приложение часов может затем соответствующим образом обработать.

Как приложение устройства iPhone обменивается информацией с приложением для часов?


Приложение на устройстве iPhone не может инициировать обмен информацией со своим расширением. Помимо записи в совместно используемый контейнер или ответа на запросы приложения часов приложение на смартфоне может использовать центр уведомлений Darwin Notification Center для уведомления расширения WatchKit о конкретном событии. Центр уведомлений Darwin Notification Center – это API фреймворка Core Foundation.

При использовании центра уведомлений Darwin Notification Center необходимо помнить о некоторых очень важных моментах:
  • у приложения есть лишь один центр уведомлений Darwin Notification Center;
  • все уведомления Darwin являются общесистемными;
  • для доставки уведомлений цикл исполнения основного потока должен осуществляться в одном из общих режимов, таких как kCFRunLoopDefaultMode;
  • чтобы обрабатывать отправку и получение уведомлений Darwin, приложение на часах и приложение на смартфоне должны работать на переднем плане;
  • посредством уведомлений Darwin нельзя передавать объекты, поскольку такие уведомления содержат лишь имя и словарь userInfo;
  • уведомления Darwin не являются «настойчивыми» и доставляются незамедлительно. Следовательно, если работа инструмента наблюдения приостановлена, прекращена или он перемещен в фоновый режим, уведомление будет утеряно.


Что будет, если не использовать статичный или динамичный интерфейсы уведомлений?


Даже если для приложения на устройстве iPhone нет сопутствующего приложения для часов, система будет отображать на часах уведомления для такого приложения. Однако у интерфейса для уведомлений по умолчанию нет настраиваемого оформления. Если у приложения есть интерактивные уведомления, на часах система скроет эти действия.

В чем разница между методами setImage(_:) и setImageNamed(_:)?


Метод setImageNamed(_:) используется, когда изображение, которое нужно отобразить, либо хранится в кэше часов, либо находится в каталоге активов комплекта приложения часов, а setImage(_:) следует использовать тогда, когда изображение не хранится в кэше — так данные изображения передадутся в часы Apple Watch по беспроводному каналу.

Изображение хранится в кэше часов, если оно отвечает одному из следующих критериев:
  1. В проекте изображение связано с целью приложения для часов, то есть изображение располагается в каталоге активов, относящемся к цели приложения часов в проекте.
  2. Изображение было непосредственно помещено в кэш заранее посредством одних их следующих API класса WKInterfaceDevice: addCachedImage(_:name:) или addCachedImageWithData(_:name:).


Можно ли использовать iCloud в приложении для часов?


Да, в приложении для часов Apple Watch можно использовать iCloud. Как это делать, можно посмотреть на примере приложения-помощника Lister — одного из простых проектов, опубликованных компанией Apple.

Анимация


Как добавить анимацию в приложение для часов?


Единственный способ отображения анимации на часах Apple Watch — последовательность изображений. Для создания анимации необходимо заранее создать серию изображений, а затем циклически пройти их, как в нарисованном в блокноте мультфильме. Времена анимированных GIF вернулись! ;]

Для создания собственной анимации можно отобразить последовательность статических изображений в объекте WKInterfaceImage.
@IBOutlet weak var image: WKInterfaceImage?

...

image?.setImageNamed(image1) // Load the initial image using the required <name><number> format

image?.startAnimating() // Starts animating

...

image?.stopAnimating() // Optional. Stops animating.

Также можно анимировать лишь часть подборки изображений, как показано в следующем отрывке кода:
image?.startAnimatingWithImagesInRange(range, duration: 2, repeatCount: 1)

Можно ли создавать анимации для часов Apple Watch в виде кода?


Да — но, возможно, не так, как вы думаете. Как сказано выше, здесь нет фреймворка Core Animation или его эквивалента. Можно использовать Core Graphics для предоставления каждому кадру анимации закадрового контекста, присвоить его экземпляру UIImage, и, в конце концов, получится серия изображений, которые можно анимировать на часах Apple Watch.

В следующих отрывках кода показано, как можно создать анимацию для движущегося круга с использованием фреймворка Core Graphics посредством генерирования последовательности изображений для каждого кадра (код любезно предоставлен Джеком Ву (Jack Wu), одним из авторов пособия «Учебные материалы по фреймворку WatchKit»):
// Create an offscreen context

UIGraphicsBeginImageContextWithOptions(size, opaque, scale)

let context = UIGraphicsGetCurrentContext()

for centerX in 0..100 {

// Draw a circle at centerX.

// Create a snapshot.

let image = UIGraphicsGetImageFromCurrentImageContext()

// Write the image as a file

let data = UIImagePNGRepresentation(image)

let file = "path\\image_\(centerX).png"

data.writeToFile(file, atomically: true)

}
// End the context.

UIGraphicsEndImageContext()



Какова максимальная частота кадров на часах Apple Watch?


Установить частоту анимации кадров на часах Apple Watch нельзя. Однако можно установить продолжительность анимации, и система автоматически определит частоту кадров.

Если последовательность изображений направляется по беспроводному каналу — например, когда изображения не помещены в кэш, — они будут проходить со скоростью до 10 кадров в секунду. Если изображения уже помещены в кэш на часах Apple Watch через кэш изображений или каталог активов в комплекте приложения WatchKit, они будут проходить со скоростью до 30 кадров в секунду.

Отладка и модульное тестирование


Как запустить и отладить приложение устройства iPhone одновременно с приложением для часов Apple Watch с использованием симуляторов?


  1. Соберите и запустите приложение для часов — запустится симулятор часов Apple Watch и приложение для часов, которое привяжется к отладчику.
  2. Затем в симуляторе iOS запустите приложение для смартфона, коснувшись его пиктограммы.
  3. Теперь вернитесь в Xcode и в верхнем меню нажмите Debug\Attach To Process, затем выберите соответствующее приложение для устройства iPhone — так в отладочный браузер Xcode добавится новый процесс, а приложение для смартфона привяжется к отладчику.


Как провести модульное тестирование расширения WatchKit?


Модульные тесты приложения для часов создаются так же, как и модульные тесты приложений для устройств iPhone и iPad: достаточно добавить в проект новую цель модульного тестирования для расширения WatchKit. Однако указать приложение для часов в качестве приложения хоста не получится.


В «целях» приложения для устройства iPhone само приложение для смартфона отображается как приложение хоста


А для расширения WatchKit приложение хоста отсутствует

В цель модульного тестирования расширения WatchKit необходимо по отдельности добавлять каждый файл, который нужно протестировать.


Из расширения WatchKit в цель модульного тестирования добавьте файлы, которые нужно протестировать

Совместное использование данных


Как организовать совместное использование данных между расширением WatchKit и соответствующим приложением операционной системы iOS?


Для этого необходимо активировать группы приложений App Groups — контейнер в локальной файловой системе, к которому могут иметь доступ расширение и содержащее его приложение для устройства iPhone. Можно определить несколько групп приложений и активировать их для различных расширений.

После активации групп приложений можно использовать любой подходящий метод из следующих:
  • Чтение и запись напрямую в совместно используемом контейнере. URL-адрес совместно используемого контейнера можно получить посредством обращения к объекту NSFileManager. Для этого используется следующий API:

let kMyAppGroupName = "com.raywenderlich.mywatchapp.container"

var sharedContainerURL: NSURL? = NSFileManager.defaultManager().

containerURLForSecurityApplicationGroupIdentifier(kMyAppGroupName)

  • Использование совместного объекта NSUserDefaults. Для создания хранилища по умолчанию в совместно используемом контейнере необходимо инициализировать новый экземпляр NSUserDefaults с использованием инструкции NSUserDefaults(suiteName:) и передать уникальный идентификатор группы приложений, например:

let kMyAppGroupName = "com.raywenderlich.mywatchapp.container"

let sharedUserDefaults = NSUserDefaults(suiteName: kMyAppGroupName)

Примечание. Во избежание порчи данных считывать и записывать данные в совместно используемом контейнере необходимо согласованно, поскольку к совместно используемому контейнеру могут одновременно обращаться разные процессы. Для согласования чтения и записи рекомендуется использовать NSFilePresenter и NSFileCoordinator. Не рекомендуется использовать API согласования файлов в расширении приложения, поскольку это может привести к зависанию.

Причина такого поведения — жизненный цикл расширений приложения. У расширений приложения есть всего 3 состояния: а) исполняется, б) приостановлено и в) завершено. Если работа расширения приложения, которое использует API согласования файлов, приостановлена во время записи, у такого процесса не будет возможности освободить ресурс, и другие процессы окажутся заблокированы.

Когда приложение для устройств iPhone или iPad перемещается на задний план, оно получает уведомление через своего делегата приложения, и затем должно удалить объекты-презентаторы. Когда приложение снова переносится на передний план, эти объекты-презентаторы можно добавить вновь.

Вместо этого можно воспользоваться атомарной операцией безопасного сохранения, например writeToURL(_:atomically:) в классе NSData. SQLite и Core Data также позволяют безопасно обмениваться данными в совместно используемом контейнере между различными процессами, даже если один из них приостановлен в момент выполнения операции.

Подробнее об этом можно узнать в разделе «Техническое примечание TN2408. Доступ к совместно используемым данным из расширения и содержащего его приложения».

Как совместно использовать базу данных Core Data между приложением для часов и приложением для устройства iPhone?


Для совместного использования файла из постоянного хранилища Core Data необходим тот же механизм, который показан в примере совместного использования данных. Ниже приводится отрывок кода, демонстрирующий работу механизма:
let kMyAppGroupName = "com.raywenderlich.mywatchapp.container"

var sharedContainerURL: NSURL? = NSFileManager.defaultManager().

containerURLForSecurityApplicationGroupIdentifier(kMyAppGroupName)

if let sharedContainerURL = sharedContainerURL {

let storeURL = sharedContainerURL.URLByAppendingPathComponent("MyCoreData.sqlite")

var coordinator: NSPersistentStoreCoordinator? =

NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

coordinator?.addPersistentStoreWithType(NSSQLiteStoreType,

configuration: nil,

URL: storeURL,

options: nil,

error: nil)

}


Приступаем к делу


Можно ли делать игры для часов Apple Watch? Какие игры подходят?


Хотя еще слишком рано говорить о том, какие виды игр будут пользоваться успехом и захотят ли пользователи играть в игры на часах, однако почти с полной уверенностью можно сказать, что часы Apple Watch следует рассматривать в другом свете.

Для разработки игр на устройствах iPhone и iPad требовалось совсем иное мышление, нежели для игр на настольном компьютере. Так же и к часам Apple Watch необходим особенный подход.

Мы уже знаем об ограничениях платформы: на часах Apple Watch нет API для аппаратного доступа, они не поддерживают распознавание жестов и не позволяют рисовать на экране. Помните: можно использовать лишь собственные элементы интерфейса.

Но не позволяйте этим ограничениям останавливать полет творческой мысли — считайте их основополагающими принципами. :]

Как зарабатывать деньги с помощью приложений для часов Apple Watch?


Еще слишком рано об этом говорить. Следует иметь в виду одно: платформа iAd не поддерживается, а с учетом малого размера экрана и времени, в течение которого пользователь будет взаимодействовать с приложением для часов, экранная реклама скорее всего будет раздражать пользователей и в любом случае не будет столь эффективной, чтобы оправдать финансовые затраты на нее.

Кроме того, если расширение WatchKit включено в комплект приложения, его нельзя будет деактивировать или иным образом помешать пользователю его установить. Поэтому его нельзя сделать платным дополнением, которое можно купить из приложения.

Однако способы монетизации расширений WatchKit все-таки есть:
  • если в магазине App Store есть бесплатная и платная версии приложения, можно просто реализовать расширение WatchKit исключительно в платной версии приложения;
  • в приложении с «внутренними» покупками можно просто отображать в расширении ограниченную информацию и разрешить пользователю разблокировать дополнительные функции посредством покупок из приложения.

Это, конечно же, не исчерпывающий перечень возможностей монетизации приложений WatchKit, но ясно одно: к монетизации следует подходить столь же творчески, как и к самим приложениям для часов.

Есть ли основания полагать, что приложения для часов — новый способ зарабатывать на жизнь посредством разработки приложений для магазина App Store?


Какие-либо выводы делать преждевременно. И хотя часы Apple Watch — это действительно совершенно новая платформа, полная интересных возможностей, с ее появлением не начнется «золотая лихорадка», которую можно было наблюдать при открытии магазина App Store.

Нужно иметь в виду, что часы Apple Watch преследуют иную цель. Учитывая их внешний вид и цену, они ближе к украшению, которое обменивается информацией с iPhone, чем к самостоятельному устройству.

Однако расширение WatchKit может помочь тому или иному приложению выделиться из толпы. Как разработанные специально для планшетов iPad приложения пользовались большим успехом, чем приложения для смартфонов iPhone, просто подогнанные под iPad, так и хорошо разработанное приложение для часов Apple Watch, которое дополняет какое-либо приложение для устройства iPhone, может повысить продажи.

А какие ваши прогнозы насчет Watch Kit?


О переводчике

Перевод статьи выполнен в Alconost.

Alconost занимается локализацией приложений, игр и сайтов на 60 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.

Мы также делаем рекламные и обучающие видеоролики — для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.

Подробнее: https://alconost.com

Tags:
Hubs:
+10
Comments 2
Comments Comments 2

Articles

Information

Website
alconost.com
Registered
Founded
2004
Employees
201–500 employees