Как заставить UITextField писать только одним языком

На самом деле все совсем иначе, чем говориться в заголовке. У одних моих друзей есть сканер штрих-кодов. Этот сканер подключается по Bluetooth и эмулирует ввод  с клавиатуры. И все было бы хорошо, но он нажимает клавиши на клавиатуре, даже в другом языке. То есть, человек переключился на русский, чтобы пообщаться в чате с коллегой, а затем пытается сканировать код. В результате он получает странное сочетание букв на русском языке вместо кода в раскладке en_US.

Начиная с версии iOS 7  мы можем это «починить». Я воспользовался советом из этого вопроса на StackOverflow, и сделал у себя подкласс UITextField. В нем я переопределил метод — (UITextInputMode *) textInputMode в точности как советует автор ответа с наибольшим рейтингом

- (UITextInputMode *) textInputMode {
  for (UITextInputMode *tim in [UITextInputMode activeInputModes]) {
    NSLog(@"%@",tim.primaryLanguage);
    if ([[self langFromLocale:self.userDefinedKeyboardLanguage]
isEqualToString:[self langFromLocale:tim.primaryLanguage]]) return tim;
  }
  return [super textInputMode];
}

В результате, теперь сканер в положенные ему поля сканирует, «печатая» только на клавиатуре en_US. Ее я выставляю в новое свойство UITextField self.userDefinedKeyboardLanguage

В завершении надо сказать, что конечно же, я не заставил писать UITextField только одним языком. Пользователь всегда может сменить язык, но первый язык (клавиатура), которые будут ему показаны, когда он выберет это поле, будет установленный вами.

Реклама

Чистка файла проекта после merge

Случилось мне тут мерджить мои изменения в проект в котором и так уже порядка 400 файлов, а тут и еще новых налабал. К тому же версия Unity viewer там постоянно меняется — что добавляет вариативности в файл проекта. И в довершении ко всему это был merge проектов на Xcode 7 c Xcode 8. В результате, после merge git всегда ругается на конфликты, которые приходится исправлять. Первый метод и самый быстрый у меня — это просто объединять все добавления из другой ветки и моей. Но тут возникла логичная проблема — в проекте оказались дубли файлов в разделе Build Phases — Compile Sources. Это порождает стандартную ошибку при линковании — duplicated symbol. Глазами дублей не увидеть, даже уже подумал, а не написать ли мне какую-нибудь приладу, как заметил маленькую подсказку в разделе ошибок и предупреждений Xcode о внесении в проект рекомендованных изменений. И там была строчка о том, что в моем проекте содержаться дубли файлов, и мне предлагается их почистить бесплатно за меня. Я конечно же согласился — и все собралось и заработало.

Тогда я решил повторить этот эксперимент. Зашел руками в файл проекта project.pbxproj и руками снова несколько раз добавил одну строчку. Зашел снова в Xcode — но он молчал, не предлагая мне ничего, хотя и отказывался собираться. Тогда я полез по меню и нашел чудесную строчку Editor -> Validate Settings… которая и вызывает нужное нам диалоговое окно. И снова Xcode сам убрал все дубли.

Отказываемся от старых циклов в Swift 2.2

Как все знают, мы постепенно отказываемся от старых «C-style» циклов в Swift 2.2. Благо, что как только вышел Xcode 7.3, то сразу вежливо предложил переделать все «старые» циклы на новый синтаксис. И оказалось, что почти все старые варианты в большом проекте у меня имели вид

for var i = 0; i < 10; i++ {
    print(i)
}

и поэтому Xcode их успешно сам заменил. Но сегодня я решил провести с ним эксперимент. Я прочитал статью Natasha The Robot о новом синтаксисе циклов и все варианты из статьи засунул в чистый проект, чтобы проверить, насколько Xcode может подсказать, как лучше изменить синтаксис на новый. Оказалось, что «автозамена» есть только у самого простого типа, который я показал выше. Тот же вариант  с обратным циклом уже не «кушается» автоматически

 
for var i = 10; i > 0; i-- {
    print(i)
}

все, на что хватает Xcode в этой ситуации, это заменить i— на i -= 1

Из всех вариантов в той статье самый необычный и незнакомый для меня был бы

/* 0, 2, 4, 6, 8 */

// instead of this
for var i = 0; i < 10; i += 2 {
    print(i)
}

// use this
for i in 0.stride(to: 10, by: 2) {
    print(i)
}

Вообще, циклы вида

for number in someNumbers {
    print(number)
}

имеют бОльшую читаемость и удобство, и это то, к чему надо стремиться. Но если для базовых классов Swift предлагает эту функциональность «из коробки», то для ваших классов это можно легко написать с помощью протоколов SequenceType и создания генераторов — Generator.

Полный список Наташиных «переделок» смотрим в ее статье вот тут. Теперь я знаю все варианты! 🙂

https://www.natashatherobot.com/swift-alternatives-to-c-style-for-loops/

Как бороться с длинными viewDidLoad()

Не секрет, что viewDidLoad могут быть достаточно большими при разработке для iOS. В него приходится засовывать очень многое, но что самое неприятное очень разностороннее. Там и настройки таблицы, и navigationController, и searchController и стартовая логика. Короче, чтение этого метода через пару месяцев после разработки очень увлекательное занятие из разряда «интересно, зачем оно здесь» и «интересно почему A поставили именно после Б».

Но наш любимый язык Swift дает нам возможность упростить этот метод, причем без всяких хитростей. Мы же используем в наших классах didSet и так называемые initalization closures. Так вот мы будем использовать все тоже самое на UIViewController. А идею мне эту подкинула просто шикарная статья — Kill Your Giant viewDidLoad

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

Конечно же я сразу засел за рефакторинг проекта на Swift, который пишу уже почти год, и 5 месяцев из которых он уже в продакшене. У меня в контроллере были выходы (IBOutlet) на таблицу, на констрейн для AutoLayout, на две дополнительные UIView, еще UISearchController и UISearchBar и все это я инициализировал во viewDidLoad различными параметрами. После того, как все это я разнес по didSet или по initalization closures все стало яснее и понятнее самому. Причем идея-то лежит на поверхности, но не понятно, почему раньше не приходило в голову так это писать. Очень рекомендую к прочтению. Ну и добавлю картинку для красоты — один из примеров использования.

Снимок экрана 2016-03-29 в 16.43.27

Два быстрых твика для Array и Dictionary в Swift

Так как Swift очень безопасный язык, то конструкция обращения к массиву по индексу через  subscript меня всегда смущала. Я начал искать, и нашел у Erica Sadun запись, где она предлагает решение этого вопроса. Однако, это было давно, и свежая версия языка Swift 2.2 лучше работает со следующим вариантом.

extension Array {
    subscript (safe index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

с вызовом

let value = array[safe:index]

Зато совсем недавний пост Эрики для Dictionary, когда нам железно надо получить non-optional значение, мне тоже очень понравился! Поэтому оставлю и его здесь тоже.

public extension Dictionary {
    public subscript(key: Key, default fallback: Value) -> Value {
        return self[key] ?? fallback
    }
}

с вызовом

let value = dict[key, default: "?"]

12 недель. Неделя 1. FancyTwitter

Итак, вот и настал день первого приложения в разделе «12 недель программирования и дизайна». Как я уже писал ранее  что для первого раза я выбрал Александра Зайцева с его дизайном приложения для Твиттера. Мне понравился сам дизайн, а большим плюсом стало наличие PSD файла к нему. В дальнейшем придется, видимо, всегда выбирать интересные проекты на dribbble.com только c PSD. Наше микро-приложения я назвал FancyTwitter.

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

  • кастомный цвет UINavigationBar
  • две кнопочки UIBarButtonItem на нем
  • необычный кастомный элемент с текстом Favorites
  • кастомная клеточка UITableViewCell для таблицы
  • ну и красивый задник под этой табличкой

В принципе, ничего сложного. При разборе файла, я обнаружил, что слой, который прорисовывает navigation bar, состоит просто из белого слоя с 10% прозрачностью. Если я нарисую точь в точь такой в программе, то текст, который будет съезжать вверх при скроле, начнет накладываться на кнопки и заголовок, и это будет некрасиво и нечитаемо. Поэтому я обратился к автору с вопросом, что же он имел в виду. Это следует делать всегда, если вам что-то не понятно. Спрашивать заказчика или дизайнера. Чаще всего, все удается дизайнеру разложить по слоям, и мне как разработчику ничего не надо сливать в Photoshop. Но в данном случае, файл готовился для себя, поэтому совершенно нормально, что автор мне ответил, что он предполагал непрозрачный navigation bar и предложил мне просто самому растрировать этот слой и получить нужный мне png. Кстати, я всегда стараюсь сам резать присылаемые PSD, потому что дизайнер не всегда представляет как я работаю, или мой стиль нарезки, поэтому проще оказывается все резать самому. Также Александр объяснил мне, что наш красивый разблюренный задник статичен по его задумке, но предполагается еще несколько задников, которые пользователь может менять по своему желанию. Этому мы уделим особое внимание дальше.

Снимок экрана 2014-06-20 в 16.22.01

Так как мы будем только симулировать работу Твиттер-клиента, то перво-наперво я создал простой класс FTDataSource, который у нас отвечает за выдачу нам твиттов. Он организован крайне примитивно — это просто массив, содержащий NSDictionary, в которых содержаться все нужные нам данные — имя пользователя, имя его твиттера, картинка профиля, текст и картинка, которую разместил пользователь. Это очень большое упрощение, но на него на приходится идти. Мы можем впоследствии заменить код в этом классе и получать реальные твиты, но пока оставим все как есть. Наша задача просто «натянуть» этот дизайн на код.

Снимок экрана 2014-06-20 в 16.28.39

Далее в первых строках нашего приложения вы видите некий класс ThemeManager. Он служит именно для того, чтобы быстро менять внешний вид всего приложения. Этот паттерн был показан помоему на WWDC 2012, и если кому-то будет интересно, я скажу название сессии. А также к ней есть исходный код. Хотя Apple и не приветствует всякого рода skins в своих приложениях, тем не менее иногда это бывает полезно. Apple в своем гайде по стилю пишет, что всегда старайтесь делать так, чтобы удовлетворить максиально широкую аудиторию всего одной настройкой, а не делать десяток,в которых пользователь утонет. Для чего этот ThemeManager может быть полезен?  Например, вы разрабатываете приложение, и еще не решили на какой варианте остановиться. Вы делаете два-три варианта и показываете их людям. С помощью этого метода вы можете делать разные соборки только меняя один из параметров в свойствах проекта.

Как это работает? Мы делаем singleton класс — ThemeManager который соответствует нашему же протоколу Theme. В этом протоколе мы указываем все-все-все параметры, которые мы планируем менять. В нашем случае это цвета текста, изображение задника, изображение navbar-a. Когда приложение стартует, то инициализирует один из классов, которые и реализуют наш протокол Theme. Также у менеджера есть общая часть, в которой можно прописать, например, все связанное с UIAppearance. Там же есть пример, как можно кастомизировать таблицу.

#import <UIKit/UIKit.h>

@protocol Theme <NSObject>

- (UIColor *)profileNameColor;
- (UIColor *)twitterNameColor;
- (UIColor *)tweetColor;
- (UIImage *)navigationBackgroundForBarMetrics:(UIBarMetrics)metrics;
- (UIImage *)backgroundImage;

@end

@interface ThemeManager : NSObject

+ (id <Theme>)sharedTheme;

+ (void)customizeAppAppearance;
+ (void)customizeTableView:(UITableView *)tableView;

@end

Едем дальше. В Photoshop я нарезал задник в двух разрешениях — для ретины и не для ретины. Задники получились просто огромных размеров. То есть два задника в сумме порядка 3,5 мегабайт. А потом я сделал еще и зеленый вариант для демонстрации. В результате наше приложение еще ничего не умеет, а уже весит не слабо. Если кто-то, кто меня читает знает, что с этим можно сделать — то милости просим в комментарии. Мое же мнение пока такое, что все переливающиеся вещи очень красивые, но с точки зрения пользователя, они очень затратны. Программист тоже мучается, пытаясь все это реализовать. Самым приемлемым вариантом мне кажется использовать некий паттерн рисунка, а затем делать из него заливку (что-то типа обоев, которые мы все любим клеить на стены). Вообщем, это место оставило у меня чувство некой неудовлетворенности.

В Storyboard я создал ViewController, который содержит UIImageView задника, а на нем прозрачная таблица с прозрачными клеточками. Там же сразу в Storyboard я создал макет нашей клеточки, содержащий все те же 5 элементов, что и наши данные. Я использовал AutoLayout для этой клеточки. Это помогло мне не рассчитывать два раза высоту блока с текстом твитта. Один раз я обязан это сделать при расчете высоты клетки в методе heightForRowAtIndexPath. После того, как я задал высоту, AutoLayout автоматически растягивает многострочный UILabel в моей клеточке.

Снимок экрана 2014-06-20 в 16.35.12

Я создал подкласс UITableViewCell, куда передаю NSDictionary, содержащий наши данные по одной клеточке. Далее я разбираю их по местам. В данном примере мне пришлось работать с методом objectForKey. В обычной практике, я стараюсь свести к минимум такой код. Так как компилятор не может проверить ни правописание вашего key, ни тип данных, который метод возвращает. Поэтому потенциально это очень неприятно. Куда удобнее было бы, если бы мы получали, например, объект Tweet унаследованный от  NSManagedObject, а в нем были бы нужные нам properties. Но для симуляции пока пойдет и так.

Хотелось бы отметить, что в коде клеточки мы обращаемся к нашему ThemeManager, чтобы получить цвет текста. Для скругления изображений мы пользуемся старым добрым setCornerRadius:20.0 но, начиная с iOS 7.1 мы обязаны также принудительно указывать clipsToBounds = YES для данного view. Также вы можете заметить, что у нас есть специальный метод, который находит в тексте гиперссылку, и согласно дизайна отрезает у нее «http://« часть, а также окрашивает ссылку в особый цвет. Также немного пришлось повозиться с межстрочным расстоянием. Оно регулируется с помощью добавления экземпляра NSMutableParagraphStyle к нашей attributed string. Обратите внимание, что ищем вхождение гиперссылки мы с помощью Apple-овского класса NSDataDetector. Рисовать свой сеператор между клеточками я не стал, а просто отодвинул его inset в InterfaceBuilder, чтобы он соответствовал дизайну.

-(NSAttributedString*)getAttributedTweet:(NSString*)tweet
{
    NSError *error = NULL;
    NSDataDetector *detector =
      [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error];

    NSArray *matches = [detector matchesInString:tweet
                                         options:0
                                           range:NSMakeRange(0, )];

    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:tweet];

    for (NSTextCheckingResult *match in matches) {

        NSString *stringWithoutHttp = [[[match URL] absoluteString]
                                       stringByReplacingOccurrencesOfString:@"http://" withString:@""];
        [string replaceCharactersInRange:[match range] withString:stringWithoutHttp];
        NSRange oldRange = [match range];
        NSRange newRange = NSMakeRange(oldRange.location, oldRange.length-7);
        [string addAttribute:NSForegroundColorAttributeName
                       value:[UIColor colorWithRed:253.0/255.0 green:166.0/255.0 blue:118.0/255.0 alpha:1.0]
                       range:newRange];

    }

    [string addAttribute:NSParagraphStyleAttributeName value:paragrahStyle range:NSMakeRange(0, [string length])];

    return string;
}

Дальше я нарезал две кнопочки, которые на navigation bar. Дизайнер совершенно правильно сделал из размером 20×20 points. Но вот только, если мы создадим на их основе кнопки, то увидим, что они смещены чуть-чуть больше к центру, чем в нашем дизайне. У нас они гораздо ближе к краю экрана. Я оставил их как есть, потому что мне этот вариант показался более правильным. Я стараюсь всегда говорить дизайнер или заказчику, что это мне кажется более правильным. Если мы нарезали нужную картинку правильного размера, сделали кнопку по умолчанию, и поставили ее на панель, а она стоит не там, то может ее правильный вариант именно этот? Заказчик всегда барин, и он может сказать, а я хочу все-таки ближе к краю экрана. И тогда мы берем один из методов, и двигаем кнопку вбок, но зато мы высказали свои замечания, что кнопка слишком близко к краю, и это может быть не удобно пользователю. Ниже мы видим наложение двух изображений в фотошопе. Так я обычно проверяю, что соответствую дизайну.

Снимок экрана 2014-06-20 в 16.39.16

Что касается Favorites со стрелочкой вниз, то такого элемента у Apple не существует, и такой навигации тоже. Но заказчик хочет именно так. Тем более, что некоторые распространенные приложения используют это — например, ВКонтакте. При нажатии на кнопочку-заголовок, у нас выезжает таблица с вариантами, мы выбираем какой-то, таблица уезжает обратно, а мы обновляем таблицы с новыми данными, а также меняем текст в этой кнопке-заголовке. Это все сделано у меня с помощью двух классов NavBarTitleView и SectionsTableViewController.

NavBarTitleView создает view, на котором расположен текст текущего раздела и изображение стрелочки вниз. К этому вью прикручен UITapGestureRecognizer, благодаря которому, мы знаем, когда пользователь нажимает на наше view. Информацию о нажатии мы передаем в наш «главный» контроллер через делагата. Этот контроллер с помощью анимации показывает нам таблицу выбора разделов и прячет ее, если мы выбрали раздел или снова нажали на середину navigation bar-a. Фантазировать на тему внешнего вида выпадающего экрана не хотелось, поэтому оставил по умолчанию. Белый фон, черный текст. В идеале, это надо все было бы заменить на соответствующий дизайнерский вид.

Хотя у нас и заранее подготовленные данные, я переживал, что очень большое количество прозрачности скажется на быстродействии скролла нашей основной таблицы. Чтобы все проверить, когда уже было закончено приложение, я запустил его на телефоне 4S и с помощью Instruments замерил FPS скролла. Он был не 60, но 57-58, что в принципе нормально, но уже сейчас мы видим просадку. Это еще раз нам говорит о том, что все-таки лучше стараться использовать непрозрачность где только возможно.

Снимок экрана 2014-06-20 в 16.43.11

Если вы выставите в свойствах проекта в разделе Apple LVVM 5.1 — Preprocessing параметр  NEW_THEME = 1, то увидите зеленый вариант приложения, о чем я вам говорил в самом начале этой статьи.

Снимок экрана 20 июня 2014 г., 15.47.31 с Симулятора iOS

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

Полный проект FancyTwitter к этой неделе на github.com

Маленькое видео с работой приложения:

До следующей недели!

12 недель программирования и дизайна для iOS

Друзья, привет!

Давно придумал для себя одну штуку, но только сейчас дошли руки. Я полон желания довести дело до конца. Суть в следующем: каждую неделю я буду выбирать некий дизайн приложения с сайта dribbble.com и с согласия автора (а может и без) я пока не знаю, я буду писать именно этот экран (всего один) в коде, так чтобы все работало, как в настоящем приложении. То есть этот дизайн сойдет с экрана сайта в симулятор iOS и даже на экраны телефонов и планшетов, если у вас есть лицензия разработчика. Статью, описывающую код, я буду выкладывать сюда. А код на github. Так за 12 дней у нас соберется большая коллекция различных дизайнов приложений. В голове уже масса планов — и графики, и collection views, и реализация «скинов» для приложения, хотя Apple это не одобряет 🙂 короче много всего интересного.

Работать мы будем с iOS7, хотя этого немного уже устаревшая ОС 🙂 возможно, что-то ближе к сентябрю напишем и для iOS8. Формат, который бы мне был удобен такой, я сначала пишу целиком весь код, и не отвлекаюсь на написание статьи, а затем в статье рассказываю основные моменты, почему я сделал так или иначе. Так я сэкономлю время себе, да и вам, не рассказывая по порядку «берем вот эту view и тянем сюда, потом снова берем и тянем туда». Я кстати, сам очень люблю учиться именно на примерах. В свое время мне очень помогла именно такая книга.  Вот ссылка, но сейчас книга во многом устарела. Это сборник совершенно готовых рабочих программ. Мне кажется, такие примеры дают понимание, как отдельные элементы работают вместе.

На этой неделе мы начнем вот с этого дизайна. Дизайнер Александр Зайцев дал свое согласие на это мероприятие. Посмотрим, что из этого выйдет. Увидимся ближе к пятнице! Всем хорошей рабочей недели!

Дизайнер Александр Зайцев. Дизайн Твиттер клиента