Tag: «objective-c»
Как отключить жест 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;
}
}
Правильный парсинг дат через RestKit. Из NSString в NSDate
Появилась проблема, связанная с тем, что неправильно парсились даты через RestKit - не учитывался часовой пояс, в итоге все даты выводились на 3 часа больше, чем нужно.
Внутри RestKit уже есть несколько NSDateFormatter для того, чтобы спарсить дату. Если один не смог - используется второй и т.д. Но для формата, в котором я получал даты через API, ни один не подошёл. Чтобы правильно парсить даты, нужно добавить свой NSDateFormatter, причём самым первым в список, чтобы он первым применялся:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss Z"];
[RKObjectMapping alloc];
[[RKValueTransformer defaultValueTransformer] insertValueTransformer:dateFormatter atIndex:0];
Жизненный цикл UIVIewController
Небольшая шпаргалка по жизненному циклу UIViewController - какие методы и в какой последовательности вызываются при разных перемещениях между UIViewController.
comment commentsTransparent Image Viewer для OS X
Я всё пытаюсь что-нибудь придумать, чтобы можно было удобно делать pixel perfect вёрстку экранов при разработке iOS-приложений. Чтобы можно было удобно и наглядно сравнивать макет и результат. Запилил сегодня вот такую тулзу для OS X - почти прозрачное окно, в котором можно октрыть макет, наложить на Xcode или на симулятор и смотреть, что получилось. Ползунком можно менять прозрачность, чтобы проверять соответствие.
Может кому пригодится. А может кто-то тоже чувствует боль и поучаствует в разработке.
Исходники лежат на github.
P.S. моё первое приложение для OS X.
comment commentsКатегория CALayer+UIColor
Хорошую штуку нашёл на Stack Overflow. В Xcode в Interface Builder можно задать что-то для UI через User Defined Runtime Attributes. Мне понадобилось задать border. Так вот толщину границы layer.borderWidth задать можно и она подхватится, а вот цвет - нет. layer.borderColor хранит значение типа CGColorRef, а цвет, который ты можешь выбрать в Interface Builder - это UIColor. Простая категория позволяет использовать UIColor через свойство layer.borderUIColor.
CALayer+UIColor.h :
//
// CALayer+UIColor.h
//
// Created by Sergey Armodin on 29/05/15.
// Copyright (c) 2015 Sergey Armodin. All rights reserved.
//
#import
#import
@interface CALayer (UIColor)
/**
* CGColor to borderColor
*/
@property(nonatomic, assign) UIColor* borderUIColor;
@end
CALayer+UIColor.m :
//
// CALayer+UIColor.m
//
// Created by Sergey Armodin on 29/05/15.
// Copyright (c) 2015 Sergey Armodin. All rights reserved.
//
#import "CALayer+UIColor.h"
@implementation CALayer (UIColor)
/**
* Setter
*
* @param color UIColor
*/
- (void)setBorderUIColor:(UIColor *)color {
self.borderColor = color.CGColor;
}
/**
* Getter
*
* @return UIColor
*/
- (UIColor *)borderUIColor {
return [UIColor colorWithCGColor:self.borderColor];
}
@end
И вуаля:
Потокобезопасное определение синглтона через GCD
Шпаргалка. Надоело каждый раз далеко бегать за ней.
+ (instancetype)sharedInstance {
static MyClass *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Получение номера дня недели на Objective-C
Шпаргалка по получению номера дня недели из NSDate:
/* берём Грегорианский календарь*/
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
/* NSDateComponents позволяет вытащить из NSDate номер дня недели, день месяца и пр. */
NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
// получаем номер дня. Он будет от 1 до 7
NSInteger weekday = [comps weekday];
В iOS, в зависимости от того, какой выбран регион в настройках устройства, неделя начинается или с понедельника, как в России, или с воскресенья, как в США.
Если нужно вывести лишь короткое название дня недели, например: пн, вт, ср, чт, то можно так:
NSDateFormatter *weekdayDateFormatter = [[NSDateFormatter alloc] init];
[weekdayDateFormatter setDateFormat: @"EE"];
NSLog(@"%@", [weekdayDateFormatter stringFromDate:dateFromString]);
Изменения в Core Location Manager в iOS 8
Заметка про то, что в iOS 8 теперь нужно запрашивать разрешение на использование геолокации иначе.
comment commentsПишем клиент для Яндекс.Метрики для iPhone
Добавлю, пожалуй, свой пост с Хабра. Пусть будет, да и к тому же он там в разделе "Я пиарюсь", который мало кто видит и он даже не индексируется поисковиками.
Как многие из вас, наверное, знают, у Яндекс.Метрики есть замечательный API, позволяющий получить данные по вашим счётчикам в XML или JSON. У меня давно уже чесались руки написать мобильный клиент, т.к. в течение дня я часто смотрю на цифры посещаемости ресурсов, которыми занимаюсь.
Существующие клиенты для iPhone мне показались ужасными и наконец руки перестали чесаться и дошли до написания.
Создание клиента можно разделить на следующие этапы:
- Регистрация приложения в Яндексе
- OAuth-авторизация пользователя и получение token'а
- Сохранение token'а
- Получение различных данных
- Отображение
Обработчик капчи для Vkontakte iOS SDK
На GitHub есть проект, реализующий работу с API ВКонтакте. У них есть метод, который срабатывает, если требуется ввести капчу, но нет реализации - как эту капчу показать пользователю и обработать её ввод. Пришлось писать самому. Вдруг кому пригодится - лежит на github.
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
Использование Google Maps SDK for iOS
В декабре Google выпустили SDK для встраивания своих карт в iOS-приложение. Последние дни я ковырялся в них, пытаясь встроить их свой проект. Небольшое описание того, как их встроить.
comment commentsПро экранирование URL'ов изображений с русскими именами в Objective-C
Начало начальство у нас вставлять в новости у нас на портале изображения, обозванные русскими именами, вроде Фото-Ресторана.jpg.
Картинки из новостей у нас показываются в приложении, соответственно по этим URL'ам они в приложение и тянутся.
Соответственно приходит в приложение URL уже закодированный, например:
В приложении используется класс 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-дюймового и программно нужный подставлять.
- Самое главное (подсказка от @Krivoblotsky) - добавить стартовую картинку для 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 дюймов
}
Как определить 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Встраивание 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Использование UINavigationController вместе с UITabBarController
Появилась нужна использовать в проекте и UINavigationController, и UITabBarController одновременно. Два способа как это сделать.
comment comments