🈸 StoreKit 2: как давать доступ к premium-фичам (и не сломать уровни подписки)
Не часто приходится работать с in-app-ми и это то место, в котором не хочется допускать ошибки. Тут и репутационные и финансовые риски. Особенно сложно работать с нескольими уровнями подписок: как только вы добавляете несколько тарифов (например, личный / семкйный + месяц / год), появляются типичные боли:
🔵 Приложение не понимает “какой план активен”, когда есть overlap при апгрейде/даунгрейде
🔵 UI не может честно показать “что сейчас” и “на что продлится”
🔵 Транзакции прилетают повторно / entitlement “откатывается” после рестарта
Подход из статьи: “всегда выбираем самый высокий tier”
1) Группы подписок + уровни (Level of Service)
Apple использует “Subscription Level” (lower number = *higher tier*) для логики апгрейдов/даунгрейдов и того, какая подписка должна считаться активной
В примере:
🟢 Family = level 1 (выше)
🟢 Individual = level 2 (ниже)
🟢 Monthly и Yearly одного тира должны иметь одинаковый level, чтобы это был тот же tier, другая периодичность.
2) Модель тиров в коде
Делаем enum
3) Вычисляем текущий доступ по status’ам
Фильтруем
4) Trust only verified
Только
5) Обязательно finish + наблюдаем апдейты
🟢 Каждую verified транзакцию нужно
🟢 На старте проверяем
🟢 Держим живым listener на
6) UI: SubscriptionStoreView + статус рядом
В статье используют
Эмпирическое правило для подписок
🟢 Entitlement = verified transaction + правильно настроенный tier
🟢 Тарифы = данные (ASC/StoreKit config), код должен лишь корректно их интерпретировать
🟢 UI всегда показывает “effective tier”, а не последний купленный productID
Не часто приходится работать с in-app-ми и это то место, в котором не хочется допускать ошибки. Тут и репутационные и финансовые риски. Особенно сложно работать с нескольими уровнями подписок: как только вы добавляете несколько тарифов (например, личный / семкйный + месяц / год), появляются типичные боли:
Подход из статьи: “всегда выбираем самый высокий tier”
1) Группы подписок + уровни (Level of Service)
Apple использует “Subscription Level” (lower number = *higher tier*) для логики апгрейдов/даунгрейдов и того, какая подписка должна считаться активной
В примере:
2) Модель тиров в коде
Делаем enum
PassStatus (notSubscribed / individual / family) и маппим productID → tier, чтобы интерпретировать статусы StoreKit3) Вычисляем текущий доступ по status’ам
Фильтруем
statuses до .subscribed и выбираем max tier (чтобы Family “перебивал” Individual при overlap)4) Trust only verified
Только
.verified транзакции считаем entitlement’ом. Если verification не прошёл — считаем “нет подписки”.5) Обязательно finish + наблюдаем апдейты
finish(), иначе StoreKit может принести её сноваTransaction.unfinished (после крэша/сети)Transaction.updates для мгновенного обновления UI6) UI: SubscriptionStoreView + статус рядом
В статье используют
SubscriptionStoreView(groupID:), а текущий tier и “renews to …” строят из renewalInfo.Эмпирическое правило для подписок
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Немного базы в пятницу, статья про жесты в SwiftUI.
Кейс один экран — несколько интеракций
Как только на элементе появляются drag + rotate + long press + tap — SwiftUI быстро начинает угадывать, какой жест вы имели в виду и не всегда у него это выходит успешно:
Все это создает негативный UX
.gesture(...) и надеяться... На что-то обычно заканчивается тем, что один жест побеждает всегда, а второй почти не срабатывает.Три базовых композиции жестов в SwiftUI:
Кейс: long press → drag
Используем
LongPressGesture(...).sequenced(before: DragGesture())Идея:
@GestureState обновляется через .updating(), а drag-translation читаем в .onChanged() на втором этапеКейс: drag + rotate
DragGesture().simultaneously(with: RotateGesture()).onChanged()Кейс: tap vs long press
ExclusiveGesture(TapGesture(), LongPressGesture(minimumDuration: 1.0))Please open Telegram to view this post
VIEW IN TELEGRAM
👍9
Я вам несколько раз рассказывал про Skip и о своих надеждах и сомнениях по поводу этого инструмента и пришли новости. Платно получать лок на вендора желающих достаточно не оказалось, но наработки пригодились Apple рабочей группе по Android.
📱 Проблема: cross-platform “без компромиссов” всегда упирался в доверие
Многие команды хотят Swift/SwiftUI → Android, но боятся строить стратегию на маленьком закрытом платном туле: “а если завтра rug pull / покупка / закрытие?”. Skip прямо называет этот страх ключевым барьером adoption.
Начиная с Skip 1.7:
Моя оценка Skip в 2026:
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔6👍3❤2🔥1
iOS 26 принёс Liquid Glass как новый визуальный слой системы. Первое время Apple дала “костыль” — compatibility mode, чтобы старый UI не «поплыл» сразу после перекомпиляции. Но ключевое слово тут — на первое время.
Когда перестаёт работать режим совместимости?
Сегодня (Xcode 26 / iOS 26) можно отложить новый дизайн через
Info.plist ключ UIDesignRequiresCompatibility (UI Design Requires Compatibility). Donny Wals прямо пишет, что Apple планирует убрать эту опцию в следующем мажорном Xcode, и поэтому Xcode 27 вероятно сделает Liquid Glass обязательнымДополнительно, на форумах Apple в обсуждениях этого ключа фигурирует предупреждение из документации:
Temporarily use this key while reviewing and refining your app’s UI for the design in the latest SDKs
То есть ставка на “оставим compat mode навсегда” — плохой план.
И ещё момент: compatibility mode уже может ломать поведение контролов (пример — баг с
UISegmentedControl, который проявляется именно при включённом UIDesignRequiresCompatibility)Стратегия миграции без боли:
UIDesignRequiresCompatibility)Самый простой вариант — две
Info.plist: Info-Dev.plist и Info-Release.plist, и переключение в Build Settings.Начинайте не с карточек/экраников, а с того, что пользователь видит всегда:
Цель: чтобы приложение выглядело органично *в рамках нового system look* — даже если ваш контент ещё не идеален.
Liquid Glass любит:
А ломает:
Правило: переходите на системные семантические цвета/материалы там, где можно, а кастом оставляйте для контента.
Accessibility — обязательный прогон
Иначе вы сделаете красиво, но сломаете читаемость для части пользователей.
1. Принять Liquid Glass как новый baseline
2. Сделать свои компоненты
⚠️ Подходит только если у вас продукт, где дизайн — ключевой дифференциатор, и есть ресурс на поддержку.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2
Большой релиз инструмента, который мы активно используем у себя внутри для работы с приложениями.
Когда у вас сотни/тысячи модулей, вы начинаете страдать от обвязки:
.xcodeproj/.xcworkspace становится медленнойРешение: Geko
Geko — CLI-утилита для управления dev-инфраструктурой Xcode-проектов: DSL на Swift для описания структуры, генерация проектов и встроенная система кэширования.
Что есть из коробки:
.xcodeproj/.xcworkspace даже для тысяч модулей⚡️ Кэш: топ фича из коробки:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12
Проблема:
ObservableObject + @Published слишком широковещательный. SwiftUI часто инвалидирует больше UI, чем нужно, потому что сигнал приходит как "объект изменился", а не "изменилось конкретное свойство".Что даёт Observation
Миграция - это не только @Published -> var, а смена связки property wrappers и того, как модель прокидывается через SwiftUI.
Переезд по шагам:
1. Модель:
ObservableObject -> @Observable ObservableObject @Published @Observable на модель @ObservationIgnored2. Владелец модели:
@StateObject -> @State @Observable можно держать в @State, это нормальный паттерн в новой системе3. Дочерние вьюхи:
@ObservedObject -> @Bindable@Bindable@Binding оставляем для value-параметров, @Bindable - для @Observable моделей4. Глобальная модель:
@EnvironmentObject -> @Environment(Model.self) @Environment(Model.self) вместо @EnvironmentObjectГде обычно ошибки
@ObservationIgnored на сервисах -> неожиданные апдейты и лишние перерисовки @Bindable и @Binding -> биндинги перестают работать так, как ожидалиObservation - это не косметика. Это смена модели наблюдения: точнее апдейты, меньше шума, но важно правильно разнести
@State, @Bindable и игнорируемые зависимости.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍2
Официальное демо от Apple по интеграции агентов и MCP в Xcode, очень рад что Apple двигается в сторону сообщества а не только пилит свои модели. Xcode 26.3 получил поддержку агентов. Это не автодополнение и не чат в IDE, а режим, где агент может выполнять многошаговые задачи внутри проекта, пробовать собрать проект и самостоятельно анализировать ошибки и исправлять их.
Ключевая идея
Что конкретно умеет агент в Xcode 26.3:
При этом Xcode 26.3 открывает свои возможности через Model Context Protocol (MCP), то есть теоретически можно подключать совместимые инструменты/агентов
Почему это важно?
Кому зайдёт больше всего
Xcode 26.3 делает следующий шаг: от подскажи строку к выполни задачу в проекте. И это выглядит как самый практичный апгрейд IDE-помощника за долгое время
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤1
Досмотрел сегодня увлекательное интервью Питера Стейнбергера. Это создатель PDF-фреймворк PSPDFKit который был невероятно популярным. История классического пути в iOS разработку - впервые увидел iPhone, впечатлился возможностями и создал приложения для iOS 2. После чего работа в Nokia, успех пет-проекта, выгорание и возрождение интереса к разработке с приходом AI. Мои хайлайты:
Развитие продукта
Культура команды
Возвращение к работе
Структура приложений
Влияние ИИ на разработку ПО
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2