Arm1.ru

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;
    }
}

comment comments

Правильный парсинг дат через 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];

comment comments

Жизненный цикл UIVIewController

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

Читать далее...

comment comments

Transparent 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

И вуаля:

comment comments

Потокобезопасное определение синглтона через GCD

Шпаргалка. Надоело каждый раз далеко бегать за ней.

+ (instancetype)sharedInstance {
  static MyClass *sharedInstance = nil;
  static dispatch_once_t oncePredicate;
  dispatch_once(&oncePredicate, ^{
    sharedInstance = [[self alloc] init];
  });
  return sharedInstance;
}

comment comments

Получение номера дня недели на 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]);

comment comments

Изменения в Core Location Manager в iOS 8

Заметка про то, что в iOS 8 теперь нужно запрашивать разрешение на использование геолокации иначе.

Читать далее...

comment comments

Пишем клиент для Яндекс.Метрики для iPhone

Добавлю, пожалуй, свой пост с Хабра. Пусть будет, да и к тому же он там в разделе "Я пиарюсь", который мало кто видит и он даже не индексируется поисковиками.

 

Как многие из вас, наверное, знают, у Яндекс.Метрики есть замечательный API, позволяющий получить данные по вашим счётчикам в XML или JSON. У меня давно уже чесались руки написать мобильный клиент, т.к. в течение дня я часто смотрю на цифры посещаемости ресурсов, которыми занимаюсь.

Существующие клиенты для iPhone мне показались ужасными и наконец руки перестали чесаться и дошли до написания.

Создание клиента можно разделить на следующие этапы:

  1. Регистрация приложения в Яндексе
  2. OAuth-авторизация пользователя и получение token'а
  3. Сохранение token'а
  4. Получение различных данных
  5. Отображение

Читать далее...

comment comments

Обработчик капчи для Vkontakte iOS SDK

 Обработчик капчи для 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 Maps SDK для iOS

В декабре Google выпустили SDK для встраивания своих карт в iOS-приложение. Последние дни я ковырялся в них, пытаясь встроить их свой проект. Небольшое описание того, как их встроить.

Читать далее...

comment comments

Про экранирование URL'ов изображений с русскими именами в Objective-C

Начало начальство у нас вставлять в новости  у нас на портале изображения, обозванные русскими именами, вроде Фото-Ресторана.jpg.

Картинки из новостей у нас показываются в приложении, соответственно по этим URL'ам они в приложение и тянутся.

Соответственно приходит в приложение URL уже закодированный, например:

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

  1. Для xib-файлов для View выставить Size: Freeform. Тогда интерфейс на всю высоту экрана растянется. Если надо всё кастомно - то можно выставить для вида размер для 3.5-дюймового экрана или 4-дюймового и программно нужный подставлять.
    Оптимизация ios-приложения для экрана iPhone 5
  2.  Самое главное (подсказка от @Krivoblotsky) - добавить стартовую картинку для 4-дюймового экрана. Без неё апп почему-то думает, что экран маленький. И да, если раньше никакой стартовой картинки не использовалось - теперь походу придётся.
    Оптимизация ios-приложения для экрана iPhone 5

Как оно со 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

Как определить 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:

Как определить Retina Display на iPad/iPhone

Ну и, собственно, проверяя размеры/тип устройства можно подставлять нужную графику нужных размеров. 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. А, т.к. в мой проект уже встроена более новая версия фреймворка приложение не компилилось.

Решение:

  1. после добавления SDK в проект удалить папку JSON из SDK Facebook;
  2. в файле FBRequest.m заменить строку #import "JSON.h" на #import "SBJson.h";
  3. в том же файле строку SBJSON *jsonParser = [[SBJSON new] autorelease] заменить на SBJsonParser *jsonParser = [[SBJsonParser new] autorelease].

Должно работать. 

comment comments

Использование UINavigationController вместе с UITabBarController

Появилась нужна использовать в проекте и UINavigationController, и UITabBarController одновременно. Два способа как это сделать.

Читать далее...

comment comments