Показаны сообщения с ярлыком iOS. Показать все сообщения
Показаны сообщения с ярлыком iOS. Показать все сообщения

27 апреля 2013 г.

Смена ориентации экрана в модальном окне iOS 5/6

Случилось мне недавно делать приложение для iOS, которое работает в портретной ориентации экрана и только одно вью (которое отображалось модально) нужно было отображать только в ландшафтной ориентации экрана. Что ж, решение для iOS 5.х довольно простое: в методе - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation модального окна просто прописываем нужный разворот устройства

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
    return (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight);
}


Для iOS 6.x все оказалось немного сложнее:

1. В классе вью контроллера модального окна определяем обработчики методов

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
 return UIInterfaceOrientationLandscapeRight;
}

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskLandscape;
}

- (BOOL)shouldAutorotate
{
    return YES;
}


2. Сразу после вызова показа модального окна устанавливаем положение статус бара:

 [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:NO];


3. Перед тем, как убрать модальное окно, возвращаем статус бар в нужное положение:

 [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait animated:NO];


Как обычно пример на github-е.

16 декабря 2012 г.

UIDatePicker как клавиатура для UITextField

Цель: сделать удобной выбор даты в UITextField, будем использовать UIDatePicker
Алгоритм: перехватим событие - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField у UITextField и вместо показа системной клавиатуры, покажем наше вью.

Думаю не стоит описывать процесс создания и назначения делегата у UITextField, это можно сделать как в Interface Builder-е так и динамически. Сразу смотрим метод делегата -(void)textFieldDidBeginEditing:(UITextField *)textField:

- (void)textFieldDidBeginEditing:(UITextField *)aTextField
{
 [aTextField resignFirstResponder];
 
 [_pickerViewPopup release];
 _pickerViewPopup = [[UIActionSheet alloc] initWithTitle:nil delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
 
 [_pickerView release];
 _pickerView = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0, 44.0, 0.0, 0.0)];
 _pickerView.datePickerMode = UIDatePickerModeDate;
 _pickerView.hidden = NO;
 _pickerView.date = [NSDate date];
 [_pickerView addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
 
 UIToolbar *pickerToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
 pickerToolbar.barStyle = UIBarStyleBlackOpaque;
 [pickerToolbar sizeToFit];
 
 NSMutableArray *barItems = [[NSMutableArray alloc] init];
 
 UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
 [barItems addObject:flexSpace];
 [flexSpace release];
 
 UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneButtonPressed:)];
 [barItems addObject:doneBtn];
 [doneBtn release];
 
 UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelButtonPressed:)];
 [barItems addObject:cancelBtn];
 [cancelBtn release];
 
 [pickerToolbar setItems:barItems animated:YES];
 [barItems release];
 
 [_pickerViewPopup addSubview:pickerToolbar];
 [_pickerViewPopup addSubview:_pickerView];
 [_pickerViewPopup showInView:self.window];
 [_pickerViewPopup setBounds:CGRectMake(0.0, 0.0, 320.0, 464.0)];
 [pickerToolbar release];
}


тут мы создаем модальное окно, на основе UIActionSheet и в него вставляем UIDatePicker и UIToolbar с кнопками "Ok" и "Cancel".

И события нажатия на эти кнопки:

- (void)doneButtonPressed:(id)sender
{
 self.dt = [_pickerView date];
 
 [_pickerViewPopup dismissWithClickedButtonIndex:1 animated:YES];
 [_pickerView removeTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}

- (void)cancelButtonPressed:(id)sender
{
 [_pickerViewPopup dismissWithClickedButtonIndex:1 animated:YES];
 [_pickerView removeTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
}


В обоих случаях скрываем наше "модельное" окно и отписываемся от события получения нотификации о изменении даты на UIDatePicker. А при нажатии на "Ok" так-же сохраняем выбранную дату на "барабане".

И событие изменения даты на UIDatePicker для изменения даты так-же в текстовом поле:

- (void)dateChanged
{
 NSDateFormatter *df = [[NSDateFormatter alloc] init];
 [df setDateFormat:@"dd.MM.yy"];
 _tf.text = [df stringFromDate:[_pickerView date]];
}


Проект на github-е. Утащено с stackoverflow.

9 декабря 2012 г.

Применение изображения-маски на UIView

После длительного перерыва решил опубликовать новый пост в блоге.

Традиционно все UIView в iOS имеют прямоугольную форму. И с помощью полупрозрачного цвета (так-же изображений с alpha каналом) и порядком (z-order) наложения вью друг на друга можно добиваться практически всего, что можно вообразить. Но вот понадобился мне полупрозрачный свой UISwitch в котором был предопределен порядок вьюшек.

И что б этот пост не был постом об одном методе, опишу процесс создания компоненты.

Есть изображения:

1. подложка компоненты

2. маска под эту подложку

3. включенное и выключенное состояния

Наследоваться будем от UIView.

В методе init будем создавать вью которые нам и понадобятся.

  _backgroundImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"butt_bg"]];
  [self addSubview:_backgroundImage];


создаем подложку компоненты и добавляем ее на родительское вью

  UIView *maskedView = [[UIView alloc] initWithFrame:self.bounds];
  maskedView.backgroundColor = [UIColor clearColor];
  [self addSubview:maskedView];


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

  _backgroundView = [[UIScrollView alloc] initWithFrame:self.bounds];
  _backgroundView.backgroundColor = [UIColor clearColor];
  _backgroundView.clipsToBounds = YES;
  _backgroundView.delegate = self;
  _backgroundView.clearsContextBeforeDrawing = NO;
  _backgroundView.pagingEnabled = YES;
  _backgroundView.showsVerticalScrollIndicator = NO;
  _backgroundView.showsHorizontalScrollIndicator = NO;
  _backgroundView.scrollsToTop = NO;
  _backgroundView.contentSize = CGSizeMake(105.0, _backgroundView.bounds.size.height);
  _backgroundView.bounces = NO;
  [maskedView addSubview:_backgroundView];
  
  _onLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  _onLabel.backgroundColor = [UIColor clearColor];
  _onLabel.opaque = NO;
  _onLabel.textColor = [UIColor blackColor];
  _onLabel.font = [UIFont boldSystemFontOfSize:16.0];
  _onLabel.textAlignment = UITextAlignmentCenter;
  _onLabel.text = NSLocalizedString(@"ON", nil);
  _onLabel.frame = CGRectMake(0.0, 0.0, 43.0, _backgroundView.frame.size.height);
  [_backgroundView addSubview:_onLabel];

  UIImage *img = [UIImage imageNamed:@"r_butt_on"];
  _ballImage0 = [[UIImageView alloc] initWithImage:img];
  _ballImage0.frame = CGRectMake((_backgroundView.contentSize.width-img.size.width)/2.0,
           (_backgroundView.contentSize.height-img.size.height)/2.0,
           img.size.width, img.size.height);
  [_backgroundView addSubview:_ballImage0];
  
  img = [UIImage imageNamed:@"r_butt_off"];
  _ballImage1 = [[UIImageView alloc] initWithImage:img];
  _ballImage1.frame = _ballImage0.frame;
  _ballImage1.alpha = 0.0;
  [_backgroundView addSubview:_ballImage1];
  
  _offLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  _offLabel.backgroundColor = [UIColor clearColor];
  _offLabel.opaque = NO;
  _offLabel.textColor = [UIColor blackColor];
  _offLabel.font = [UIFont boldSystemFontOfSize:16.0];
  _offLabel.textAlignment = UITextAlignmentCenter;
  _offLabel.text = NSLocalizedString(@"OFF", nil);
  _offLabel.frame = CGRectMake(60.0, 0.0, 43.0, _backgroundView.frame.size.height);
  [_backgroundView addSubview:_offLabel];


тут _backgroundView - скролл который будет анимированно двигаться, котда будем прикасаться к нему, на нем лежит две текстовые метки: _onLabel и _offLabel которые просто отображают текст и вьюшки с включенной и выключенной лампочками (_ballImage0 и _ballImage1).

А вот и подобрались к самому главному, создаем слой маски из изображения и применяем его к вью:

  UIImage *_maskingImage = [UIImage imageNamed:@"butt_mask"];
  CALayer *_maskingLayer = [CALayer layer];
  _maskingLayer.frame = CGRectMake(1.0, 1.0, self.bounds.size.width-2.0, self.bounds.size.height-2.0);
  [_maskingLayer setContents:(id)[_maskingImage CGImage]];
  maskedView.layer.mask = _maskingLayer;



Регистрируем слушателя на касание внутри нашей компоненты:

UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; [self addGestureRecognizer:singleFingerTap]; [singleFingerTap release];

и обработчик этого одиночного касания (до этого мы все делали в методе init):

- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer
{
 [self setOn:!_on animated:YES];
}


так-же методы установки/получения состояния компоненты:

- (void)setOn:(BOOL)on animated:(BOOL)animated
{
 _on = on;
 _scrollingAnimation = animated;
 [_backgroundView setContentOffset:CGPointMake(on?0.0:_backgroundView.contentSize.width-self.bounds.size.width, 0.0) animated:animated];
 _ballImage0.alpha = _on?1.0f:.0f;
 _ballImage1.alpha = _on?.0f:1.0f;
}

- (void)setOn:(BOOL)on
{
 [self setOn:on animated:NO];
}

- (BOOL)on
{
 return _on;
}



тут в зависимости от состояния меняем прозрачность наших изображений _ballImage0 и _ballImage1 ну и храним состояние в переменной _on

и обработчики перемещения UIScrollView пользователем:

- (void)scrollViewDidScroll:(UIScrollView *)sender
{
 CGFloat koof = sender.contentOffset.x/(sender.contentSize.width-sender.bounds.size.width);
 _ballImage0.alpha = 1.0f-koof;
 _ballImage1.alpha = koof;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
 if (!_scrollingAnimation)
 {
  self.on = _backgroundView.contentOffset.x<1.0;
 }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
 if (!decelerate && !_scrollingAnimation)
 {
  self.on = _backgroundView.contentOffset.x<1.0;
 }
}


Вот и все, проект можно посмотреть на github-е, в нем специально закомментирована строка _backgroundView.bounces = NO;, для более наглядной демонстрации эффекта.

ЗЫ: Вот еще одно интересное применение маски.

6 марта 2012 г.

Перехват двойного тапа у UIWebView

В классе, который будет обрабатывать двойной тап по UIWebView регистрируем обработчик двойного тапа, если это UIViewController то, например, в методе viewDidLoad:
- (void)viewDidLoad
{
    UITapGestureRecognizer *doubleFingerTap = [[UITapGestureRecognizer alloc]
                                                initWithTarget:self action:@selector(handleDoubleTapGesture:)];
    doubleFingerTap.numberOfTapsRequired = 2;
    doubleFingerTap.delegate = self;
    [self.webView addGestureRecognizer:doubleFingerTap];
    [doubleFingerTap release];
}

и
#pragma mark Gesture recognizer delegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 
{
    return YES;
}

так-же и сам метод, который будет вызываться при двойном тапе:
- (void) handleDoubleTapGesture:(UITapGestureRecognizer *) gestureRecognizer
{
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
 {
...
 }
}

Ну и на последок, в *.h-файл нашего класса, добавляем описание поддержки протокола
<UIGestureRecognizerDelegate>

1 октября 2011 г.

UIGestureRecognizer перехват одиночного касания и двойного на одном UIView

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

В этом случае нужно запретить в UIGestureRecognizer одиночного тапа посылку события при двойном тапе. Делается просто методом - (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer.

Пример:
        UIGestureRecognizer *doubleTapGestureRecognizer = [[UIGestureRecognizer alloc]
                                initWithTarget:self action:@selector(handleDoubleTap:)];
        doubleTapGestureRecognizer.numberOfTapsRequired = 2;
        [self addGestureRecognizer:doubleTapGestureRecognizer];

        UIGestureRecognizer *singleTapGestureRecognizer = [[UIGestureRecognizer alloc]
                                                              initWithTarget:self action:@selector(handleSingleTap:)];
        singleTapGestureRecognizer.numberOfTapsRequired = 1;
        [singleTapGestureRecognizer requireGestureRecognizerToFail:doubleTapGestureRecognizer];
        [self addGestureRecognizer:singleTapGestureRecognizer];

17 июня 2011 г.

Запрет лока экрана при запущенном приложении в iOS

[UIApplication sharedApplication].idleTimerDisabled = YES;

Рисование под статус баром в iOS

Полезно иногда просматривать официальную документацию.

В моем приложении, так-же как и в стандартном Photos необходимо убирать статус бар при тапе на экран, но так, что б вью на экране не "прыгало". Думал уже реализовывать кастомным вью и непосредственно добавлять его на UIWindow, но нашел замечательное свойство wantsFullScreenLayout у UIViewController, которое все проблеммы решило.

23 апреля 2011 г.

Куки в iOS (UIWebView)

Для каждого приложения в iOS существует свое хранилище http-cookies. Класс, который отвечает за куки - NSHTTPCookieStorage. Класс имеет простой интерфейс доступа к кукам, например прочитать все куки можно так:
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies])
{
NSLog(@"%@", cookie);
}
Так-же есть методы утановки куков (- setCookie:), и чтения для конкретного урла (– cookiesForURL:)

19 апреля 2011 г.

Скрытие статус бара в iOS

Статус бар - верхняя панелька в iOS со значком оператора, часами и зарядом батареи.

Программно можно скрыть статус бар так:
[[UIApplication sharedApplication] setStatusBarHidden:YES];
Можно так-же отредактировав Info.plist приложения, добавив ключ UIStatusBarHidden

24 января 2011 г.

Скрипт конвертирования видео для iPhone/iPod/iPad в Linux

Основано на моих предыдущих записях:
- "Установка ffmpeg и x264"
- "Конвертирование видео для iPhone/iPod Touch/ iPad с несколькими звуковыми дорожками и субтитрами"

Скрипт писался для личного использования, тестировался исключительно на системе Ubuntu Linux 9.10, 10.04 и 10.10. Но не вижу проблем с запуском на других *nix системах. Писалось для конвертирования серилов с поддержкой нескольких звуковых дорожек и субтитров.

Подготовка

Установка ffmpeg с поддержкой кодека x264

- Удаляем пакеты x264, libx264-dev, и ffmpeg если они были установлены:

  1. $  sudo apt-get purge ffmpeg x264 libx264-dev


- Если включено автообновление пакетов в системе, запрещаем обновлять пакет ffmpeg:
Если файл "/etc/apt/apt.conf.d/50unattended-upgrades" существует то прописываем в секцию "Package-Blacklist" пакет ffmpeg, если файла нет, создаем его:

  1. $ sudo echo -e "Unattended-Upgrade::Package-Blacklist {\n \"ffmpeg\";\n};" > /etc/apt/apt.conf.d/50unattended-upgrades


- Получаем зависимые пакеты необходимые для установки ffmpeg и x264:

  1. $ sudo apt-get update
  2. $ sudo apt-get install build-essential subversion git-core checkinstall yasm texi2html libfaac-dev libfaad-dev libmp3lame-dev libtheora-dev


- Устанавливаем x264 из исходников

  1. $ mkdir ~/src; cd ~/src
  2. $ git clone git://git.videolan.org/x264.git
  3. $ cd ./x264/
  4. $ ./configure
  5. $ make
  6. $ sudo checkinstall --fstrans=no --install=yes --pkgname=x264 --pkgversion "1:0.svn`date +%Y%m%d`-0.0ubuntu1" --default


- Устанавливаем ffmpeg с поддержкой кодека x264:

  1. $ cd ~/src
  2. $ svn checkout svn://svn.ffmpeg.org/ffmpeg/trunk ffmpeg
  3. $ cd ffmpeg/
  4. $ ./configure --enable-gpl --enable-nonfree --enable-pthreads --enable-libfaac --enable-libmp3lame --enable-libtheora --enable-libx264
  5. $ make
  6. $ sudo checkinstall --fstrans=no --install=yes --pkgname=ffmpeg --pkgversion "3:0.svn`date +%Y%m%d`-12ubuntu3" --default


- устанавливаем необходимые утилиты кодирования

  1. $ sudo apt-get install mkvtoolnix gpac atomicparsley mencoder


mkvtoolnix - для работы с mkv файлами, gpac - библиотека для работы с mpeg4, atomicparsley - утилита для прописывания тэгов видео, mencoder - сейчас не используется, в будущем будет использоваться для кодирования mkv segmented linked видео.

Скачиваем скрипт кодирования

  1. $ cd
  2. $ git clone git://github.com/derand/video2iDevice.git


Краткое описание файлов:
2iDevice.py - собственно сам скрипт конвертирования
subConverter.py - модуль конвертирования субтитров, можно использовать как скрипт конверта сабов в ttxt формат
mpeg4fixer.py - модуль исправления выходного видео (замена графического Dumpster)
mpeg4Viewer.py - скрипт показывает структуру mpeg4 файла

Настройка завершена, теперь использование.

  1. $ ~/video2iDevice/2iDevice.py <опции> <видео файлы> <опции>


Имя параметраТипОписание
-th[int]кол-во потоков, которые будет использовать ffmpeg для кодирования
-lang[str]языки потоков (трехбуквенные имена) в выходном файле разделенные ":", например: "jpn:jpn:rus:eng:rus"
-streams[str] использовать дорожки в файле (номера дорожек разделенные ":" первая дорожка имеет индекс 0, например "0:3:1"), по умолчанию кодируются все дорожки из видео-файла
-format[str]выходной формат. Может быть "mp4", "m4v" - по умолчанию
-TRACK_REGEX[srt]регулярное выражение для выделения номера эпизода из имени файла.
-track[int]номер эпизода (серии)
-tracks[int]кол-во эпизодов (серий)
-stream[int]указывает номер используемого потока для добавленных потоков аудио или субтитров (см -afile, -sfile)
-title[str]прописывает имена потоков для добавленных потоков аудио или субтитров (см -afile, -sfile)
-b[int]битрейд видео (640 по умолчанию)
-refs[int]кол-во ref-frames (2 по умолчанию)
-vq[int]разрешение выходного видео (класс). 1 - 480x*, 2 - *x320, 3 - максимальный из первых двух (по умолчанию)
-s[int]x[int]выходное разрешение, имеет более высокий приоритет над параметром -vq
-fdПрописать продолжительность видео равную продолжительности видео-дорожки
-ar[int]частота аудио (по умолчанию 48000)
-ab[int]битрейд аудио (по умолчанию 128k)
-afile[str]добавление дорожки аудио из внешнего файла. Путь задается от папки с исходным видео. В строке могут встречаться подстроки которые заменяются: "[NAME]" - заменяется на имя файла, "[2EID]" - на номер эпизода (серии). После этого параметра может идти параметр -title и -stream
-sfile[str]добавление дорожки субтитров из внешнего файла. Формат такой-же как и в параметре -afile.
-stik[str]Задать тип видео. Может бить 'Movie' - фильм, 'Music Video' - клип, 'TV Show' - ТВ шоу ... (подробнее: AtomicParsley --stik-list)
-TVShowName[str]Задать название сериала/фильма
-TVSeasonNum[str]Задать сезон (для сериалов)
-description[str]Задать описание (максимально 255 символов)
-year[str]Установить год
-artwork[str]Установить изображение обложки. Полный путь к файлу изображения.


Несколько примеров использования можете найти в инструкции на rutracker

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:. Первый возвращает массив имен семейств шрифтов в системе, а второй массив имен шрифтов по семейству шрифта.

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.

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 за инструкцию.

1 ноября 2010 г.

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

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



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

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

16 октября 2010 г.

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

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

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

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