iOS Broadcast
前往频道在 Telegram
Подборка новостей и статей для iOS разработчиков. Новости Kotlin и мультиплатформы @kotlin_broadcast Новости Android @android_broadcast Реклама и прочее @ab_manager
显示更多3 489
订阅者
无数据24 小时
-17 天
-1330 天
帖子存档
3 489
+9
📱 Подготовьте свои приложения для iPhone Duo
Apple выпустила целый раздел для разработчиков по адаптации приложений для iPhone Duo, давайте разбиарться! Начнем с
🟡Приложения будут работать криво в режиме совместимости
🟡Если собрать с iOS 27 SDK то будет в режиме совместимости и только
🟡При сборке с iOS 27.1 SDK можно сделать пополноценную поддержку
🟡Apple предлагает ориентироваться на доступное пространство через size classes. Для SwiftUI используется .enviroment
🟡На внешнем экране Duo всё выглядит примерно как на обычном iPhone.
🟡На внутреннем экране имеет
regular size class сразу по горизонтали и вертикали и спокойно помещает sidebar
🟡Внутренний экран вообще не обязан соблюдать ваши supportedInterfaceOrientations.
🟡У Duo два дисплея, поэтому понятие UIScreen.main становится неоднозначным. Apple прямо говорит, что такие обращения будут deprecated
🟡Если реально нужен экран конкретного окна, получаем его через UIWindowScene:
window?.windowScene?.screen
🟡Лучше вообще работать через environment, traits и размеры самой scene.
🟡Ещё одна ловушка — safe area теперь может быть асимметричной width - leftInset * 2 больше не гарантирует правильный результат Левый и правый inset могут отличаться из-за формы устройства, камеры и системного UI. Apple советует считать каждую сторону отдельно и держать весь интерактивный UI внутри safe area, а фон уже спокойно растягивать edge-to-edge.
🟡Если вы используете стандартную навигацию, большая часть адаптации приезжает бесплатно.
NavigationSplitView, UISplitViewController, TabView, UITabBarController, sheets, popovers и alerts уже умеют адаптироваться к разным положениям Duo
🟡На внутреннем экране даже можно автоматически превращать tab bar в sidebar.
🟡А в iOS 27.1 появилась ещё одна интересная штука: ReservedRegion в SwiftUI и UIViewReservedRegion в UIKit. Она нужна кастомному edge-to-edge UI, чтобы сказать системе: дай мне максимум пространства, но не залезай туда, где уже живёт системный интерфейс.
🟡В Xcode 27.1 через Device Hub можно симулировать разные состояния Duo: открыть, закрыть, повернуть, сложить и посмотреть, как ваш layout переживает всё это
🟡Появился новый AI скил для адаптации новых практик
Главный вывод после просмотра довольно приятный: iPhone Duo не приносит какой-то отдельный "FoldableKit" Apple просто продолжает давить в сторону адаптивного интерфейса:
🟢не угадывать устройство
🟢не ориентироваться на конкретную ширину,
🟢не считать, что экран один,
🟢не предполагать симметричные safe areas
Если вы уже следовали этим правилам, у вас будет минимум работы по адаптации3 489
+7
⚡️Apple Event новинки
📱Начали с iPhone 18 Pro and iPhone 18 Pro Max:
🟡A20 Pro:2-нанометровая архитектура, 2 новых "супер ядря", 4 новых эффективных ядря. 7-ядерный GPU
🟡48MP главная камера
🟡Уменьшили Dynamic Island - теперь до 3 live activities одновременно
🟡4 цвета: черный, голубой, серебристый и винный
🟡Цена iPhone 18 Pro - 1,199$ и 1,299$ за Max, оба 256Гб
🔴Подорожание на 100$ с прошлого года
🎧 AirPods 5
🟡Улучшенная звукоизоляция на 50%
🟡129$ за базовую версию и 159$ с сенсорным управлением
⌚️Apple Watch Series 12 и Apple Watch Ultra 4
🟡Улучшили отслеживание сердцебиения
🟡Новый чип S11
🟡24ч время жизни от заряда у 12 и 50 у Ultra
🟡Ускорили зарядку
🟡Вернули вариант керамического корпуса для S12
🟡Series 12 - 399$, Ultra 4 - 799$
⚡️iPhone Duo
🟡3 экрана
🟡Чип A20
🟡2 камеры: 48-мегапиксельная основная камера и телефото
🟡Камера спрятана за дисплеем
🟡Титановый корпус
🟡Соотношение 1/1.4 и в открытом и закрытом состоянии
🟡7.6-дюймов в раскрытом состоянии, 5.4-дюйма внешний
🟡Заметно переработали iOS для работы с iPhone Duo
🟡Откатились к TouchID
🟡Сенсоры положения в каждой половинке
🟡Новый режим ожидания, доступный не только на зарядке
🟡от 1999$ за 256Гб
3 489
⚡️Презентация Apple уже сегодня
В 20:00 ждём новые складные и обычные iPhone, iPad и Apple Watch
Ссылки на трансляцию:
🎞 YouTube
🍏 Сайт Apple
И, как обычно, под этим постом буду писать первые впечатления во время презентации и приглашаю вас присоединиться к обсуждению. Давно всё-таки не было чего-то по-настоящему нового 🥳
3 489
🐥 Новый warning в Swift 6.4 про weak self, который реально полезен
После последних proposal, на которых основано текущее поведение захватов
➡️ SE-0365 — Allow implicit self for weak self captures
➡️ SE-0269 — Increase availability of implicit self in escaping closures
Появилось важное изменение в Swift Compiler
➡️ Request for feedback: emitting warnings when weak capture list items cause strong captures
Давайте разберемся в чем суть:
С захватом слабых ссылок в замыканиях всё давно понятно. Есть риск retain cycle, пишем:
{ [weak self] in
self?.update()
}
Но есть неприятный случай с вложенными замыканиями, который раньше Swift вообще не подсвечивал:
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
service.load { [weak self] result in
self?.result = result
}
}
Выглядит безопасно. self ведь захватывается слабой ссылкой. Но только во внутреннем замыкании. Чтобы внутреннее замыкание вообще смогло создать weak self, внешнему замыканию тоже нужен доступ к self. И Swift неявно захватывает его там уже сильной ссылкой. Получается примерно такая цепочка:
self → Timer → внешнее замыкание → self
И здравствуй retain cycle. Самое неприятное, что в Swift 6.3 такой код спокойно компилировался без единого предупреждения. В Swift 6.4 это наконец меняется. Компилятор покажет:
'weak' ownership of capture 'self' differs from implicitly-captured strong reference in outer scope
То есть буквально:
Ты во внутреннем замыкании захватываешь self слабо, но снаружи я уже неявно захватил его сильно.
Как исправить?
В большинстве случаев достаточно перенести захват слабой ссылки во внешнее замыкание:
Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { [weak self] _ in
service.load { result in
self?.result = result
}
}
Теперь внешнее замыкание хранит только слабую ссылку на self, и цикл разрывается. Если же self нужен и внешнему, и внутреннему долгоживущему замыканию, слабый захват может понадобиться на обоих уровнях. А если сильный захват действительно безопасен, например внутри короткоживущего Task, его теперь можно просто указать явно через [self].
Swift не вводит очередное правило владения памятью. Он просто начинает предупреждать о том, что язык и раньше делал неявно, но что было очень легко пропустить глазами. Начиная со Swift 6.4 компилятор хотя бы начнёт смотреть на это вместе с нами 👨💻3 489
🧭 Podlodka iOS Crew: разработка с AI
С 14 по 18 сентября пройдет новый сезон Podlodka iOS Crew. В этот раз в центре внимания авторов конференции то, как AI меняет iOS-разработку.
Что ждёт участников:
• Стратегия внедрения терминальных ИИ-агентов и MCP-интеграции в ежедневную iOS-разработку
• AI как экзокостюм мобильного разработчика: harness, skills, CLI, orchestration, validation и evals
• Знания о том, как запускать локальные модели на Apple Silicon
• Готовые скрипты, которые можно забрать в свой проект: шаблон рабочего пространства агента, примеры скиллов и MCP, пайплайн от Jira-задачи до Merge Request.
И это ещё не всё! Подробности о сезоне смотрите на сайте, и там же есть билеты по early-bird цене.
👉 Билеты на Podlodka iOS Crew
А по промокоду BROADCAST_iOS18 получите скидку🎁
#реклама
3 489
🈸 Apple сломала iMazing. Через неделю его уже починили
Новость, которая пронеслась на прошлой неделе по iOS сообществу, получила разрешение. В России iMazing давно стал чем-то большим, чем просто менеджер для iPhone. После удаления банков и других популярных приложений из App Store через него начали возвращать приложения на новые устройства. Банки пошли ещё дальше и сделали свои версии таких установщиков. Поэтому когда в августе эта схема внезапно перестала работать, новость оказалась довольно чувствительной.
20 августа
Пользователи iMazing, ipatool, 3uTools и других инструментов начали массово получать:
HTTP 403 Forbidden
Причём проблема возникала не при подключении iPhone, а раньше, во время общения программы с App Store.
Привычная схема:
Apple Account → получить доступ к App Store → скачать IPA
перестала проходить.
Сначала всё выглядело довольно грустно. Появилась версия, что Apple перевела авторизацию на новую аппаратную аттестацию через Secure Enclave. Если бы это оказалось обязательным условием, воспроизвести App Store-клиент на обычном PC было бы очень сложно.
Тут же полетели заголовки:
«Apple закрыла iMazing»
Я решил что не стоит спешить, и попробовал разобраться сам, альтернативные установщики работали, а значит ужесточения шифрования не произошло
21 августа
Разработчики вокруг open-source ipatool довольно быстро докопались до одного из изменений.
App Store теперь ожидает дополнительную криптографическую подпись запроса:
X-Apple-ActionSignature
Это часть внутреннего механизма Apple, который называют SAP. Без неё сервер просто отвечает 403. И тут выяснилась важная деталь. На macOS эту подпись уже умеет генерировать системный CommerceKit. То есть Apple не обязательно запретила сам сценарий скачивания приложений. Она просто поменяла внутренний протокол, которым пользуются её собственные сервисы. Появился форк ipatool-sapfix, который начал получать подпись через CommerceKit, и авторизация снова заработала.
Версия про непробиваемую аппаратную защиту оказалась уткой.
27 августа
DigiDNA выпускает iMazing 3.6.3.
В release notes формулировка довольно прямолинейная:
исправлены вход в Apple Account и загрузка приложений, переставшие работать после изменений на стороне Apple.То есть спустя примерно неделю iMazing снова умеет скачивать приложения. Но пока не везде. На момент релиза фикс работает на macOS 26 и старше. Для: 🟡Windows 🟡macOS 27 Golden Gate разработчики прямо пишут, что требуется дополнительная работа. И это косвенно хорошо подтверждает техническую картину. На macOS есть системный CommerceKit, который можно использовать для новой подписи App Store-запросов. На Windows такого компонента Apple просто нет. Значит, установка iOS приложений через Android телефоны пока что не возможна. Фундаментальная проблема никуда не исчезла. Вся эта экосистема живёт на недокументированных внутренних API Apple. А значит, завтра Apple снова может что-нибудь поменять и в следующий раз это может быть действительно Secure Enclave ⚰️
3 489
⚡️ Новые Mac mini и Mac Studio
Приятные утренние новости, Apple без отдельной презентации вчера обновила сразу два настольных Mac. И в этот раз интереснее всего даже не прирост CPU, а то, насколько явно Apple начала продавать Mac как машину для локального AI и агентов.
🖥Mac mini
Базовый mini первым получил новый M6:
🟢12-ядерный CPU и 12-ядерный GPU
🟢Neural Accelerators теперь есть в каждом ядре GPU
🟢до x4 производительности в AI-задачах и x2 по графике относительно M4
🟢16 ГБ RAM в базе, максимум 32 ГБ, пропускная способность 170 ГБ/с
🟢SSD до x2 быстрее
🟢Wi-Fi 7 и Bluetooth 6
🟢2.5Gb Ethernet теперь в базе, 10Gb опционально
🟢3× Thunderbolt 4 сзади
И мне нравится, как Apple сама описывает новый mini: always-on agentic computing. То есть маленький Mac под столом для локальных моделей, coding agents и всяких домашних AI-сервисов теперь буквально официальный use case.
Есть и версия на M5 Pro:
🟢до 18 CPU и 20 GPU ядер
🟢до 64 ГБ RAM и 307 ГБ/с
🟢Thunderbolt 5
🟢до x4 быстрее M4 Pro в обработке LLM prompt'ов по тестам Apple
По ценам:
💵Mac mini M6 от $899
💵Mac mini M5 Pro от $1699
32 ГБ максимум у обычного M6 немного портят красивую историю про локальные LLM, но как компактная машина для разработки и агентов mini становится очень интересным.
🖥 Mac Studio
Тут уже начинается тяжёлая артиллерия.
M5 Max:
🟢18-ядерный CPU
🟢до 40 GPU ядер с Neural Accelerators
🟢до 128 ГБ RAM
🟢614 ГБ/с пропускной способности памяти
🟢до x3.9 быстрее M4 Max в LLM prompt processing
M5 Ultra:
🟢до 36 CPU ядер
🟢до 80 GPU ядер
🟢до 512 ГБ unified memory
🟢1.2 ТБ/с пропускной способности памяти
🟢до x4 быстрее M3 Ultra в LLM-задачах по тестам Apple
512 ГБ unified memory уже выглядит не столько как домашний компьютер, сколько как локальный AI-сервер в очень маленькой коробке.
Причём Apple теперь официально поддерживает объединение нескольких Studio через Thunderbolt 5 + RDMA. Кластер из четырёх машин обещает до x3 к скорости distributed AI inference.
Из остального:
🟢Wi-Fi 7 и Bluetooth 6
🟢до 6× Thunderbolt 5
🟢до 8 дисплеев
🟢SSD до x2 быстрее
Цены тоже соответствующие:
💵Mac Studio M5 Max от $2499
💵Mac Studio M5 Ultra от $5499
Продажи обеих линеек стартуют 22 сентября.
Мне кажется, тут хорошо заметен новый вектор Apple. Раньше Mac продавали через монтаж, музыку и Xcode, а теперь практически каждый второй абзац пресс-релиза про local LLM, inference, agents и AI development.
Для обычной iOS-разработки мощности снова с огромным запасом. А вот как компактные машины под локальных coding agents новые mini выглядят всё интереснее.
Кук завершает свою работу в Apple на высокой ноте 🤘
3 489
⚡️Презентация новых iPhone состоится 9 сентября
Что ждете от презентации?
🟢Представят ли складной iPhone Ultra
🟢Что нового будет в iPhone 18 если чехлы остались те же
🟢Будут ли изменения в Apple Watch
3 489
🛡 Вредоносная dylib может спрятаться внутри обычного приложения
Редкая статья про разбор базы про реальные уязвимости, в этот раз рассматривается
dylib. Это обычная динамическая библиотека с кодом, которую приложение может загрузить при запуске или уже во время работы.
Проблема начинается, когда вместо нормальной библиотеки внутрь процесса попадает вредоносная. Тогда malware не обязательно запускать отдельным подозрительным приложением. Код может спокойно выполняться внутри условного Photoshop.
И это даёт довольно неприятный бонус. Если Photoshop уже получил доступ к Documents, сети или другим защищённым TCC ресурсам, загруженная внутрь него dylib работает в том же процессе и может использовать эти разрешения. При этом со стороны системы всё выглядит примерно так:
➡️Photoshop открыл файл.
➡️Photoshop сходил в сеть.
А то, что на самом деле действие инициировал чужой код внутри dylib, многие security API уже не показывают. Patrick Wardle в свежем исследовании Objective-See напоминает, что это не теоретическая история. В supply-chain атаке на 3CX злоумышленники модифицировали libffmpeg.dylib внутри приложения. Вредоносная версия какое-то время не детектилась security-продуктами, а само приложение даже прошло нотаризацию Apple.
Как такое искать?
Автор показывает сразу три уровня:
🟢Статически разбирать Mach-O и проверять зависимости приложения
🟢Во время работы смотреть executable memory mappings процесса через proc_pidinfo
🟢Ловить сам момент загрузки dylib через Endpoint Security
И последний вариант самый интересный.
ES_EVENT_TYPE_AUTH_MMAP позволяет получить событие ещё до того, как executable mapping будет добавлен в процесс. То есть security tool может посмотреть:
🟢Откуда загружается библиотека
🟢Как она подписана
🟢Совпадает ли Team ID с приложением
🟢Выглядит ли вообще эта dylib ожидаемой
И если условный Photoshop внезапно пытается загрузить библиотеку с чужой подписью, mapping можно просто запретить. Вредоносный код даже не начнёт выполняться. Конечно, здесь тоже нет магической кнопки "защитить всё". Если ошибочно заблокировать обязательную dylib, приложение может вообще не запуститься. А экзотические варианты с anonymous executable memory потребуют дополнительных проверок.
iOS разработчикам тут можно выдохнуть, iOS не разрешает просто подложить чужую .dylib внутрь другого приложения и запустить её. На iOS всё жёстче: код приложения подписан, embedded frameworks тоже подписаны, чужой код не должен пройти проверку подписи, а sandbox не даёт одному приложению залезть в bundle другого и что-то там заменить.
Важная оговорка: если вредоносный код попал в приложение ещё до подписи, например через заражённый SDK или скомпрометированный CI, тогда iOS уже будет считать его частью легитимного приложения.3 489
📱 Как тестировать навигацию в SwiftUI
Навигацию обычно тестируют примерно никак. Еще 3 года назад я рассказывал как можно в UIKit описывать навигацию декларативно и тестировать ее (Декларативная навигация в iOS-приложении). В SwiftUI тенденция тестировать навигацию, к моему удивлению, до сих не стала повсеместной. Хотя почти у всех есть
NavigationStack, несколько маршрутов, deep links, sheet, ошибки и авторизация, и если подумать - навигация уже является частью бизнес-логики. В статье Azam Sharp интересный подход: не тестировать сам NavigationStack. Вместо этого вынести решение по навигации в отдельную логику. Например, вместо:
if isLoggedIn {
navigationPath.append(.home)
} else {
navigationPath.append(.login)
}
сделать так, чтобы ViewModel или router решал:
destination = .home
или:
destination = .login
А SwiftUI уже занимается отображением этого состояния. Тогда тесту вообще не нужен UI. Можно проверить:
🟢авторизованный пользователь → .home
🟢неавторизованный → .login
🟢после сохранения → .details
🟢ошибка → .error
🟢deep link → правильный маршрут
Основная идея - сделать навигацию обычным состоянием приложения. Например:
enum Route: Hashable {
case home
case profile
case settings
}
А NavigationStack просто отображает текущий маршрут. Это особенно хорошо работает с NavigationPath, потому что сам path можно рассматривать как данные. Изменился path → изменился navigation state → SwiftUI обновил UI.
после такого сдвига парадигмы тестировать становится намного проще. При этом не обязательно сразу строить огромный Router. Для небольшого приложения вполне может хватить @State или @Observable-модели. После того как Navigation становится обычными данными, тестировать становится намного проще. Ну а если вам идея понравится, советую пойти дальше и посмотреть мой доклад, фреймворк отлично подходит и для SwiftUI3 489
+8
🐥 Data Detector в iOS 26: ссылки прямо из обычного текста
В iOS 26 Apple добавила новый фреймворк -
DataDetector, который умеет находить в тексте полезные сущности: ссылки, телефоны, адреса, даты и другие данные. Раньше для такого сценария часто приходилось самостоятельно разбирать строку, искать regex'ами URL и телефоны, а потом превращать найденные куски в ссылки. Совсем олдскульные разработчики вспомнят про NSDataDetector, но его далеко не так просто адаптировать для прямой работы со строкой в Swift.
DataDetector умеет работать не только с обычным String, но и с AttributedString, добавляя найденным данным соответствующие атрибуты.
То есть текст:
Позвони мне завтра в 15:00 или посмотри https://t.me/ios_broadcastможет автоматически превратиться в интерактивный текст с распознанными сущностями. Особенно полезно для: 🟢чатов 🟢заметок 🟢email 🟢документов 🟢preview пользовательского текста Это системный механизм, который знает гораздо больше о том, что именно находится в тексте. Основной плюс системного подхода: Data Detector учитывает локаль и системные форматы. Это сильно поможет при международном распространении приложения. Телефон, дата или адрес могут выглядеть совершенно по-разному в разных странах, и поддерживать все эти варианты самостоятельно довольно быстро становится неприятно. Но
DataDetector не превращает любой Text автоматически в интерактивный. Это именно инструмент для анализа текста и добавления metadata. А уже UI-слой решает, как эти данные отображать и что делать по нажатию.
Очень радует что это не SwiftUI компонент для работы с текстом, а небольшой нормальный системный фреймворк - кирпичик. Он закрывает одну раздражающую задачу.3 489
📱 ContentBuilder - секрет ускорения проверки типов в SwiftUI
На сессии WWDC 2026 инженеры Apple представили новую функцию SwiftUI:
ContentBuilder. Он представляет собой не что иное, как ViewBuilder с более широким охватом - API, которые раньше принимали ViewBuilder, ToolbarContentBuilder, или CommandsBuilder по отдельности, теперь это один конструктор.
Apple также утверждает, что это изменение значительно повышает производительность проверки типов. Давайте рассмотрим что же такое ContentBuilder и за счет чего достигается такая производительность.
Для начала давайте вспомним как работает @ViewBuilder в SwiftUI:
@ViewBuilder
var content: some View {
Text("Hello")
Image(systemName: "star")
}
И SwiftUI как будто просто понимает, что делать с несколькими View, но никакой магии тут нет. @ViewBuilder это result builder, то есть специальный механизм Swift, который превращает декларативный код в обычные вызовы методов builder'а. То есть когда мы пишем:
if isLoading {
ProgressView()
} else {
ContentView()
}
SwiftUI не исполняет это как обычный if, а возвращающий разные типы. ViewBuilder строит из этого единую структуру, которую затем SwiftUI использует для своего view tree. За счет этого body может содержать несколько View, хотя обычная функция не может просто так вернуть несколько разные типы. ViewBuilder не является чем-то исключительно SwiftUI-ным, это обычный result builder из Swift. Поэтому похожий подход можно использовать и для собственных DSL.
За счет чего же достигается ускорение проверки типов?
Помимо typealias указания на ViewBuilder, реальным фактором повышения производительности стало изменение способа объявления API. Выигрыш обусловлен тем, что SwiftUI переработал инициализаторы общих компонентов и выходные типы builder-а.
public static func buildBlock<Content>(
_ content: Content
) -> Content
@available(iOS 27.0, macOS 27.0, *)
public static func buildBlock<each Content>(
_ content: repeat each Content
) -> TupleContent<repeat each Content>
public static func buildIf<Content>(
_ content: Content?
) -> Content?
public static func buildEither<TrueContent, FalseContent>(
first: TrueContent
) -> _ConditionalContent<TrueContent, FalseContent>
Обратите внимание: ни одна из этих сигнатур не содержит Content: View, Content: ToolbarContent или Content: Commands ограничений. Задача builder-а стала чисто структурной. Недавно представленный TupleContent является ключевым элементом дизайна. Его отличительная особенность заключается в том, что он принимает различные формы в зависимости от того, что могут делать его элементы.
Другими словами, TupleContent изначально не принадлежит ни к какому домену. Это View только в том случае, если каждый элемент, который он содержит, является View. когда все его элементы являются ToolbarContent, он является содержимым панели инструментов. Optional, _ConditionalContent, Group и ForEach используют один и тот же подход.
Таким образом, разработчик может сначала создать единую конкретную структуру:
TupleContent<Text, Image>Только когда сигнатура функции требует
some View компилятор возвращается к проверке того, что и Text и Image соответствуют View.
Для Group новый интерфейс можно описать так:
public struct Group<Content> { ... }
extension Group {
public init(@ContentBuilder content: () -> Content)
}
extension Group: View where Content: View {}
extension Group: ToolbarContent where Content: ToolbarContent {}
extension Group: Commands where Content: Commands {}
Для доменов ContentBuilder унифицированы - View, ToolbarContent, Commands и т. д. — Group { ... } в обычном клиентском коде теперь предпочтительнее использовать эту универсальную точку входа в сборку без ограничений по доменам. Apple решила исправить междоменные вложенные общие компоненты, такие как Group, ForEach, и Section. Сократилось время проверки типов выражений, содержащих эти деревья перегрузок.3 489
📱 Как заставить SwiftUI делать красивые
переносы
Обычно статьи про текст рассказывают то как быстро и точно рассчитать размер контейнера, но тут более редкая проблема - не красивые переносы слов. Случай, когда текст красиво выглядит почти на всех размерах экрана, а потом на каком-нибудь iPhone последняя строка внезапно состоит из одного-двух слов. Понятная логика, перенести это слово на предыдущую строку не понятно как описать без костылей вставки переносов в исходный текст, ведь стандартный
Text не даёт нормального контроля над такими переносами.
В статье интересный подход: использовать AttributedString и управлять правилами переноса через paragraph style. В частности, можно задать lineBreakStrategy и использовать .pushOut, чтобы SwiftUI старался не оставлять слишком короткую последнюю строку.
Получается довольно приятная штука:
🟢не нужно вручную вставлять \n
🟢не нужно делать разные варианты текста для разных устройств
🟢перенос остаётся адаптивным
И это хороший пример того, как иногда проблема SwiftUI находится не в самом Text, а уровнем ниже, в типографике и AttributedString. При этом есть важный нюанс: Не стоит пытаться запретить любые короткие строки. Иногда перенос действительно является правильным, а попытка любой ценой выровнять текст только ухудшит результат.
Статья меня зацепила даже не самой идеей решения, а концепцией: типографику тоже можно сделать частью layout-поведения. И если текст выглядит странно только на некоторых размерах экрана, возможно, не нужно добавлять ещё один frame(). Сначала стоит посмотреть, какие правила переноса вообще используются.3 489
🐥 @FocusState в SwiftUI
Давайте базовую тему - фокус в SwiftUI.
@FocusState позволяет хранить состояние фокуса прямо в SwiftUI и управлять им из кода. Например, можно описать поля формы: 🟡имя🟡email🟡пароль. После чего можно переключать фокус между ними после ввода. Это можно сделать через enum:
enum Field { case name, email, password }
А затем привязать его к focused(...)
В итоге логика становится довольно читаемой:
🟢открыть экран и сразу поставить фокус на поле
🟢нажать далее и перейти к следующему
🟢после ошибки вернуть пользователя к нужному полю
🟢скрыть клавиатуру, сбросив focus в nil
@FocusState лучше воспринимать именно как состояние UI, а не как часть бизнес-логики. Не нужно тащить информацию о том, какое поле сейчас активно, в модель или сервис. Фокус относится к интерфейсу. Но не стоит использовать несколько независимых @FocusState, когда все поля относятся к одной форме. Обычно один enum для всей формы получается проще и предсказуемее.
Магия @FocusState лежит не только в упрощении работы с формами, это подход по делегированию поведения системе. Это особенно актуально, если интерфейс по-настоящему кросплатформенный и работает не только на iOS/iPad, но и на mac или даже Apple TV. Вместо becomeFirstResponder, ссылок на UIKit и ручного управления клавиатурой достаточно описать: какое поле сейчас должно быть в фокусе и дальше SwiftUI сам занимается остальным.3 489
🆓 Типобезопасные JSON в StructuredQueries
Оч интересное обновление вышло у библиотеки StructuredQueries. Сама библиотека разработана Point-Free предоставляет набор инструментов, которые позволяют вам писать типобезопасные, выразительные и компонуемые SQL-выражения на языке Swift. Но данное обновление расширяет DSL на JSON и JSONB ( это тип данных, который хранит JSON-документы в оптимизированном бинарном формате).
Элегантный DSL для указания типа для сериализации в таблице:
@Table struct Trip: Identifiable {
let id: UUID
var name = ""
@Column(as: Location.JSONRepresentation.self)
var location: Location
}
Вот так будет выглядеть запрос местоположения поездки, хранящейся в столбце JSON:
@Selection struct Location: Codable {
var latitude = 0.0
var longitude = 0.0
}
При применении макроса @Selection к Location структура типа становится видимой для builder-а запросов, и дает возможность перемещаться по JSON с помощью метода jsonExtract и привычного пути к ключу в Swift:
Trip.where {
$0.location
.jsonExtract(\.longitude) < 0
}
// Превращается в SQL
SELECT … FROM "trips"
WHERE json_extract(
"trips"."location",
'$."longitude"') < 0
Это означает, что вы получаете безопасность схемы для столбцов вашей таблицы, для полей внутри ваших JSON-данных и для извлекаемых значений. И эти выражения можно использовать в любом месте запроса: where, select, order в полях и т. д.
Вы также можете обновлять данные в формате JSON непосредственно в базе данных, не загружая их в память, используя jsonSet, jsonInsert, jsonAppend, jsonRemove и jsonReplace:
Profile.update {
$0.author = $0.author
.jsonSet(\.name, "Blob")
}
// превращается в
UPDATE "profiles"
SET "author" = json_set(
"profiles"."author",
'$."name"', 'Blob'
)
Profile.update {
$0.tags = $0.tags
.jsonAppend("new")
}
// превращается в
UPDATE "profiles"
SET "tags" = json_insert(
"profiles"."tags",
'$[#]', 'new'
)
Не сказал бы что работа с SQLite напрямую часто требуется, но во-первых это красиво 😀 . А Во-вторых можно взять на вооружение такой подход для создания своих DSL для работы с JSON
😺️ MR со всеми доработками и тестами3 489
🐥 Почему @MainActor и протоколы в Swift 6 могут поссориться
В теории всё просто, есть протокол:
protocol ImageLoader { ... }
Реализация, которая работает с UI:
@MainActor final class ImageLoaderImpl: ImageLoader { ... }
Но Swift 6 начинает задавать неприятный вопрос: а сам протокол изолирован от Main Actor или нет?
И вот тут начинается самое интересное. @MainActor на конкретной реализации не означает автоматически, что любой код, который работает с протоколом, тоже находится на Main Actor.
Для компилятора протокол может использоваться совершенно независимо от конкретной реализации.
В результате вполне невинный код начинает упираться в ошибки concurrency:
🟡Вызов main-actor метода из nonisolated контекста
🟡Несоответствие требований протокола и actor isolation
🟡Необходимость добавлять @MainActor туда, где раньше его вообще не было
Особенно часто это всплывает при dependency injection.
Например, ViewModel хранит зависимость как:
let loader: ImageLoader
а конкретно переданный ImageLoaderImpl является @MainActor.
Swift должен гарантировать, что контракт ImageLoader сам по себе безопасен. И здесь есть несколько вариантов:
➡️ Можно сделать весь протокол @MainActor
➡️ Можно изолировать только отдельные методы
➡️ Можно использовать nonisolated, если конкретная операция действительно не зависит от Main Actor
И вот последний вариант особенно важно не использовать просто для того, чтобы "заткнуть компилятор". Если метод реально работает с состоянием, которое должно жить на Main Actor, nonisolated проблему не решает. Он просто заставляет вас взять ответственность за безопасность на себя.
Попробую подытожить:
@MainActor — это не просто атрибут класса. Это часть контракта, и при работе через протокол этот контракт должен быть виден там, где он действительно нужен. Поэтому при проектировании Swift 6 API стоит заранее решить:
🟢Весь сервис main-actor isolated?
🟢Только часть его методов?
🟢Протокол вообще должен знать про actor isolation?
🟢Можно ли использовать dependency без Main Actor?
Это особенно важно для архитектуры с ViewModel + protocols + dependency injection. Это со SwiftUI как раз очень распространено. Раньше можно было сказать:
«Ну эта реализация всё равно работает на Main Actor».Swift 6 отвечает:
«Мне всё равно. Я проверяю контракт».И, пожалуй, это хорошая новость. Компилятор наконец заставляет нас явно описывать, где именно должен выполняться код, вместо того чтобы надеяться, что конкретная реализация всё разрулит сама
