31 декабря 2010 г.

Конвертирование "человеческого" адреса в широту и долготу

Что б отобразить что-то на карте (MKMapView) нужно знать координаты (широту и долготу) точки в которой мы хотим отобразить элемент. У меня возникла ситуация когда в качестве координаты у меня есть только почтовый адрес. Сервисов преобразования почтового адреса в широту и долготу довольно много, я использовал "Yahoo! Maps Web Services - Geocoding API"

Функция конвертации выглядит так:
- (void) convertAddress:(NSString *) address
{
NSError *err = nil;
NSString *url = [NSString stringWithFormat:@"http://api.maps.yahoo.com/ajax/geocode?appid=onestep&qt=1&id=m&qs=%@",
[self convertChars:address]];
NSString *str = [NSString stringWithContentsOfURL:[NSURL URLWithString:url]
encoding:NSUTF8StringEncoding
error:&err];
NSLog(@"%@", str);
CGFloat lat = [[self getAttr:@"Lat" fromString:str] floatValue];
CGFloat lon = [[self getAttr:@"Lon" fromString:str] floatValue];
if (fabs(lat)>.0001 || fabs(lon)>.0001)
{
MKCoordinateRegion region;
region.center.latitude = lat;
region.center.longitude = lon;
region.span.latitudeDelta = 0.1;
region.span.longitudeDelta = 0.1;
[mapView setRegion:region];
}
}


Для начала конвертируем символы адреса, так как адрес передаем в GET запросе:

- (NSString *) convertChars:(NSString *) str
{
NSMutableString *rv = [str mutableCopy];
[rv replaceOccurrencesOfString:@" "
withString:@"+"
options:0
range:NSMakeRange(0, [rv length])];
[rv replaceOccurrencesOfString:@","
withString:@""
options:0
range:NSMakeRange(0, [rv length])];
[rv replaceOccurrencesOfString:@"."
withString:@""
options:0
range:NSMakeRange(0, [rv length])];
return [rv autorelease];
}


Результатом запроса будет javascript строка, и из нее выделяем долготу и широту:

- (NSString *) getAttr:(NSString *) attr fromString:(NSString *) str
{
NSString *rv = nil;
NSRange rng = [str rangeOfString:[NSString stringWithFormat:@"\"%@\":", attr]];
if (rng.location != NSNotFound)
{
rng.location = rng.location +rng.length;
rng.length = 0;
unichar ch = [str characterAtIndex:rng.location+rng.length];
while ((rng.location+rng.length)<[str length] && (ch!=',' && ch!='}'))
{
rng.length++;
ch = [str characterAtIndex:rng.location+rng.length];
}
rv = [str substringWithRange:rng];
}
return rv;
}


В качестве примера обновил проект на github

Так же смотрите:
- MapKit в iOS

16 декабря 2010 г.

Градиент на UINavigationBar

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

Будем модифицировать метод drawRect: компоненты UINavigationBar

  1. - (void) drawRect:(CGRect) rect
  2. {
  3. // подготовка контекста для рисования
  4. CGContextRef context = UIGraphicsGetCurrentContext();
  5. CGFloat locations[2] = { 0.0, 1.0 };
  6. CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
  7. // создаем и рисуем градиент
  8. CGFloat components[8] = COLOR_COMPONENTS;
  9. CGGradientRef gradient = CGGradientCreateWithColorComponents(myColorspace, components, locations, 2);
  10. CGContextDrawLinearGradient(context, gradient, CGPointMake(0, 0), CGPointMake(0,self.frame.size.height), 0);
  11. CGGradientRelease(gradient);
  12. CGColorSpaceRelease(myColorspace);
  13. // верхнюю линию компоненты делаем белой
  14. CGContextSetRGBStrokeColor(context, 1, 1, 1, 1.0);
  15. CGContextMoveToPoint(context, 0, 0);
  16. CGContextAddLineToPoint(context, self.frame.size.width, 0);
  17. CGContextStrokePath(context);
  18. // нижнюю линию закрашиваем черным цветом
  19. CGContextSetRGBStrokeColor(context, 0, 0, 0, 1.0);
  20. CGContextMoveToPoint(context, 0, self.frame.size.height);
  21. CGContextAddLineToPoint(context, self.frame.size.width, self.frame.size.height);
  22. CGContextStrokePath(context);
  23. }


Пример можно скачать с github

11 декабря 2010 г.

Шрифты в iOS

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

Пример можно скачать.

Работает все просто. У класса UIFont есть методы familyNames: и fontNamesForFamilyName:. Первый возвращает массив имен семейств шрифтов в системе, а второй массив имен шрифтов по семейству шрифта.

6 декабря 2010 г.

SVN добавление файла в репозиторий с "@" в имени

Столкнулся с проблемой добавления файла в svn репозиторий:

  1. $ svn add ./file\@2x.png
  2. svn: warning: 'file' not found


решается просто, нужно добавить как завершающий символ в названии файла "@":

  1. $ svn add ./file\@2x.png@
  2. A (bin) file@2x.png

2 декабря 2010 г.

Поддержка Retina-display в приложении

С выходом iPhone 4 программистам нужно в своих программах добавлять поддержку нового разрешения экрана Retina. Для поддержки этого разрешения в вашем приложении необходимо выполнить следующие условия:

- Перерисовать все изображения которые вы используете в качестве ресурсов
- Добавить иконку приложения в высоком разрешении
- Если вы используете векторную графику фраемворков Core Graphics или UIKit, то можете улучшить детализацию, смотри “Updating Your Custom Drawing Code
- Если вы используете Core Animation, то возможно вам понадобится изменить параметры маштабирования перед отрисовкой слоев “Accounting for Scale Factors in Core Animation Layers.
- Если вы используете Open GL ES для рисования, то для отрисовки в высоком разрешении вам понадобится маштабирование вашего слоя, как описано в “Drawing High-Resolution Content Using OpenGL ES.
- Если вы создаете изображения программно, то понадобится изменить код создания изображения, как описано в “Creating High-Resolution Bitmap Images Programmatically.”

Для начала опишу первые два способа.

Допустим у вас уже есть изображения оптимизированные под стандартное разрешение и под retina-display. Как же нам из программы узнать какое изображение загрузить? Ответ прост: программисту можно не знать на каком устройстве запущенно приложение. iOS может может сама выбрать нужное изображение в зависимости от устройства, на котором запущенна программа. Изображение только нужно назвать в соответствии с форматом:

- для стандартного резрешения iPhone/iPod Touch файл ресурса изображения должен иметь вид: <ImageName><device_modifier>.<filename_extension>
- для высокого (retina) разрешения: <ImageName>@2x<device_modifier>.<filename_extension>

где, ImageName - имя изображения, filename_extension - расширение, device_modifier - необязательный параметр, который указывает тип устройства для которого сделан ресурс (может быть "~iphone" и "~ipad").

Так например, если у вас ресурс для стандартного разрешения iPhone называется "image.png", то для iPhone 4 должен быть ресурс под названием "image@2x.png", если iOS его не находит, то берется ресурс для стандартного разрешения ("image.png").

так, читаем ресурс:

  1. UIImage* anImage = [UIImage imageNamed:@"image.png"];


кстати, с прошивки iOS 4.0, расширение ресурса (".png") можно не указывать, но я советую оставлять для совместимости с iPhoneOS 3.x.


Теперь о иконках приложения

С версии iOS 3.2 в файле Info.plist приложения появилась возможность задавать массив файлов иконок приложения в параметре CFBundleIconFiles (не путайте с CFBundleIconFile). В этом массиве можно перечислить список файлов иконок с разным разрешением, и система сама подберет оптимальную иконку для устройства на котором запущено приложение.

Список разрешений иконок приложения:
57х57 иконка для iPhone и iPod Touch стандартного разрешения экрана
114х114 (@2x)иконка для iPhone и iPod Touch с retina экраном
72х72iPad иконка
29х29иконка для iPhone и iPod Touch стандартного разрешения экрана, которая отображается в приложении Settings и при поиске
58х58 (@2x)иконка для iPhone и iPod Touch с retina экраном, которая отображается в приложении Settings и при поиске
50х50iPad иконка приложения в результатах поиска


По материалам "Supporting High-Resolution Screens"

28 ноября 2010 г.

Кастомные заголовки и подписи в секциях UITableView

Иногда необходимо что б в заголовке секции таблицы UITableView отображался не только текст, но и картинка, или может даже кнопка. Компонента UITableView позволяет не только задавать текстовую строку как заголовок к каждой секции но и устанавливать свое UIView как элемент заголовка.

UITableView поддерживает отсылку событий по двум протоколам UITableViewDataSource и UITableViewDelegate. Первый отвечает за получение данных которые отображаются в компоненте, второй за их внешний вид. В протоколе UITableViewDataSource есть методы – tableView:titleForHeaderInSection: и – tableView:titleForFooterInSection: которые должны возвращать строки заголовка и подписи для каждой секции в таблице. Но в протоколе UITableViewDelegate можно реализовать методы – tableView:viewForHeaderInSection: и – tableView:heightForHeaderInSection: для установки вашего вью на заголовок секции (методы – tableView:viewForFooterInSection: и – tableView:heightForFooterInSection: для подписи секции)

Реализуем простой пример использования (скачать можно отсюда)

У нас есть UIViewController на котором находится UITableView. Создаем вью которое будем использовать как заголовок секции таблицы.

  1.   CGRect rct;
  2.   headerView = [[UIView alloc] initWithFrame:CGRectZero];
  3.   UIImage *img = [UIImage imageNamed:@"apple.png"];
  4.   UIImageView *tmpImgView = [[UIImageView alloc] initWithImage:img];
  5.   rct = CGRectMake(screenRect.size.width*.05, 0.0, img.size.width, img.size.height);
  6.   tmpImgView.frame = rct;
  7.   [headerView addSubview:tmpImgView];
  8.   rct.origin.x = 0.0;
  9.   rct.size = CGSizeMake(screenRect.size.width, rct.size.height);
  10.   headerView.frame = rct;
  11.   [tmpImgView release];


тут headerView - имеет тип UIView, его то мы и будем отображать в таблице.

А теперь опишем функции отображение этого вью как заголовка первой секции таблицы:

  1. - (UIView *) tableView:(UITableView *) tableView viewForHeaderInSection:(NSInteger) section
  2. {
  3.   if (section==0)
  4.   {
  5.     return headerView;
  6.   }
  7.   return nil;
  8. }
  9. - (CGFloat) tableView:(UITableView *) tableView heightForHeaderInSection:(NSInteger) section
  10. {
  11.   if (section==0)
  12.   {
  13.     return headerView.frame.size.height;
  14.   }
  15.   return 0.0;
  16. }


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

Так-же в компоненте UITableView можно использовать свойства tableHeaderView  и tableFooterView  для задания вью как заголовка и подписи всей таблицы.

20 ноября 2010 г.

Изменение шрифта в компоненте UISegmentedControl

Если вам не хватает стандартных стилей компоненты UISegmentedControl, то изменение шрифта надписей, это то место, которое не описано в iOS Reference Library. Код для изменения шрифта в компоненте UISegmentedControl взят с форума www.iphonedevsdk.com:

  1. void changeUISegmentFont(UIView* aView)
  2. {
  3.   NSString* typeName = [[aView class] className];
  4.   if ([typeName compare:@"UISegmentLabel" options:NSLiteralSearch] == NSOrderedSame)
  5.   {
  6.     UILabel* label = (UILabel*)aView;
  7.     [label setTextAlignment:UITextAlignmentCenter];
  8.     [label setFont:[UIFont boldSystemFontOfSize:14]];
  9.   }
  10.   NSArray* subs = [aView subviews];
  11.   NSEnumerator* iter = [subs objectEnumerator];
  12.   UIView* subView;
  13.   while (subView = [iter nextObject])
  14.   {
  15.     changeUISegmentFont(subView);
  16.   }
  17. }


параметром этой функции и есть экземпляр обьекта UISegmentedControl.

16 ноября 2010 г.

Пишем бота для twitter на python

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

Приступим к написанию бота. Первое, что необходимо сделать - зарегистрировать приложение на сервисе twitter. В результате получаем Consumer key и Consumer secret.



Теперь разберем сам скрипт. Он написан на основе библиотеки oauth-python-twitter2, задача: фолловить и анфолловить случайных пользователей. Скрипт можно скачать здесь.

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. from oauth import oauth
  4. from oauthtwitter import OAuthApi
  5. import sys
  6. import os
  7. twitter = None
  8. consumerKey = ''
  9. consumerSecret = ''
  10. def auth():
  11.   authToken = None
  12.   authSecret = None
  13.   if os.path.exists('/tmp/twitter.tmp'):
  14.     f = open('/tmp/twitter.tmp', 'r')
  15.     authToken = f.readline().strip()
  16.     authSecret = f.readline().strip()
  17.     print "oauth_token: " + authToken
  18.     print "oauth_token_secret: " + authSecret
  19.     f.close()
  20.   needAuth = True
  21.   if authToken!=None and authSecret!=None:
  22.     twitter = OAuthApi(consumerKey, consumerSecret, authToken, authSecret)
  23.     if twitter.autorized():
  24.       needAuth = False
  25.   if needAuth:
  26.     twitter = OAuthApi(consumerKey, consumerSecret)
  27.     temp_credentials = twitter.getRequestToken()
  28.     print temp_credentials
  29.     print twitter.getAuthorizationURL(temp_credentials)
  30.     oauth_verifier = raw_input('What is the PIN? ')
  31.     access_token = twitter.getAccessToken(temp_credentials, oauth_verifier)
  32.     print access_token
  33.     print("oauth_token: " + access_token['oauth_token'])
  34.     print("oauth_token_secret: " + access_token['oauth_token_secret'])
  35.     f = open('/tmp/twitter.tmp', 'w')
  36.     f.write('%s\n%s'%(access_token['oauth_token'], access_token['oauth_token_secret']))
  37.     f.close()
  38.     twitter = OAuthApi(consumerKey, consumerSecret, access_token['oauth_token'], access_token['oauth_token_secret'])
  39.   return twitter
  40. def unfollow():
  41.   friends = twitter.GetFriendsIDs()
  42.   for i in range(min(len(friends),30)):
  43.     twitter.UnfollowUser(friends[i])
  44. def follow(query):
  45.   users = twitter.searchByQuery(query)
  46.   count = min(30, len(users))
  47.   for user in users:
  48.     print user['id']
  49.     twitter.FollowUser(user['id'])
  50. if __name__=='__main__':
  51.   if len(sys.argv)<2:
  52.     print "Usage: %s <mode>"%sys.argv[0]
  53.     print "Mode:"
  54.     print "  follow   - find and follow by twitter users"
  55.     print "  unfollow - unfollow twitter users"
  56.     print ""
  57.     sys.exit(0)
  58.   if sys.argv[1]=='unfollow':
  59.     twitter = auth()
  60.     unfollow()
  61.   elif sys.argv[1]=='follow':
  62.     twitter = auth()
  63.     follow('twitter')


самое интересное в скрипте, метод auth. В файле /tmp/twitter.tmp я храню токен авторизации пользователя. Если этот токен не существует или не принимается пользователем, то вывожу на экран урл авторизации и жду пока пользователь авторизуеться в твиттере и введет PIN в приложение. По этому пину и получаем токен авторизации который и сохраняем в файл /tmp/twitter.tmp до следующего запуска скрипта.

Методы follow вызывает метод search твиттера и фолловит найденых пользователей. Метод unfollow берет список фолловеров и отписываеться от некоторых из них.

2 ноября 2010 г.

Ссылка на AppStore reviews приложения из приложения

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

Можно попробовать решить сделав переход из вашего приложения на страничку написания ревью к вашему приложению. Как это Вы реализуете: ссылкой в "О программе" или алертом при каждом запуске приложения с просьбой оценить приложение - не важно. Я здесь привожу лишь код сотавления урл-аддреса и открытие его:

  1. NSString *str = @"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa";
  2. str = [NSString stringWithFormat:@"%@/wa/viewContentsUserReviews?", str];
  3. str = [NSString stringWithFormat:@"%@type=Purple+Software&id=", str];
  4. // Здесь добавляем AppleID вашего приложения из iTunesConnect
  5. str = [NSString stringWithFormat:@"%@348529788", str];
  6. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:str]];


В результате переходим в приложение AppStore на сраничку Write review приложения (не забудьте поменять id в коде)



Спасибо memention за инструкцию.

Добавление файлов в библиотеку iTunes без копирования файлов

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

Решение простое - добавлять в iTunes не сами файлы а ссылки на них. Для этого, перетаскиваем с Finder при нажатой клавише Option нужный файл или папку. Вуаля, в библиотеку добавилась ссылка на файл.

1 ноября 2010 г.

Локализация даты/времени

Формат даты/времени не зависит от текущего языка на вашем устройстве. А зависит от региональных настроек:



Для отображения даты/времени я использую UIDatePicker который от региональных настроек устройства отображает дату и время корректно. Если же мне нужна текстовая строка с локализованной датой/временем (для вывода на экран например), то я использую класс NSDateFormatter для получения строки. У обоих классов есть так-же метод setLocale: в котором можно жестко указать в каком региональном формате отображать дату/время.

См. также:
1. Локализация строк в Xcode

26 октября 2010 г.

github

Выложил все примеры которые я приводил в этом блоге на github.com. Пока ссылки в постах не изменяю, там видно будет.

20 октября 2010 г.

Разворачиваем tomcat web-приложения в Linux

Процедура публикации .war файла (tomcat 6)

1. Остановить tomcat
  1. $ sudo /etc/init.d/tomcat6 stop


2. Если приложение было установленно и вы хотите его обновить, удаляем старую версию
  1. $ sudo rm -r $TOMCAT_HOME/webapps/foo


3. Копируем .war файл в папку приложений TOMCAT_HOME/webapps/

4. Запускаем tomcat
  1. $ sudo /etc/init.d/tomcat6 start


TOMCAT_HOME - по умолчанию /var/lib/tomcat6/

19 октября 2010 г.

Upgrade Ubuntu из консоли

1. Устанавливаем пакет update-manager-core, если он еще не стоит:
  1. $ sudo apt-get install update-manager-core


2. Редактируем файл /etc/update-manager/release-upgrades, устанавливаем Prompt=normal

3. Запускаем утилиту апгрейда системы
  1. $ sudo do-release-upgrade


4. Следуем инструкциям

16 октября 2010 г.

Органайзер Xcode говорит "Could not support development"

Сегодня при подключении своего iPhone и попытки скомпилировать и установить на него свое приложение Xcode начал ругаться на то, что к нему не подключено ни одно устройство. Органайзер Xcode видел устройство, но говорил, что оно не может использоваться для разработки (Could not support development). Хотя iTunes синхронизировался хорошо.

Все сертификаты разработчика внешне были в порядке, но для надежности обновил все сертификаты. Проблеммы не решило.

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

15 октября 2010 г.

Локализация строк в Xcode

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

С помощью Xcode можно локализировать:
- статический текст в коде программы
- ресурсы (иконки, рисунки, звуковые файлы, nib-файлы)
- вывод даты/времени

В этой заметке я расскажу о локализации статического текста в коде программы.

Локализация текста в коде программы необходима если, например, в коде программы задается текст который потом показывается пользователю (это может быть название кнопок, текст отчета и т.п.). Перед локализацией необходимо интернационализировать код программы. Для этого любой текст который показывается пользователю необходимо обвернуть макросом NSLocalizedString. Если раньше вы показывали диалог таким способом:

  1. UIAlertView *tmp = [[UIAlertView alloc] initWithTitle:@"Warning" message:@"Your e-mail application not configured" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
  2. [tmp show];
  3. [tmp release];


то теперь показываем так:

  1. UIAlertView *tmp = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Warning", @"") message:NSLocalizedString(@"Your e-mail application not configured", @"") delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil];
  2. [tmp show];
  3. [tmp release];


Макрос NSLocalizedString вызывает метод localizedStringForKey:value:table: обьекта NSBundle приложения, которая возвращает локализированную строку.

Теперь нужно создать файл Localizable.strings, который будет шаблоном для файлов локализации. Для этого в терминале переходим в папку Classes приложения и выполняем команду:

  1. $ genstrings ./*.m


В результате в той же папке будет находиться файл Localizable.strings который и добавляем в проект (кодировку файла указываем UTF-16).



Теперь осталось локализировать этот файл на нужный нам язык. Для этого открываем свойства добавленного в проект файла Localizable.strings.



На вкладке "General" нажимаем кнопку "Маке File Localizable"



Переходим обратно на вкладку "General" и там добавляем локализацию



При этом в проекте создается папка с локализацией для этого языка и в нее помещаются файл Localizable.strings который нужно перевести на указанный язык. В проекте это выглядит так:



Файл Localizable.strings представляет собой пары "ключ" = "значение", где ключом выступает параметр переданный в макрос NSLocalizedString а значением - локализированная строка

  1. "Done" = "Готово";


См. также:
1. Локализация даты/времени

11 октября 2010 г.

♠ Краплёная колода: Угадай мелодию: как работает Shazam?

♠ Краплёная колода: Угадай мелодию: как работает Shazam?: "Технологии способны удивлять. Помню, как мне показали распознавание музыки на айфоне. Про Shazam тогда ещё ничего не знал, впечатления были на уровне американского wow-эффекта. А на днях набрёл на материал, где доступно и понятно рассказано о таинстве фокуса. Есть подозрение, что сеанс разоблачения будет интересен и вам, уважаемые читатели..."

5 октября 2010 г.

Добавление изображений в iPhone Simulator

Если необходимо добавить в iPhone Simulator несколько изображений, то этот способ вам подойдет.
Открываем iPhone Simulator и перетаскиваем в него изображение из Finder. Это изображение откроется в приложении Mobile Safari на iPhone Simulator. Теперь просто кликаем на нем и удерживаем кнопку мыши нажатой до появления вплывающего меню (имитируем долгий тап на изображении).


Теперь нажимаем на "Save Image" и изображение попадает в Фотоальбом.

Если нужно добавить большое кол-во изображений или создавать в симуляторе фотоальбомы, для iPhone Simulator версий 3.х есть довольно простое решение: в папку ~/Library/Application Support/iPhone Simulator/4.1/Media/DCIM/100APPLE поместить два изображения.
первое IMG_хххх.JPG - изображение которое мы помещаем в Фотоальбом симулятора в формате jpeg
второе - IMG_хххх.THM - привью первого изображения в формате jpeg размером 96x96
Скрипт на Python который добавляет изображения в Фотоальбом iPhone Simulator-а.

Для iPhone Simulator версий 4.х нужно дополнительно править sqlite базы.

Так-же можно написать iPhone приложение которое будет в себе как ресурс содержать изображения и в цикле будет добавлять в фотоальбом изображения.

2 октября 2010 г.

Добавление изображений в Фотоальбом

Этот пост посвящен единственной функции из фраемворка UIKit, а именно UIImageWriteToSavedPhotosAlbum. В параметры функции передается изображение класса UIImage, а так-же функция-селектор которая вызовется после добавления изображения в Фотоальбом, либо при возникновении ощибки добавления.

Скачать пример.

29 сентября 2010 г.

О субтитрах формата ttxt

Пример субтитров формата 3GPP timed text:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!-- GPAC 3GPP Text Stream -->
  3. <TextStream version="1.1">
  4. <TextStreamHeader width="320" height="60" layer="0" translation_x="0" translation_y="0">
  5. <TextSampleDescription horizontalJustification="center" verticalJustification="bottom" backColor="0 ff 0 80" verticalText="no" fillTextRegion="no" continuousKaraoke="no" scroll="None">
  6. <FontTable>
  7. <FontTableEntry fontName="Serif" fontID="1"/>
  8. <FontTableEntry fontName="Helvetica" fontID="2"/>
  9. <FontTableEntry fontName="Arial" fontID="3"/>
  10. </FontTable>
  11. <TextBox top="0" left="0" bottom="60" right="320"/>
  12. <Style styles="Normal" fontID="1" fontSize="18" color="ff ff 00 ff"/>
  13. </TextSampleDescription>
  14. </TextStreamHeader>
  15. <TextSample sampleTime="00:00:00.000" xml:space="preserve"></TextSample>
  16. <TextSample sampleTime="00:00:00.800" xml:space="preserve">Детки, в каждой любовной истории
  17. есть два особенных дня:<Style fromChar="10" toChar="46" styles="Italic " fontID="2" fontSize="18" color="ff ff 00 80"/><Style fromChar="16" toChar="40" fontID="1" fontSize="18" color="00 ff 00 80"/>
  18. </TextSample>
  19. <TextSample sampleTime="00:00:03.794" xml:space="preserve"></TextSample>
  20. <TextSample sampleTime="00:00:03.895" xml:space="preserve">День, когда встречаешь
  21. девушку своей мечты,<Style fromChar="0" toChar="43" styles="Italic " fontID="3" fontSize="18" color="ff ff ff ff"/>
  22. </TextSample>
  23. <TextSample sampleTime="00:00:06.246" xml:space="preserve"></TextSample>
  24. <TextSample sampleTime="00:00:06.247" xml:space="preserve">и день, когда женишься на ней.<Style fromChar="0" toChar="30" styles="Italic " fontID="1" fontSize="18" color="ff ff ff ff"/>
  25. </TextSample>
  26. <TextSample sampleTime="00:00:07.882" xml:space="preserve"></TextSample>
  27. </TextStream>

хорошее описание тэгов есть здесь. Для меня интересен тэг TextSample, так как он может содержать в себе тэг Style, что в свою очередь дает возможность изменят цвет субтитра и его стиль. Так-же MP4Box хорошо вставляет субтитры этого формата в контейнер MPEG 4, который потом прогрывается на iPhone. Правда iPhone так-же как и с SubRip Text субтитрами не понимает полужирный текст и изменение шрифтов.

28 сентября 2010 г.

О субтитрах формата srt

Формат SubRip Text пожалуй самый популярный из форматов субтитров. Файл этого формата имеет расширение .srt и состоит из последовательности блоков. Каждый блок имеет формат:

  1. <Номер блока>
  2. <Время начала показа субтитра> --> <Время конца показа субтитра>
  3. Текст субтитра (одна или несколько строк)
  4. <пустая строка>


Номер блока - целое число начинающееся с 1, время начала и конца показа субтитра имеет формат HH:MM:SS,milliseconds, пустая строка необходима для отделения блоков в файле и текст субтитра не может содержать пустых строк.

Пример файла:

  1. 1
  2. 00:00:20,000 --> 00:00:24,400
  3. Бла-<font color="#4096d1">бла</font>-бла
  4. <i>ыва</i>


  5. 2
  6. 00:00:24,600 --> 00:00:27,800
  7. йцукен


текст субтитра может содержать тэги, и многие плееры их подтерживают. Мне извесны такие тэги в тексте субтитра:
<b>…</b> - полужирный
<i>…</i> - наклонный
<u>…</u> - подчеркнутый шрифты
<font color="#RRGGBB">…</font> - задает цвет текста

Что интересно, при кодировании видео для iPhone и добавлении субтитров SubRip Text формата (использовался MP4Box) iPhone корректно отображал тэги в субтитрах (кроме <b>)

23 сентября 2010 г.

"Привязываем" UISearchBar к UITableView

У меня возникла задача использовать фильтр к большому списку однотипных элементов (проще говоря поиск). Раньше я решал такую задачу кидая на UINavigationBar элемент UITextField и обрабатывал его события, но выглядело это очень некрасиво. UISearchBar хоть и занимает дополнительное место на экране, но выглядит приятно и вписывается в интерфейс, хотя при редактировании в UISearchBar можно скрывать UINavigationBar для освобождения пространства на экране.

Как я реализовывал.
Есть класс наследник от UIViewController в котором находятся визуальные компоненты (UISearchBar и UITableView). Так-же этот класс содержит два массива: массив со всеми элементами которые могут присутствовать в списке, второй с элементами которые удовлетворяют условиям поиска. Изначально массивы равны. Второй массив необходимо отображать в UITableView и применять к нему фильтр при изменении текста в UISearchBar. Просто?

Начнем с функции поиска, в моем случае я у меня строковые массивы (itemsList - со всеми элементами, searched - с элементами которые удовлетворяют условию из UISearchBar). Единственным параметр функции - строка из UISearchBar:

  1. - (void) search:(NSString *) matchString
  2. {
  3.   NSString *upString = [matchString uppercaseString];
  4.   if (searched)
  5.     [searched release];
  6.   searched = [[NSMutableArray alloc] init];
  7.   for (NSString *line in itemsList)
  8.   {
  9.     if ([matchString length] == 0)
  10.     {
  11.       [searched addObject:line];
  12.       continue;
  13.     }
  14.     NSRange range = [[line uppercaseString] rangeOfString:upString];
  15.     if (range.location != NSNotFound)
  16.       [searched addObject:line];
  17.   }
  18.   [tv reloadData]; // UITableView
  19. }


я тут не боролся за оптимальность поиска. Можно было б не пересоздавать при каждом вызове массив searched, а так-же учитывать предыдущий поиск.

Функция search: должна вызываться при каждом изменении в UISearchBar, для которого нужно определить делегат с методом searchBar:textDidChange:

  1. - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
  2. {
  3.   [self search:searchText];
  4. }


так-же для иницилизации массива searched метод search: нужно вызвать при загрузке вью конттроллера, например в методе loadView

  1. - (void)loadView
  2. {
  3.   [self search:@""];
  4. }


и осталось отобразить массив searched в UITableView

  1. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. {
  3.   UITableViewCell *rv = nil;
  4.   NSString *cellID = @"cell_ID";
  5.   rv = [tableView dequeueReusableCellWithIdentifier:cellID];
  6.   if (rv==nil)
  7.   {
  8.     rv = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellID] autorelease];
  9.   }
  10.   rv.textLabel.text = [searched objectAtIndex:indexPath.row];
  11.   return rv;
  12. }


В статье не описано, что нужно обрабатывать появление клавиатуры, а так-же можно скрывать UINavigationBar при фокусе на UISearchBar, что есть в примере.

22 сентября 2010 г.

Изменение размера системного раздела в Ubuntu Linux

Есть у меня на работе несколько виртуальных серверов под управлением Ubuntu Linux для локальных задач которые зависят от конкретных версий пакетов. Сегодня на одном из серверов закончилось дисковое пространство, проблема усугубилась тем, что в этой виртуальной машине диск имеет один единственный раздел, т.е. изменять размер прийдется системному разделу.

Как увеличить размер диска с помощью средств виртуализации я не буду, там все просто. После этой операции Ubuntu видит увеличенный обьем диска как неразмеченную область, нужно добавить эту область к уже существующему разделу. Для этого нужно загрузить виртуальную машину с установочного диска (у меня это Ubuntu 9.10 Desktop Edition).

Изменять размер раздела будем с помощью утилиты gparted (графическая надстройка над консольной parted), я пробовал parted, но с первого раза у меня не получилось изменить размер и поэтому я выбрал графическую версию, для меньшей вероятности потерять данные.


Выбираем нужный раздел диска и в сплывающем меню выбираем "Resize/Move".


двигаем ползунков размера диска покрывая неразмеченую область диска. Сохраняем изменения на диск.

Все. После перезагрузки машины размер вашего диска увеличился.

20 сентября 2010 г.

Запуск приложения в "ландшафтном" режиме

Иногда необходимо, что б iPhone приложение запускалось в горизонтальном (ландшафтном) режиме. Все довольно просто, и ограничивается правкой файла Info.plist приложения. Добавляем в него ключ UIInterfaceOrientation со значением UIInterfaceOrientationLandscapeLeft или UIInterfaceOrientationLandscapeRight.



не забывайте, что метод shouldAutorotateToInterfaceOrientation: вью-контоллеров приложения должны возвращать YES для выбранной ориентации, и элементы во вью должны правльно изменять размер при изменении ориентации устройства.

Пример приложения

14 сентября 2010 г.

Бекапы подключенных к iTunes устройств

При синхронизации с устройством (iPhone|iPod Touch|iPad) iTunes делает бекап файлов настройки устройства а так-же данные приложений на установленных на устройстве.

Лежат эти бекапы в папке:
~/Library/Application Support/MobileSync/Backup/

13 сентября 2010 г.

Даунмикс аудио 5.1 в стерео (ffmpeg)

Даунмикс мне необходим для конвертирования видео для iPhone/iPod Touch.
Использую ffmpeg:

  1. $ ffmpeg -y -i <input_audio> -map 0.1 -vn -acodec libfaac -ab 128k -ac 2 -ar 41000 -strict experimental <out.aac>



некоторые типы аудио у меня ffmpeg незахотел конвертировать командой выше (например "Stream #0.0: Audio: aac, 48000 Hz, 5.1, s16, 148 kb/s"). Из-за этого пришлось конвертировать через временный файл

  1. $ ffmpeg -y -i <input_audio> -map 0.1 -vn -acodec ac3 -ab 448k -ar 44100 -ac 6 ./tmp.ac3
  2. $ ffmpeg -y -i ./tmp.ac3 -vn -acodec libfaac -ab 128k -ar 44100 -ac 2 <out_audio>.aac


Если есть более лучший вариант, пишите.

10 сентября 2010 г.

Подключение проекта статической библиотеки к вашему проекту в xCode

Сейчас я не буду рассказывать о прелестях использования статических библиотек при программировании для iPhone, а просто опишу как подключить проект статической библиотеки к вашему проекту. Итак, у вас имеется проект статической библиотеки и проект (буду его называть проектом приложения) в который вы хотите ее подключить.

Открываем в xCode проект приложения. В Finder переходим в папку проекта статической бибилотеки и перетаскиваем файл проекта в xCode


Идем в меню Project->Edit Active Target "…". В появившемся окне жмем на "+" на вкладке "General" в группе "Direct Dependencies" и выбираем статическую библиотеку только-что добавленную в проект.


после добавления


а так выглядит "Group & Files" в окне проекта приложения


теперь нужно сказать xCode, что при сборке этого таргета необходимо прилинковывать нашу статическую библиотеку. Для этого перетаскиваем библиотеку и проекта библиотеки в секцию "Link Binary With Libraries" таргета приложения.


теперь можно добавить заготовочные файлы библиотеки в проект приложения и запускать приложение.

Если ваша статическая библиотека использует классы ObjectiveC, то в свойствах проекта добавте ключ -ObjC к "Other Linker Flags"



Пример.

8 сентября 2010 г.

Извлечение постера сериала/фильма из iTunes Store

Мне сегодня понадобился постер сериала из iTunes Store. Я знал, что есть у каждого постера постоянная http-ссылка, но инструкции как ее получить на просторах Всемирной паутины не нашел. Пришлось разбираться самому.

Итак имеем телешоу в iTunes Store, копируем ссылку на него прямо из iTunes:


Открываем ссылку в браузере


теперь нужно скопировать ссылку на постер размером 100х100:


имеем ссылку типа:

http://a1.phobos.apple.com/us/r30/Features/a2/76/0d/dj.wvuibynu.100x100-75.jpg

жирным выделен размер изображения. Существует несколько размеров изображений, нам необходим максимальный, для фильмов это 227х227, для телешоу - 600х600. Получаем:

http://a1.phobos.apple.com/us/r30/Features/a2/76/0d/dj.wvuibynu.600x600-75.jpg

PS: Найдены еще такие размеры постеров (некоторых может не быть):
110x110
140x140
167x167
170x170
176x176
182x182
200x200
225x225
227x227
600х600

3 сентября 2010 г.

Создание шаблонов приложений в xCode (часть 2)

В первой части я рассказывал о создании проекта в шаблоне xCode. В этой части разберем файл TemplateChooser.plist и отличие проекта шаблона от обычного проекта.

В файле TemplateChooser.plist расположено описание окна "New Project" выбраного шаблона, а так же набор правил выбора проекта из шаблона в зависимости от выбраных пользователем параметров шаблона.


Приведенный пример представлен хешом из 4-х элементов: "Checkboxes", "OptionsLabel", "PopupMenu" и "TemplateSelection". Первые три отвечают за внешний вид она "New Project" при выбраном шаблоне, TemplateSelection - за правила выбора проекта из шаблона.

Секции Checkboxes это массив чекбоксов, а PopupMenu отвечает за пункты во всплывающем списке при выборе шаблона. OptionsLabel отвечает только за заголовок. Каждый элемент этих массивов имеет уникальный идинтификатор (Key) и строку описания(Title) которая выводится как имя элемента.

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

Например в таком варианте:


будет использоваться ключ "iPhoneStorage"

Сам проект в шаблоне тоже немного отличается от стандартного проекта. В папке проекта содержаться два файла: TemplateIcon.icns и TemplateInfo.plist. Первый - иконка шаблона, второй - краткое описание шаблона.