Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

10.12.2023

Дев-адвент 10: Прибирання

Трохи ресурсів, якими я користуюсь для розробки на Swift.

PS: чи знаєте ви, що у Swift є офіційна співачка? Може то й занадто розкрутки від Apple, але ж музика чудова! 💃


09.12.2023

Дев-адвент 9: виявлення проміжків стабільності

Окей, зрозумів, що без стабільності — нікуди. А саме в аналізі графіка. Без стабільності аналіз утворює зигзаг “вгору-вниз”, бо так алгоритм побудований: або відрізок їде вгору, або вниз, та звісно, вони чергуються, бо інакше бути не може.

Але в реальності стан стабільної ваги навіть важливіший за зріст чи падіння, а ще важливіше помітити, що він закінчився. Отже, займався сьогодні стабілізацією алгоритму.

Звучить, воно, може, й просто — стабільність це коли вага незмінна. Але маленькі коливання нас теж влаштують. Але маленькі коливання — це скільки? Та навіть маленькі коливання мають бути збалансованими.

По-перше, відмовився від ідеї “ціни” відрізка як математичної функції. Довго намагався (в чудовій програмі Grapher) винайти формулу, яка виконує всі мої вимоги, але реально набагато простіше зробити парою if. Відповідно, відрізок або стабільний (за рядом ознак), або направлений. Щоб перевірити стабільність, дивлюся на мале значення зміни ваги, а також щоб відношення між зміною в обидва напрямки було майже 1 (тобто була збалансованість).

Поки є проблемний випадок: за одною сумою змін стабільність можна виявити тільки на коротких проміжках. На проміжках більших десь за 3 тижні сума маленьких змін накопичується настільки, що вже незрозуміло, чи то результат непомітних коливань, чи суттєвого набору, а потім скиду ваги. Що вже стабільністю не назвеш. Тому зʼявляються відрізки стабільності, які йдуть один за одним, щоб не перебільшувати межу по сумі. А якщо нормалізувати суму на тривалість, тоді інша проблема — будь-який достатньо довгий проміжок часу виглядає як стабільність.

Треба придумати ще додаткову метрику, щоб це розрізняти. Можливо, підійде дельта між найбільшим та найменшим значенням за проміжок.


08.12.2023

Дев-адвент 8: збереження додаткової інформації

Apple Health - не універсальна база даних, та окрім ваги туди нічого корисного не запишеш (Ну, хіба що ще обхват талії. Чому саме талії, а не будь-якої іншої частини тіла? Бо, як я розумію, обхват талії використовується для виявлення діабету. Порівняно з цим, для відстеження харчування є метрика для кожного мікроелемента. Мабуть, записувати, скільки зʼїв хрому важливіше, ніж обхват груді чи стегна, чи біцепса. Але то таке.)

Але для мене головне, що в Apple Health не можна анотувати показники текстом. Бо без подробиць з історією ваги мало що зробиш (а точніше, їх доведеться згадувати в будь-якому разі.)

Детального щоденника я не робити хочу — це забагато шуму. Краще коли анотації будуть тільки на важливі події. Наприклад, “вечірка”, “перший раз пішов до спортзалу”, “застудився”. В майбутньому, думаю, програма буде сама запрошувати внести коментар, коли з вагою відбувається щось неочікуване.

Для збереження анотацій додав SwiftData, про який я писав місяць тому. Ще раз зрадів тому, наскільки мало для цього потрібно зробити — буквально пару рядків (плюс сама модель, запити — тобто значущий код.) Так само легко додати й синхронізацію бази через iCloud.

Є ще така фантазія: оскільки програма знає дати локальних мінімумів та максимумів ваги, вона може пропонувати обрати фотографію з тих дат, та утворити справжню галерею “до/після” в напівавтоматичному режимі.


07.12.2023

Дев-адвент 7: інтерпретація графіка та побудова історії

Ламана на графіку не дуже потрібна. Справжня цінність вчорашнього алгоритму — в тому, що програма тепер розуміє зміст графіку: відрізки зросту та спадання, точки переламу. З цього можна витягнути декілька корисних функцій.

Що я тут зрозумів: досліджувати історію змін за величезним графіком не дуже зручно, який масштаб не обирай. Бо ті самі відрізки доведеться шукати на графіку вручну, та ще й приблизно.

Отже, найпростіше, що можна зробити з ламаною — це показати її у вигляді не графіку, а списку відрізків. Так історія відразу стає зрозумілою: тут два місяці худнули, тут місяць набирали. Можна було б ще розбити список на роки, але поки те, що є на скриншоті, вже приємно.

З інших моментів: гарно було б також виявляти відрізки стабільності… для цього треба лише поміняти формулу ціни відрізка так, щоб відрізки без значних змін отримували перевагу. Взагалі вчорашній алгоритм завдяки цій формулі вийшов досить гнучкий.

Наприклад, хочу я, щоб проміжки без зважувань теж відокремлювалися… але якщо це проміжок не у 2-3 дні. Можна видумувати ще алгоритм, а можна просто доробити функцію ціни.


06.12.2023

Дев-адвент 6: підбірка ламаної під графік

Одна з аналітичних функцій, яку я хотів зробити — це автоматичне виявлення динаміки — тобто, чи йде вага вгору, чи вниз, чи залишається на місці. Задача, яка людині здається зрозумілою, алгоритмічно нетривіальна.

Наївний підхід — виділяти проміжки монотонного зростання чи спадання — працює неприйнятно. Особливо в періоди відносно сталої ваги отримаємо проміжки в один-два дні, бо вага буде коливатись то в один бік, то в інший. Взагалі, алгоритм мусить бачити “загальну картину” та ігнорувати маленькі відхилення. При цьому, він також має вірно та чітко знаходити точки, в яких динаміка змінюється на протилежну — навіть якщо з першого погляду ця зміна починається теж з маленьких “відхилень”.

Я знаю багато алгоритмів, що описують навколо графіка криві — всякі сплайни, Безьє і так далі. Але мені то зовсім не потрібно — мені важливі періоди зростання/спадання та крайні точки. Поміркувавши, знайшов гідне рішення.

Побудував алгоритм за методом динамічного програмування. Задача побудови ламаної зводиться до розвʼязку для меншої кількості даних. Для двох позицій даних розвʼязок тривіальний. Далі, для кожної нової позиції i шукаємо таке j<i, щоб відрізки, що складають розвʼязок для позиції j - вже відомий — плюс новий відрізок від j до i мали оптимальну ціну з усіх можливих j. Залишається повторити для i від 2 до N і ламана побудована.

Щоб розвʼязати задачу динамічним програмуванням, потрібно визначити ціну розвʼязка. Я поки зупинився на (i-j)^1.2 * (1 - counter/amount). Тобто з першим множником все ясно — чим довше відрізок, тим краще (ступінь можна підібрати емпірично). А другий множник визначає, наскільки у відрізку багато позицій, що йдуть супротив його напрямку.

🧠 Мозок розімʼяв успішно, одним словом. А головне, що алгоритм працює — дивись ілюстрацію вище.


05.12.2023

Дев-адвент 5: історичні дані та графіки з прокруткою

Сьогодні доробляв графік, щоб можна було подивитись не тільки поточні, а й історичні дані.

Отримати історичні дані ваги дуже просто, бо їх обсяг в компʼютерних масштабах непомітний: 365 записів за рік це менше даних ніж цей пост. API HealthKit HKSampleQuery віддає всі дані одним запитом. Залишається показати їх красиво.

Для відображення теж не потрібно ніяких високих технологій, графік впорається з (максимум) декількома тисячами точок. Звісно, так само як і з маленьким графіком, тут потрібно обрати правильний масштаб — як горизонтальний (для дат), так і вертикальний (для ваги.)

По датах робимо прокрутку. Для прокрутки графік розташовуємо в ScrollView. Оскільки хочемо, щоб графік прокручувався “ліворуч”, тобто в напрямку минулого, то його просто відразу перемотати на кінець. Такі операції у SwiftUI робляться через “проксі”-обʼєкти, в цьому разі - ScrollViewProxy. Вони нагадують ref у React.

О, ще цікавий момент: SwiftUI позбувся моделі “екранів” в додатку. Це повністю протилежне старої моделі інтерфейсу для iOS, де, навпаки, у кожного екрану був свій контролер та була сувора та незручна схема переходу між ними. Навіть React Native був вимушений емулювати таку саме модель — принаймні React Native Navigation робив окреме дерево React на кожний екран. А тепер, у SwiftUI, ніяких екранів немає, та структура компонентів дуже нагадує те, до чого ми звикли в React.


04.12.2023

Дев-адвент 4: масштаб

Сьогодні працював з масштабом графіка. Це один з найважливіших моментів того, як зробити програму кращою за табличку в Екселі.

Якщо нічого не придумувати, то, напевно, графік буде сталої висоти (може, у весь екран?), а дані масштабуватимуться, щоб його заповнити. Математично це вірно, та за ціною ділення завжди можна чітко зрозуміти, що відбувається. Але наочно ми можемо побачити тільки характер змін, але ніяке не їх розмір, а відповідно, не значення.

Щоб дізнатись обсяг змін, доведеться долучити наше аналітичне мислення… Це погано, бо графік має бути зрозумілим на рівні відчуттів. Відкрив — побачив — зрадів (чи засмутився.)

Або ще більш типовий випадок — поки вага на місці, графік має бути прямим. Як відомо, наївний графік підсилюватиме навіть найменші зміни, щоб зайняти весь діапазон шкали.

Щоб уникнути цих проблем та зробити графік наочним, масштаб мусить бути сталим, а висота графіка змінюватись відповідно до діапазону даних.

Далі — справа техніки — обрати вірний масштаб, тобто коефіцієнт “пікселів на кілограм”. Тут спирався на вигляд, бо ніяких абсолютних значень, до яких можна було б привʼязатися, немає. За коефіцієнтом обчислюю висоту графіку.

PS. Найгірше у розробці програм для iPhone - це симулятор, який завжди витрачає 100% процесора — вже роками. Але я знайшов, як від цього врятуватись: замість симулятора запускати програми на справжньому телефоні — практично це нічим не відрізняється.


03.12.2023

Дев-адвент 3: віджет

Iнтервальне голодування - перевірений спосіб контролю ваги. Якщо одним реченням, то “їсти дозволено тільки протягом вікна у 8 годин на добу.” Давно хотів впровадити віджет для iOS, який показував би поточний інтервал. Що і зробив сьогодні.

Вже писав, як взагалі працюють віджети. Але у SwiftUI я ще віджетів не робив — тільки через React Native. Ну, точніше, сам віджет був все одно на SwiftUI. Але дані для віджета я будував в головній програмі на JavaScript (бо база та все інше було там), та передавав через UserDefaults - що взагалі було гаком, бо UserDefaults призначений для налаштувань.

Коли вся програма на Swift, то так робити немає потреби. Віджет робиться зі спільного коду програми, та не має технічних обмежень на те, що дозволено робити для підготовки даних. Взагалі, чому я кажу “підготувати дані для віджета”? Тому що віджет на iOS складається з часової послідовності простих обʼєктів-конфігурацій та компоненти на SwiftUI, який робить з поточної конфігурації статичний вид.

Важливий момент 1: ми маємо заздалегідь запланувати, в який час та який вигляд буде приймати віджет. Немає такого, що можна сходити в базу чи в інтернет та оновити дані — таке оновлення відбувається рідко та за рішенням ОС (та повністю заміняє всю послідовність). В моєму випадку, це влаштовує, бо віджет показуватиме стан сталого розкладу.

Важливий момент 2: для кожної нової конфігурації віджет малюється тільки один раз, буквально. Після цього ОС бере з нього “знімок”, який вже не зміниться. Як мінімум це значить, що всі дані мають бути підготовані заздалегідь — ніякого “стану завантаження” нам не дано, як і ніяких ефектів після промальовки. В мене була модель, яка обчислювала свій стан в асинхронному блоці. Ось її довелось переробити на синхронне обчислення.

З приємного: архітектура віджетів однакова для iOS, watchOS, та macOS, а також для всіх різновидів віджетів, таких як віджети екрана блокування. Тому і навички, і код дійсно кросплатформні.


02.12.2023

Дев-адвент 2: значок

Програму iOS можна розробляти без значка аж до випуску в App Store. Але, звісно, це не дуже зручно, особливо якщо таких програм декілька, бо всі вони матимуть однаковий порожній значок. Отже, взявся й сьогодні зробив нормальний значок.

Загальні міркування: якщо навичок ілюстрації немає, допоможе мінімалізм. Взагалі, якщо роздивитися значки програм для айфона, більшість з них складаються лише з двох-пʼяти компонентів та стільки ж кольорів. Тож будь-який задум доведеться спрощувати.

Нагадаю, що мій додаток побудований навколо цікавого графіку ваги. Хотілося, щоб саме графік й був зображений на значку. А точніше, його особливість — зелені та червоні крапки, що позначають прогрес. Спочатку так і хотів — зелену та червону крапку. Але у червоних крапок значення негативне, тож краще тільки зелені.

Зелені крапки тягнуть графік вниз… така й була перша концепція, яку я довів до значка. Треба було щось поставити на тло. Для креслень є історично-стереотипна синька, тож саме її й поставив. v1 чомусь дуже нагадувала про ПриватБанк. А ще в маленькому розмірі деталей на ній було забагато, вони губилися.

Тоді пішов на спрощення. Головне — це зелені крапки — тож нехай вони тільки й залишаються. Також і “нитки”, які тягнуть графік донизу. Крапки прямують вниз — є прогрес! Щоб залишилась згадка про графік, додав координатне перехрестя. v2 реально виглядає, як позитивний шматочок графіка.

От тільки сіре тло графіка, яке прийнятно виглядало всередині додатка, на значку виглядає сумно та… сіро. Спробував повернутись до синьої теми. Трохи підправив кольори та виявилось, що графік на синьому тлі має чудовий вигляд! На v3 вирішив зупинитись. Можливо, до випуску ще додам текстуру або принаймні градієнт — якщо придивитись, то більшість значків мають ледве помітний градієнт.

Графік у додатку теж буде синій — це й візуально веселіше, і натякає на інженерну орієнтованість додатка.


01.12.2023

Дев-адвент, день 1: відновлюю роботу над додатком

🎄 Грудень — місяць відліків. Хочу використати цю оказію, щоб спонукати себе закінчити ще один проєкт: програму для відстежування дієти. Я користуюсь ним вже цілий рік, проте для публічного випуску не вистачає базового набору функцій. Спробую тридцятьма маленькими кроками його здобути. (Маленькими, бо доведеться це вмістити проміж іншими обовʼязками.)

Сьогодні зробив фічу, яка, в залежності від точки зору, може бути абсолютно зайвою або необхідною: додавання зважування на сьогодні. Нагадаю, що програма моя використовує як головне сховище базу Apple Health, тому вона цілком могла б бути тільки засобом малювання красивих графіків, без можливості редагування.

Проте, по-перше, це все одно трохи дивно, а по-друге, інтерфейс додавання ваги в Aple Health нагадує роботу з базою даних та його можна й покращити. Наприклад, вага за сьогодні мало відрізняється від ваги за вчора, та не обовʼязково вводити її наново вручну — можна зробити спінер.

Хоча для початка я просто розібрався, як додавати дані до бази HealthKit; в цілому все просто, єдиний складний момент це знайти правильний “тип значень”, а також одиницю виміру.

Окреме питання — автоматичне оновлення даних — запорука сучасного реактивного інтерфейсу. Для того є механізм HKObserverQuery. То добре, та він в мене навіть працював, от тільки нові дані там не зʼявлялись. Сьогодні знайшов дуже просту причину: для запиту потрібно вказати діапазон дат, та як дату кінця я вказував поточну дату… яка, звісно, виключала будь-які нові дані. Коли дату кінця замінив на Date.distantFuture, то оновлення виправилось.