Tag: «ios»
Анимация касания индикатора в UISlider
Стояла задача - при касании слайдера анимировать в приложении сделать анимированное увеличение ползунка. Так же, как это сделано у Apple в приложениях Apple Music и Podcasts в плеере, когда начинаешь перематывать позицию воспроизведения. Пока искал способ стандартными средствами, убил немало времени. Очень не хотелось писать прям свой кастомный слайдер, хотелось использовать системный UISlider, что мне, в итоге, и удалось.
comment commentsSMS Антикредит
Надоел мне СМС-спам, который мне периодически приходит - то кредит предлагают, то ипотеку. Как раз, просматривая один из видосов с WWDC, увидел, что Apple дали API для фильтрации SMS-сообщений. Если сработает твой фильтр - то такие сообщения будут приходить без звукового или вибросигнала, втихую, и попадать в отдельную категорию спама в Сообщениях.
Недолго думая, запили такой вот фильтр и выложил в App Store. Надеюсь, кому ещё, кроме меня, пригодится.
comment commentsCompareShots v1.2
Вышло обновление CompareShots версии 1.2
Выпустил потому, что написал один из пользователей на почту просьбу скрывать логотип и текст после того, как выбрана картинка, т.к. иначе, если она не на полный экран, то они торчат из-за картинки или просвечивают. Попутно решил ещё прокачать приложение. Наконец-то пригодилась подписка на разные новые библиотеки. Поверх картинок теперь можно рисовать. Разными цветами и кистями разного размера. Один вечер - и такая красота :)
comment commentsCompareShots v1.1
Вышло обновление CompareShots версии 1.1.
Из нового - прозрачность теперь меняется просто перетаскиванием пальца (горизонтально). Причём такую фичу попросил один из пользователей в письме. К тому же так прозрачность меняется плавнее.
Кроме того, переписал полностью приложение на Swift. Теперь у меня есть аж один зарелизенный проект на нём :) Но это только начало.
Что удивительно - обновление в App Store я отправил вчера, и менее чем через сутки оно уже прошло Review и вышло. Ни разу так быстро Apple на моей памяти так быстро не пропускали приложение.
comment commentsСборка iOS проекта из консоли через xcodebuild
Оставлю себе шпаргалку, как сделать на bash скрипт, который соберёт тебе проект в ipa файл.
В папке с исходниками iOS-проекта делаем папку, например, scripts, в нем создаём файл build.sh
mkdir scripts
touch build.sh
chmod +x build.sh
В папку кладём наш provision profile. Скажем, называется он arm1.ru.mobileprovision. После этого внутрь build.sh кладём код:
#!/bin/bash
# идём в директорию скрипта
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "${CURRENT_DIR}"
# вводим то же имя, которое в настройках проекта выбрано в Build Settings в разделе Code Signing Identity
CODE_SIGN_IDENTITY="iPhone Distribution: Your Code Signing Identity"
# имя provision profile, который лежит тут же
PROVISION="$PWD/arm1.ru.mobileprovision"
# имя схемы, которую собираем
SCHEME="appScheme"
WORKSPACE="$PWD/../your-app.xcworkspace"
echo "Building..."
BUILDDIR="$PWD/build"
DSYMDIR="$PWD/dSYM"
if [ ! -d "$BUILDDIR" ]; then
mkdir -p "$BUILDDIR"
fi
if [ ! -d "$DSYMDIR" ]; then
mkdir -p "$DSYMDIR"
fi
# ищем UUID в provision profile
UUID=`grep UUID -A1 -a "${PROVISION}" | grep -io "[-A-Z0-9]\{36\}"`
xcodebuild \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-sdk iphoneos \
-configuration Release \
CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" \
PROVISIONING_PROFILE="${UUID}" \
OBJROOT=$BUILDDIR \
SYMROOT=$BUILDDIR
if [ $? != 0 ]; then
echo "Build failed"
exit 1
fi
echo "Packaging..."
NOW=$(date +"%d_%m_%Y_%H_%M_%S")
xcrun -sdk iphoneos PackageApplication -v "${BUILDDIR}/Release-iphoneos/${SCHEME}.app" -o "$PWD/${SCHEME}_${NOW}.ipa"
if [ $? != 0 ]; then
echo "Packaging failed"
exit 2
fi
mv "${BUILDDIR}/Release-iphoneos/${SCHEME}.app.dSYM" "${DSYMDIR}/${SCHEME}_${NOW}.app.dSYM"
echo "Build succeeded."
Вуаля, в папке со скриптом лежит appScheme.ipa - собранный проект. В папке dSYM лежат dSYM-файлы. В конце скрипта можно дописать что-нибудь. У меня в одном из проектов в конце идёт заливка билда на наш самодельный Testflight и отправка ссылки на установку всем кому нужно. Удобно, два раза кликнул по sh-файлу и всё собралось и отправилось.
comment commentsКак отключить жест swipe back в iOS 8
В iOS есть системный жест - свайпишь от левого края экрана и возвращаешься на предыдущий экран. Он клёвый и интуитивно понятный и меня бесит, когда его отключают в приложениях, но иногда всё-таки приходится это делать. Например, из-за интерфейсного решения, которое пересекается с этим свайпом.
В общем для iOS 8 надо в текущем UIViewController добавить:
в YouViewController.h:
// ставим протокол обработки жеста
@interface YouViewController : UIViewController
в YouViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// отключаем жест если это swipe back
if ([gestureRecognizer isEqual:self.navigationController.interactivePopGestureRecognizer]) {
return NO;
} else {
return YES;
}
}
comment
comments
CompareShots
Выпустил новое приложение - CompareShots.
В ходе работы возникла мысль, что неплохо бы иметь какой-то инструмент для сравнения макета и фактического результата. За вечер написал такую вот тулзу. Ещё за несколько вечеров сделал скриншоты. Да, на скриншоты ушло времени больше :)
Приложение позволяет выбрать 2 изображения из библиотеки устройства. Например, макет приложения или сайта от дизайнера, и скриншот того, что сделал разработчик, и проверить соответствие - действительно ли всё пиксель в пиксель свёрстано. Пока держишь палец на экране - показывается первая картинка. Убираешь - вторая. Есть слайдер прозрачности, можно наглядно увидеть, в каких местах несоответствие. Результат несоответствия в виде картинки можно расшарить или отправить по почте, или отправить в любой другое приложение, которое принимает картинки.
Приложения для iPhone и iPad.
comment commentsИтог айПодписок
Итак, в феврале Яндекс всех пользователей Яндекс.Подписок насильно перетащил на сервис Яндекс.Новости. Без предупреждения и как-то по-свински всё это получилось. Веб-версия стала недоступна. Но API работало, про его судьбу яндексоиды лишь говорили, что оно будет работать. Но про сроки и планы молчали. Собственно, сначала перестали подсасываться новые RSS-записи и API давал доступ только к старым записям, а потом разослали всем письма, что 31 марта 2015 года API они тоже убьют. Что и произошло. Поэтому я убрал из App Store приложение айПодписки и хочу подвести какие-то итоги для себя и для истории.
Приложение я написал весной 2014 года примерно за неделю, кодя по вечерам. В App Store оно появилось 17 апреля 2014 года, о чём я радостно писал у себя и в посте на Хабре. Пост набрал рейтинг +15 и принёс какое-то количество скачиваний и запросов на фичи. Приложение не дотянуло 17 дней до своего первого дня рождения :)
За этот год было:
1321 скачивание приложения и всего 496 обновлений, когда я выпускал новые версии. Негусто, но это и приложение не массовое. RSS вообще считается технологией для роботов и гиков. Абсолютное большинство пользователей из России. 2 и 3 место - США и Украина, там поровну. Рекорд: 77 установок за сутки.
1431.42 р. дохода на внутренних покупках. Изначально приложение было бесплатным, но с рекламой и её платным отключением. Из моих 4 приложений в App Store, это худший результат. С другой стороны, получить какие-то деньги за то, что учишься, получаешь опыт и решаешь какую-то свою проблему - тоже неплохо :)
Большую часть времени приложение находилось в Топ-200 в своей категории (Новости) в России. На скрине выше - самые высокие позиции, на которые оно заползало в топах.
С оценками всё не очень. Я нигде не просил людей поставить оценку приложения, поэтому ставили её либо энтузиасты и друзья, либо недовольные. Я, к сожалению, пропустил глюк очень досадный. Когда приложение запускалось в портретном режиме - кнопка «Все подписки» просто не появлялась. В итоге пользователь видел только приветственный экран и кнопку «убрать рекламу» за деньги :) И грустно, и смешно. Сам я постоянно приложением пользовался в горизонтальном режиме и попросту эту проблему не замечал, зато получил отзывы типа «работает только реклама».
Большинство запрошенных фич я так и не смог реализовать по причине нехватки времени. Занимаясь веб-разработкой целыми днями, тяжело вечером переключаться на мобильную, да ещё и уставшим после работы.
В приложении показывается реклама из AdMob. За год было всего 71135 показов рекламы, которые принесли $23.87. То есть внутренняя покупка отключения рекламы принесла больше денег, чем сама реклама :)
Такие вот скромные, но итоги.
comment commentsайПодписки и закрытие Яндекс.Подписок
Так как Яндекс закрыл свой сервис Яндекс.Подписки и всех насильно перевёл всех в Яндекс.Новости, я думаю на этом айПодписки закончатся. Разве что они сделают публичный API для Яндекс.Новостей. Если он будет годным для перевода, то попробую перевести айПодписки на новый API. Но пока API нет. API самих Яндекс.Подписок пока работает, но сбор новых новостей из RSS-лент они остановили. У меня там последние новости за 5-6 февраля. Сам я перешёл на Feedly. Спасибо всем, кто пользовался :)
UPDATE: пришло письмо, что 31 марта API подписок закроется. Видимо, придётся и приложение из App Store убирать.
comment commentsВыход за пределы песочницы в iOS
Пост боли. Так получилось, что сейчас ковыряю чужой проект, который рассчитан на работу на айфонах с Jailbreak. Да не просто рассчитан, а ему нужно иметь доступ за пределы песочницы.
Как известно, все приложения в iOS работают внутри песочницы, за пределы которой они выходить не могут. Все приложения из App Store устанавливаются в /var/mobile/Applications/ (приложения, которые ставятся в iPhone из Xcode - туда же), где для них создаётся отдельная папка с нечитаемым именем. Выходить за пределы этой папки нельзя. Ни для чтения, ни, тем более, для записи.
Это, например, папка гугловой игры Ingress.
А это айосный калькулятор, например, лежащий в /Applications.
Если мы хотим, например, прочитать в приложении SMS-сообщения телефона, то нам нужно прочитать файл /var/mobile/Library/SMS/sms.db - это обычная SQLite база данных, без шифрования и защиты. Её можно скачать с джейлбрейкнутого телефона и открыть любой тулзой, которая умеет открывать SQLite-файлы, и посмотреть все SMS-ки и даже фигачить по ней sql-запросы для поиска и прочего.
Тут, например, лежит файл со всеми смс-ками iPhone.
Так вот, доступа к этому файлу из песочницы нет. И Jailbreak от этого не спасает, он даёт полный доступ к файловой системе, но только если ты работаешь вне песочницы.
Чтобы приложение работало вне песочницы - его нужно переместить из /var/mobile/Applications/ в директорию /Applications. Тогда приложение будет в папке системных программ, будет иметь доступ на брейкнутом девайсе к файловой системе, не будет удаляться из телефона удержанием пальца на иконке и всё такое.
И вот боль тут в том, Xcode ну никак не может установить приложение туда, только в песочницу. Можно делать это руками - заходить в телефон, перемещать через какой-нибудь iFunBox, и вот такой вот геморрой каждый раз. И самая боль в том, что ты теряешь удобство отладки. Нельзя запустить в Xcode апп на девайсе и спокойно смотреть в консольку - что же там приложение твоё пишет - работает или нет?
Никакие твики из Cydia, типа дающие для приложений доступ к файловой системе даже для приложений из песочницы, не дали эффекта. По крайней мере, у меня на iOS 7.1.2. Говорят, что даже если запустить приложение от root'а, но всё равно в песочнице - и это не даст прав на чтение системных директорий. Хотя, такое ощущение, что раньше такое работало, но и джейлбрейки были другие.
Такой вот ад. В ближайшее время буду пробовать найденные в инете скрипты для автоматизации перемещения приложения внутри айфона после билда через SSH с отлавливанием syslog'а. Хочу ещё расписать, чего я наковырял внутри айфона в плане «где что хранится», но позже, по окончании ада :)
comment commentsКлиент Linode для iPhone
Пользуюсь уже несколько лет услугами хостера Linode.com, где держу свою VPS-ку с сайтами/проектами и для всяких личных целей.
У них есть API, на основе которого, как я понимаю, они сделали клиент для iPhone.
Клиент это, по-моему, просто перекрасен.
comment comments
Пишем клиент для Яндекс.Метрики для iPhone
Примеры создания графиков в PNChart есть на странице проекта на Github. С регистрацией приложения всё понятно - добавляем приложение, отмечаем права, которые приложение хочет получить. При авторизации пользователь должен будет разрешить или отклонить разрешение на получение данных, да и будет видеть - какие права просит приложение.
OAuth-авторизация
Лично меня слегка подбешивает, когда ты устанавливаешь неофициальный клиент для какого-нибудь сервиса, а он просит ввести логин и пароль в свою форму. Мало ли куда они потом идут. По-моему, процедура авторизации выглядит более прозрачно, если всплывает окно браузера с авторизацией на сайте сервиса.
Технически OAuth-авторизация делается довольно просто - добавляем в приложение собственную URL-Scheme (например, myapp), в настройках приложения на сайте Яндекса (там, где регистрировали приложение) указываем в качестве Callback URI что-то вроде myapp://, после чего кодим, чтобы после авторизации приложение пыталось открыть url по адресу myapp://, таким образом url откроется в нашем приложении, а в нём уже все данные, включая access_token.
На Хабре есть отличная статья именно с такой авторизацией и именно для Яндекса, которая сэкономила мне время, поэтому особо больше описывать авторизацию не буду, чтобы не повторяться. После авторизации у нас есть token, который мы и сохраним.
Технический нюанс: логично, что когда-нибудь пользователь захочет разлогиниться в приложении. Чтобы кто-нибудь не имел доступа к статистике кроме него, или, например, чтобы залогиниться под другим аккаунтом. При авторизации через браузер (то есть, через UIWebView), мало удалить полученный ранее token. Ваша авторизация в UIWebView запоминается, поэтому при разлогинивании пользователя нужно почистить cookies в песочнице приложения, иначе при авторизации вы автоматически залогинитесь в тот же аккаунт. Простого кода в моём случае было достаточно:
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
[NSUserDefaults standardUserDefaults] synchronize];
Сохранение token'а
Важный момент для параноиков - сохранение полученного token. Можно, конечно, положить его в NSUserDefaults - но говорят, при наличии, например, jailbreak, это не безопасно и можно расковырять этот token. Хорошая практика - хранить ключи доступа, логины и прочие важные штуки в KeyChain, там всё зашифровано и секьюрно.
Ковыряя его возможности в первый раз, даже со статьями на родном языке, возникает лёгкое желание побиться головой обо что-нибудь. Разбираться в этом всём, когда тебе нужно просто сохранить n-символов, а потом их оттуда взять, очень не хочется. Для ленивых вроде меня, Apple к документации приложила класс KeychainItemWrapper. Название само за себя говорит - обёрка вокруг KeyChain. Правда, класс этот написан давно и без поддержки ARC, но на github легко можно найти его форк с поддержкой ARC. Вот этот, например (его ещё и через CocoaPods можно подключить).
Ну а теперь сохранение и удаление token'а сводится к нескольким строчкам:
// сохранение
KeychainItemWrapper *keychainWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"OAuthToken" accessGroup:nil];
[keychainWrapper setObject:@"accountName" forKey:(__bridge id)(kSecAttrAccount)];
[keychainWrapper setObject:tokenToSave forKey:(__bridge id)(kSecValueData)];
// удаление
KeychainItemWrapper *keychainWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"OAuthToken" accessGroup:nil];
[keychainWrapper resetKeychainItem];
Получение различных данных
Лично мне больше нравится получать данные в JSON. С запросами всё довольно просто - шлём нужный запрос к методу из API, в заголовок включаем Authorization: OAuth
https://api-metrika.yandex.ru/counters?oauth_token=ACCESS_TOKEN
Раз уж мы параноим с самого начала насчёт безопасности, то запросы все можно слать через https (но можно и через http). Получаем данные в JSON, парсим их через встроенный NSJSONSerialization:
dataObject = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingMutableContainers error:&e];
Результат, например, для получения списка счётчиков:
Ну а дальше полученные данные нужно как-то отобразить.
Отображение
Если смотреть на web-интерфейс Метрики, то там в основном 2 представления данных - таблицы с текстовыми данными и графики.
С таблицами всё, вроде, понятно - используем UITableView. Почти все данные содержат одни и те же общие показатели - просмотры, визиты, уникальные посетители, глубина просмотра, время на сайте, показатель отказов. Поэтому я сделал класс-потомок UITableViewCell, чтобы не плодить кучу одинакового кода в каждом из UIViewController, после чего в методе
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
мы просто создаём ячейку с уже полученными данными, например:
NSString *CellIdentifier = [NSString stringWithFormat:@"CellId_%li_%li", (long)indexPath.section, (long)indexPath.row];
SourcesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if ( cell == nil ) {
cell = [[SourcesTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier
textOnLabel:@"просмотры" labelTextColor:[UIColor black]];
}
// задаём значения
[cell
changeValuesWithpageViewsNum:pageViewsNum
visitTime:visitTime
denials:denialsValue
visitsNum:visitsNum
];
Время, проведённое на сайте, кстати, возвращается в секундах, поэтому его ещё надо красиво вывести в формате мм:сс, а процент отказов - от 0 до 1, поэтому 50% вернётся как 0.5.
Для рисования графиков есть несколько готовых решений. Кое что, опять же, можно найти на Хабре. Лично мне на гитхабе приглянулся PNChart - минималистично, симпатично. Подключаем через CocoaPods и в путь.
Примеры создания графиков в PNChart есть на странице проекта на Github.
Компонуем всё в UITabsViewController, немножко оформляем и получаем на выходе быструю статистику в кармане.
Результат я решил выгрузить в AppStore, желающие могут установить.
Для стимула продолжать разработку, сделал приложение платным.
Полезные ссылки:
CocоaPods — мощное средство в руках Objective-C разработчика
Яндекс OAuth авторизация в iOS
PNChart (рисование графиков)
KeychainItemWrapper (обёртка вокруг KeyChain)
Обновление айМетрик 1.1
Сегодня вышло обновлений айМетрика. Основные нововведения - вкладка Посетители с отчётами по полу, возрасту и географии, и вкладка с целями, в которой есть все те же самые отчёты, но по целям и с дополнительными параметрами вроде достижения цели, конверсия и др.
Плюс поправил пару косяков.
comment commentsайПодписки - клиент для Яндекс.Подписок для iPad
Давно хотел написать это приложение. Странно, но с тех пор, как 8 месяцев назад Яндекс открыли API для Яндекс.Подписки в честь закрытия Google Reader, не появилось ни одного клиента для iOS. А иметь такое приложение мне и самому хотелось. Начал читать новости дома за чашкой чая, потом продолжил в iPad читать в дороге - все прочитанное уже отмечено как прочитанное, красота. Да и в целом мне удобнее читать новости с iPad. Как-то, web-версия подписок не особо быстро работает.
В приложении можно не только читать новости и делиться ими в соц. сетях или добавить в Список для чтения в Safari, но и управлять подписками - добавлять новые новостные ленты и удалять уже добавленные.
Нет приложения - напиши сам :) Приложение бесплатное.
comment commentsайМетрик - клиент для Яндекс.Метрики для iPhone
Выпустил мобильный клиент для Яндекс.Метрики для iPhone. Первая версия умеет:
- сводка посещаемости (визиты, просмотры, посетители);
- график посещаемости по дням;
- отчёт об источниках переходов на ваш сайт;
- отчёт по переходам из поисковых систем;
- отчёт по переходам с других сайтов;
- отчёт по поисковым фразам, с помощью которых посетители нашли ваш сайт;
- отображение списка ваших счётчиков.
AllCafe для iPhone в разделе Все о Москве App Store
Мелочь, а приятно. В российском App Store появилась отдельная подборка «Все о Москве», куда попало написанное мною приложение.
comment comments
Этот день 2.0
Выпустил обновление приложения Этот день. Передизайнил под iOS 7 (что примерно равно тому, чтобы убрать вообще почти всё оформление). Стало, по-моему, приятнее и лаконичнее.
Заюзал стандартный UIActivityViewController для шаринга через Facebook, Twitter и другие встроенные вещи, типа отправки по email, sms/iMessage и т.д., плюс прикрутил для шаринга в ВК Vkontakte-iOS-SDK вместо самописной хрени, что была раньше. Правда, визуальных отличий особо для пользователя нет, да и пока у меня руки дошли всё наконец допилить, появился уже официальный SDK для iOS от ВК, но ковырять его, чтобы заменить то, что уже работает, не стал.
Попутно избавился от уже ненужного SBJson для работы с JSON, т.к. можно смело пользоваться уже встроенным в iOS NSJSONSerialization. Решил встроить в приложение мобильную Яндекс.Метрику, чтобы считать нормально пользователей и смотреть - кто, например, какой версия iOS пользуется и на каком девайсе. Пока наблюдаю за цифрами, познавательно.
Такое вот продолжение маленького проекта-хобби. Буду думать, чего бы в нём ещё запилить, и буду писать следующее приложение. А пока - качаем/обновляемся:
comment commentsОтлавливание остановки карты в Google Maps SDK для iOS
Дополнение вот к этому посту. Там я указал в проблемах, что в гуглокартах реализовано только событие mapView: didChangeCameraPosition:, которое срабатывает на каждый чих карты, даже если палец ещё не оторван от экрана. Если вести медленно пальцем по карте, а на карте должны появляться какие-то метки, которые получаются извне http-запросами, то оно умудряется отправлять по 10-15 запросов в секунду. Это, конечно же, неприемлимо, особенно в условиях мобильного интернета.
Но с этим можно справиться своими средствами, а точнее с помощью UIPanGestureRecognizer.
Код-шпаргалка:
// где-то в коде проекта
panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mapDidChange:)];
[panRecognizer setMinimumNumberOfTouches:1];
[panRecognizer setMaximumNumberOfTouches:1];
// GoogleMapsView это гуглокарта, класс GMSMapView
GoogleMapsView.gestureRecognizers = @[panRecognizer];
//.. метод, реагирующий на отпускание касания
-(void)mapDidChange:(UIPanGestureRecognizer *)pan {
if ( panRecognizer.state == UIGestureRecognizerStateBegan ) {
// ничего не делать, но можно и что-нибудь делать
}
if ( panRecognizer.state == UIGestureRecognizerStateEnded ) {
// палец отпущен, можно делать то, что нужно, например:
[self sendMapRequest];
}
}
Всё, навешивается тач на гуглокарту. Когда палец отпущен - делаем действия. Теоретически этим можно даже заменить метод mapView: didChangeCameraPosition: если нужно.
comment commentsЗамена UIDevice uniqueIdentifier
Поскольку uniqueIdentifier теперь deprecated (ещё со времён iOS 5) и Apple больше не принимает приложения, использующие этот метод (последний Xcode даже не собирает, кажется, проект с использованием этой функции.
Погуглил, чем заменить и собрал из разных решений одно:
NSString *uuid = @"";
if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
// This is will run if it is iOS6 or later
uuid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
} else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuidId = [defaults objectForKey:@"deviceUuid"];
if (uuidId)
uuid = (NSString *)uuidId;
else {
// Create universally unique identifier (object)
CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);
// Get the string representation of CFUUID object.
uuid = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidObject);
CFRelease(uuidObject);
[defaults setObject:uuid forKey:@"deviceUuid"];
}
}
Apple предлагает использовать теперь метод identifierForVendor. Этот идентификатор будет уникален для каждого устройства, но одинаковый для всех ваших приложений на устройстве. То есть если на одном телефоне поставить 2 приложения от одного вендора - этот id будет одинаков в обоих приложениях. Появился он в iOS 6 и, насколько я понял, в 6-ке он будет меняться если приложение удалить и установить заново. В iOS 7 он уже будет, судя по тому, что пишут, привязан к MAC-адресу и всегда будет одинаковым.
У метода identifierForVendor был баг в iOS 6.0 - у устройств, обновившихся по воздуху, возвращалось значение с нулями. В 6.0.1 и старше это пофиксили.
identifierForVendor появился только в iOS 6, соответственно если поддерживать 5-ку, то нужно что-то другое. Тут на помощь приходит CFUUIDCreate. ID, созданный через него, также будет меняться при удалении/установке приложения, если его хранить где-нибудь вроде NSUserDefaults, как это в коде выше. Если хранить в KeyChain - то можно избежать его смены после установке. Хотя насколько это нужно - лично мне не очень понятно. Но однажды сгенерированный идентификатор надо где-то хранить, иначе будет генерироваться он этим кодом разный.
Такая вот шпаргалка.
comment commentsПристальный взгляд на иконки в iOS 7
Перевод статьи с iconfinder.com.
Мы следили за конференцией WWDC компании Apple и более пристально взглянули на новую версию их популярной ОС для мобильных устройств iOS 7. Посмотрим на иконки в iOS 7 поближе.
comment commentsИспользование Google Maps SDK for iOS
В декабре Google выпустили SDK для встраивания своих карт в iOS-приложение. Последние дни я ковырялся в них, пытаясь встроить их свой проект. Небольшое описание того, как их встроить.
comment commentsПро экранирование URL'ов изображений с русскими именами в Objective-C
Начало начальство у нас вставлять в новости у нас на портале изображения, названные русскими именами, вроде Фото-Ресторана.jpg.
Картинки из новостей у нас показываются в приложении, соответственно по этим URL'ам они в приложение и тянутся.
Соответственно приходит в приложение URL уже закодированный, например:
http://allcafe.ru/s/pic/news/!_2012/2012_12/%D0%A0%D0%B5%D1%81%D1%82%D0%BE%D1%80%D0%B0%D0%BD-Graf-in-%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3-%D0%B7%D0%B8%D0%BC%D0%BD%D1%8F%D1%8F-%D1%82%D0%B5%D1%80%D1%80%D0%B0%D1%81%D0%B0.jpg
В приложении используется класс AsynchronousUIImage, найденный мною около года назад в интернетах. Класс просто асинхронно загружает картинку с помощью NSURLConnection.
- (void)loadImageFromURL:(NSString *)anUrl {
anUrl = [anUrl stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:anUrl]
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:30.0
];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
Собственно, в stringByAddingPercentEscapesUsingEncoding оказался затык. Т.к. URL'ы приходят уже экранированными, то, похоже, происходило повторное экранирование и картинка просто не загружалась. Ну а если не прописан из протокола NSURLConnectionDelegate метод connection:didFailWithError: - то это чревато падением приложения, с чем я и столкнулся.
Итог ковыряний забавен, если есть URL'ы с кириллицей - то экранировать их надо или на сервере перед отправкой в клиент, или в клиенте. Если экранировать и там, и там - то не работает. Если нигде не экранировать - то тоже не работает :)
Благо я справился на серверной стороне ничего не сломав и избежав срочного обновления клиента. На Android, кстати, такой проблемы нет.
comment commentsОптимизация iOS-приложения для экрана iPhone 5
-
Для xib-файлов для View выставить Size: Freeform. Тогда интерфейс на всю высоту экрана растянется. Если надо всё кастомно - то можно выставить для вида размер для 3.5-дюймового экрана или 4-дюймового и программно нужный подставлять.
-
Самое главное - добавить стартовую картинку для 4-дюймового экрана. Без неё апп почему-то думает, что экран маленький. И да, если раньше никакой стартовой картинки не использовалось - теперь походу придётся.
Как оно со Storyboard пока не знаю. И ещё мелкая полезность - если в коде надо определить - широкий ли экран или обычный, то можно а prefix.pch задефайнить:
#define IS_WIDESCREEN ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )
и проверять в коде:
if ( IS_WIDESCREEN == YES ) {
// юзать интерфейс для 4 дюймов
} else {
// юзать интерфейс для 3.5 дюймов
}
comment
comments
Приложение Этот день теперь и для iPhone
Наконец дописал приложение «Этот день» так, что оно теперь ставится не только на iPad, но и на iPhone.
comment commentsЭтот день для iPad 1.1
Вышла версия 1.1 "Этот день" для iPad. Изначально я хотел сделать листалку событий пальцем и мелочь поправить, но как-то это затянулось. Когда наконец закодил - 2 или 3 раза делал Reject приложения из App Store, т.к. находил дикие косяки, от которых приложение просто падало или криво показывалось. Пока исправил всё критичное - вышел новый iPad, поэтому следом я добавил графику для него. А под самый конец обнаружил, что шаринг в ВК требует капчу, пришлось гуглить и писать её обработку.
В общем, изначально мелкие изменения вылились в довольно нехилый для такого аппа changelog:
- поддержка iPad с Retina-экраном;
- события теперь можно пролистывать пальцем;
- при публикации события в Facebook или ВКонтакте в приложении теперь появляется уведомление об успешной публикации;
- при публикации событий в Facebook или ВКонтакте они публикуются теперь в более понятном виде с полной датой;
- добавлена обработки капчи при публикации во ВКонтакте;
- исправлена ошибка, в результате которой длинные события не публиковались;
- небольшие доработки и исправления ошибок.
Обновляемся. А кто ещё не установил - ставим!
comment commentsAllCafe для iPhone 2.0
Вышла AllCafe для iPhone 2.0.
Что нового в версии 2.0
- Новое оформление информации о ресторане;
- просмотр фотографий ресторанов;
- новое оформление новостей ресторана;
- просмотр фотографий к новости;
- новое оформление отзывов о ресторане;
- просмотр фоторафий в отзыве и всех фотографий из всех отзывов посетителей;
- добавлены журналы ресторанов;
- различные исправления и дополнения.
Как определить Retina Display на iPad/iPhone
Внимание! Если вам нужно просто понять, какой экран у вас на вашем iPad, iPhone или iPod - просто перейдите по ссылке: https://arm1.ru/retina/
Пытаясь понять, как мне на Objective-C определить наличие Retina-экрана в устройстве, пришлось погуглить. Нашёл такое решение, которое запишу для шпаргалки.
Получаем размеры экрана:
CGRect screenBounds = [[UIScreen mainScreen] bounds];
Возвращает размер экрана, обычно 320x480, даже на iPhone 4, iPhone 4S и iPod Touch вернёт 320x480 (иначе вроде как старые приложения падают). Для iPad возвращает 768x1024 - и на iPad/iPad 2, и на новом iPad с Retina Display.
Получаем масштаб экрана:
CGFloat screenScale = [[UIScreen mainScreen] scale];
Для всех НЕ-retina-экранов вернёт 1.0f. Для Retina-экранов вернёт 2.0f. Касается всех iOS-девайсов.
Посему - имея размеры экрана, характерные для форм-фактора (телефон/айпод или планшет) и зная масштаб - мы можем высчитать настоящий размер экрана девайса:
CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
Если выполнить такой код:
CGRect screenBounds = [[UIScreen mainScreen] bounds];
NSLog(@"%f x %f", screenBounds.size.width, screenBounds.size.height);
CGFloat screenScale = [[UIScreen mainScreen] scale];
NSLog(@"%f", screenScale);
CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
NSLog(@"%f x %f", screenSize.width, screenSize.height);
то мы увидим в консоли все размеры. В данном случае - запускал на эмуляторе iPad Retina:
Ну и, собственно, проверяя размеры/тип устройства можно подставлять нужную графику нужных размеров. Profit.
P.S. что касается картинок, то нужно создать всего 2 картинки, например "image.png" и такую же картинку в 2 раза больше с именем "image@2x.png", и дальше пользоваться только первой. Например:
[UIImage imageNamed:@"image.png"];
Если на устройстве Retina - то приложние автоматически подхватит файл с большим разрешением (image@2x.png).
comment commentsПолезная штука Prefix.pch
Открыл для себя полезную штуку в разработке под iOS - файл Prefix.pch. pch - Precompiled Header.
Судя по описанию - Precompiled Headers компилируются, кэшируются и потом автоматически включаются в каждый компилируемый файл. Поэтому, если есть какой-то класс, который нужен везде или в почти везде в проекте - можно внутри Prefix.pch-файла, который автоматически создаётся в новом проекте, сделать include этого класса и он будет доступен везде. Мне вот совсем не нравилось в каждом View Controller'е снова и снова инклудить один класс, который нужен почти везде.
comment commentsУвеличение громкости iPhone 4
У меня iPhone 4 из Франции. В телефонах для продажи во Франции Apple обязали ограничить громкость звука в наушниках, мол, заботятся о своих гражданах и не хотят, чтобы они глохли. Мне, в условиях метро, громкости в итоге не хватает, к тому же наушники бывают разные, какие-то играют громче, у каких-то сопротивление другое и они играют тише.
В общем это ограничение можно обойти. Для этого нужно сделать Jailbreak, после чего получить доступ к файловой системе и изменить пару plist-файлов. Вариантов много - от установки какого-нибудь Терминала через Cydia, до монтирования телефона как раздела на компе. Мне больше нравится второй вариант - поставил Phone Disk - он подклчил мой телефон как раздел, а дальше надо пошариться по файловой системе.
comment commentsAllCafe для iPhone 1.1.2
Выпустили мелкое обновление AllCafe для iPhone. Основные изменений - поддержка iPhone 3G (то бишь поддержка всей 4-й ветки iOS) и сжатие фотографий при добавлении отзывов на клиенте. К тому же теперь они загружаются в правильной ориентации (вертикальной или горизонтальной).
Что приятно - заапрувили всего лишь за сутки обновление. Похоже ревьюверы работают в ускоренном режиме сейчас - с 22 декабря по 29 декабря даже iTunes Connect будет закрыт на праздники.
Этот день для iPad
Запустили с товарищем приложение для iPad под названием "Этот день". С почином меня. Подробнее...
comment commentsВстраивание Facebook sdk в ios-приложение
Шпаргалка. Инструкция по тому, где скачать и как встроить - в документации Facebook. Несмотря на то, что обновляли они проект на Github недавно (23 ноября на данный момент), в нагрузку идёт у них старая версия фреймворка для работы с JSON. А, т.к. в мой проект уже встроена более новая версия фреймворка приложение не компилилось.
Решение:
- после добавления SDK в проект удалить папку JSON из SDK Facebook;
- в файле FBRequest.m заменить строку #import "JSON.h" на #import "SBJson.h";
- в том же файле строку SBJSON *jsonParser = [[SBJSON new] autorelease] заменить на SBJsonParser *jsonParser = [[SBJsonParser new] autorelease].
Должно работать.
comment commentsПриложение AllCafe для iPhone
Мы наконец-то сделали первую версию AllCafe для iPhone, которую уже можно показывать.
Сколько раз вы попадали в ситуацию, когда приходишь в заведение - а там нет мест или просто вам там сегодня не нравится? Я - довольно часто. И первая мысль - а где можно ещё посидеть/поесть/попить рядом?
На нашем портале AllCafe.ru собрана довольно большая база ресторанов по всей России. А также отзывов пользователей о них. Мы сделали приложение для iPhone, которое, кстати, весил всего около 200 кб. С его помощью можно посмотреть на карте ближайшие заведения, либо увидеть их списком с примерным расстоянием.
О каждом заведении можно посмотреть краткую информацию - адрес, телефон (само собой можно сразу набрать номер из приложения), что за кухня в этом заведении, почитать новости о заведении, и, что немаловажно, почитать отзывы - что люди пишут и стоит ли туда идти.
Помимо этого можно самому написать отзыв о заведении. Например, сидите вы весь довольный (или недовольный) в ресторане - открываете и добавляете отзыв.
Это пока только то, что на данный момент есть в приложении. Список запланнированных фич довольно большой и работы мне хватит надолго.
Собственно - это моё первое приложение для iOS. С почином меня.
Ссылка на приложение в iTunes: AllCafe для iPhone.
comment commentsИспользование UINavigationController вместе с UITabBarController
Появилась нужна использовать в проекте и UINavigationController, и UITabBarController одновременно. Два способа как это сделать.
comment commentsMac OS X Lion и iCloud
Ну вот, вышла новая макось с поддержкой iCloud, да и сам iCloud вышел из beta. Кстати, в этом посте я плюю на ваш траффик, но заботливо предупреждаю - скрины в png с размером от 150 кб до 1 мб =)
comment commentsРабота с JSON (парсинг) в Objective-C при разработке под iOS
Ещё один пост, чтобы у самого в голове улеглось. Про работу с JSON в Objective-C на примере парсинга твитов из публичной ленты Twitter.
comment commentsРезюме встречи Apple Developers Community #7
Побывал сегодня на Apple Developers Community #7. Рассказывали про продвижение ios-приложений. По сути докладывались два человека - представитель компании Nevosoft, которая, похоже, на конвеере фигачит игры, и один инди-разработчик.
comment comments