4 заметки с тегом программирование

и как его исправить

Apple Watch — невероятно крутой фитнес-трекер. Пульс, калории, пройденная дистанция — всё это считается само и синхронизируется с айфоном. А два механизма — ежедневные цели и шаринг активности с друзьями — грамотно мотивируют не халтурить.

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

Но есть один косяк — из коробки часы поддерживают лишь небольшой набор тренировок:

  • ходьба,
  • бег,
  • велосипед,
  • плавание,
  • эллиптический тренажёр,
  • гребля,
  • степпер,
  • «остальное» (Other).

Игровых видов спорта нет совсем. Я занимаюсь спортом шесть—семь раз в неделю, и ни одной из моих тренировок на часах нет:

  • теннис,
  • баскетбол,
  • традиционная силовая,
  • функциональная,
  • кросс-фит.

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

В АппСторе оказалось много приложений для разных видов спорта, и это натолкнуло меня на мысль порыться в документации HealthKit — фрэймворка для работы с тренировками и данными о здоровье. Оказалось, что в SDK доступен трекинг 70 (!) видов спорта.

Правда, почти все эти приложения какие-то кривоватые

В итоге я решил написать минималистичное watchOS-приложение, в котором были бы только нужные мне тренировки с возможностью легко добавить любую другую. Приложение получило кодовое имя «Just Do It», потому что там нет даже целей (мне и не нужно). Есть только выбор спорта и вывод основных показателей в процессе тренировки — время, калории, текущий и максимальный пульс.

Ну и главное — я хотел научиться писать приложения для часов. В итоге получилось вот что:

В комплекте идёт iOS-приложение, через которое можно стартануть тренировку на часах.

Исходный код лежит на Гитхабе.

Если вы пользуетесь часами и хотите добавить свой вид спорта, а также у вас есть Мак, Xcode и базовые навыки программирования, то это делается с помощью нескольких строчек кода в WorkoutConfig.swift.

Занимайтесь спортом! :-)

apple   apple watch   devstory   ios   iphone   swift   watchos   мобильная разработка   программирование   спорт

Вчера обновился до iOS 8.3 и заметил, что одно из моих приложений стало ужасно тормозить при скроллинге UICollectionView. Лэйаут там наподобие CoverFlow, в ячейках используется Auto Layout, но на iOS 8.2 скроллинг работал быстро.

Запустил профайлер. Оказалось, что в самом тяжёлом стэке вызовов целых 20% времени съедает метод [UICollectionReusableView _preferredLayoutAttributesFittingAttributes:]:

Начал искать, были ли у кого похожие проблемы, и наткнулся на обсуждение в Гитхабе. У них тоже тормозил скроллинг, причём на более ранних версиях iOS, и кто-то предложил добавить такой код в дочерний класс UICollectionViewCell:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    return layoutAttributes;
}

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

The default implementation of this method adjusts the size values to accommodate changes made by a self-sizing cell.

Но мне не нужен был self-sizing. Стал искать дальше, почему начал вызываться метод, пересчитывающий атрибуты лэйаута. В UICollectionViewFlowLayout.h нашёл поле estimatedItemSize:

// defaults to CGSizeZero - setting a non-zero size enables cells 
// that self-size via -preferredLayoutAttributesFittingAttributes:
@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0);

Проверил — estimatedItemSize у меня действительно равен нулю, но preferredLayoutAttributesFittingAttributes: всё равно дёргается, причём реализация по умолчанию чаще всего возвращает атрибуты, отличающиеся на несколько десятых от переданных в аргументе.

Получается, что в iOS 8.3 ребята из Apple изменили логику self-sizing для ячеек UICollectionView. В любом случае, согласно документации, этот метод не должен вызываться. Если вдруг у вас тоже начал тормозить скроллинг, попробуйте этот фикс :-)

apple   ios   iphone   objective-c   баг   мобильная разработка   программирование
19 декабря 2014, 10:43

WeekDefiner 2.0

Наконец-то я обновил WeekDefiner — своё первое и самое любимое аппсторное приложение.

Я хотел это сделать ещё больше года назад, но так как сам уже не учился в университете, то всё никак не доходили руки. Недавно я понял, что такое приложение — это отличный способ попробовать новые айосные фишки. И вот версия 2.0 дождалась релиза, попутно став полем для экспериментов с анимацией, блюром и виджетами.

У новой версии нет ничего общего со старой, кроме главной функции: показывать, какая сейчас идёт неделя. Только теперь WeekDefiner делает это гораздо лучше:

  • Знает все недели семестра. Можно листать в прошлое и будущее, чтобы посмотреть, например, на какие числа выпадет отчётная 17-я неделя.
  • Подсказывает даты при настройке. WeekDefiner научился угадывать начало и конец семестра. За основу я взял даты Аэрокоса.
  • Показывает неделю в центре уведомлений. На iOS 8 приложение даже необязательно запускать — в любой момент можно свайпнуть сверху вниз и посмотреть в виджете, какая идёт неделя. Для тех, кто пользуется iOS 7 или не хочет засорять центр уведомлений, остался кружочек с номером недели на иконке.

Я полностью обновил дизайн приложения. Оно было скеоморфное, а теперь — минималистичное с приятными эффектами. Мне особенно нравятся клёвое перетекание фона и анимация верхней кнопки. Большущее спасибо Жене Демьяненко за советы по интерфейсу и помощь с картинками.

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

Скачать в App Store

app store   ios   iphone   weekdefiner   дизайн   мобильная разработка   программирование

Давно я не писал о кодерском...

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

Попытался воспроизвести — никак. Пошёл за девайсом, на котором баг воспроизвёлся, но после перезапуска на нём всё заработало правильно. Странно. Попробовал ещё несколько раз — без толку. Полез смотреть код, перепроверил несколько раз всю логику — вроде всё верно. Потратил больше часа и не смог найти проблему. «Как же так, я же только что видел, что он воспроизводится!» Вспомнив про фазу луны, решил запустить в последний раз, и — ура! — проблема повторилась. Начал дебажить. Смотрю — в коде есть метод:

- (BOOL)isEnabled {
    return (_text);
}

Здесь _text — это строка, и она содержит правильный текст. Адрес у неё 0x15f11800, а метод возвращает NO.

Конечно, я знал, что так неявно возвращать BOOL не нужно, и сам так никогда не пишу. Но в чужом коде глаз за такое не зацепился. Написал тест:

BOOL BOOLCast() {
    long long address = 0x15f11800;
    return address;
}

int main(int argc, const char * argv[]) {
    NSLog(@"\%@\n", BOOLCast() ? @"YES" : @"NO");
    return 0;
}

Выводит:

NO

Xcode не выдаёт никаких ворнингов, анализатор — тоже. Забавно, что такая конструкция сработает правильно:

if (_text)

Дело в том, что в Objective-C тип BOOL объявлен как signed char, поэтому адрес 0x15f11800 обрезается и приводится к нулю. Другими словами, 0x15f11800 делится нацело на размер BOOL. То есть баг воспроизводится каждый раз, когда последний байт адреса оказывается нулевым.

368 121 856 / 256 = 1 437 976

Коллега сказал, что в C++ такая штука будет работать правильно с типом bool, так как инициализация происходит по-другому. Проверяем:

#include <iostream>

typedef signed char BOOL;

bool boolCast(void) {
    long long i = 0x15f11800;
    return i;
}

BOOL BOOLCast(void) {
    long long i = 0x15f11800;
    return i;
}

int main(int argc, const char * argv[]) {
    printf("bool: %d\nBOOL: %d\n", boolCast(), BOOLCast());
    return 0;
}

bool: 1
BOOL: 0

В Objective-C тоже есть тип bool, и результат аналогичен:

#import <Foundation/Foundation.h>

bool boolCast() {
    long long i = 0x15f11800;
    return i;
}

BOOL BOOLCast() {
    long long i = 0x15f11800;
    return i;
}

int main(int argc, const char * argv[]) {
    NSLog(@"\nbool: %d\nBOOL: %d\n", boolCast(), BOOLCast());
    return 0;
}

bool: 1
BOOL: 0

Однако Apple советует везде использовать BOOL. В любом случае, чтобы не зависеть от платформы, языка и архитектуры, лучше явно сравнивать с nil. Лишние символы спасут от непонятных багов:

return _text != nil;

Ну а как же Свифт? Свифт — молодец:

А теперь представьте, сколько подобного кода может быть у вас :-)

c++   ios   objective-c   swift   баг   мобильная разработка   программирование