Индикатор пульса — это простой, но эффективный элемент пользовательского интерфейса, который помогает визуализировать состояние подключения или активности. В отличие от индикатора загрузки, он передает идею о сигнале, исходящем из центральной точки, что особенно полезно для отображения состояния GPS или сетевого подключения.
Вот полная составная функция, которая отображает индикатор пульса:
@Composable
закрытый метод PulseIndicator(
@DrawableRes значок: Int,
модификатор: Modifier = Modifier
) {
val periodMs = 3600L
val offsetsMs = longArrayOf(0L, 1200L, 2400L)
val startNs = запомнить { System.nanoTime() }
var frameTimeNs by запомнить { mutableLongStateOf(startNs) }
LaunchedEffect(Единица измерения) {
while (true) {
withFrameNanos { now -> frameTimeNs = now }
}
}
(offsetMs: Long)фазафункция: число с плавающей запятой {
val elapsedMs = (frameTimeNs - startNs) / 1_000_000L + offsetMs
return ((elapsedMs % periodMs).toFloat() / periodMs.toFloat())
}
Box(modifier.size(80.dp), contentAlignment = Alignment.Center) {
(p:
Float )Кольцофункция@Composable = Box(
Модификатор
.matchParentSize()
.graphicsLayer {
scaleX = 1f + 0,8f * p
scaleY = 1f + 0,8f * p
alpha = 1f - p
}
.border(1,5.dp, Color.White.copy(alpha = 0,9f), CircleShape)
)
Кольцо(фаза(смещения[0]))
Кольцо(фаза(смещения[1]))
Кольцо(фаза(смещения[2]))
Box(
Modifier
.size(80.dp)
.background(Color.White, CircleShape),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(icon),
contentDescription = null,
modifier = Modifier.size(32.dp)
)
}
}
}
Компонуемый элемент использует
withFrameNanos внутри LaunchedEffect. Это позволяет получить доступ к текущей временной метке кадра и обеспечивает плавность анимации, пока компонуемый элемент находится на экране. Когда компонуемый элемент покидает композицию, сопрограмма автоматически отменяется.Функция
phase(offsetMs) преобразует прошедшее время в значение между 0f и 1f. Каждое кольцо смещено (0, 1200, 2400 мс), поэтому они расширяются в разные моменты. Это создаёт иллюзию непрерывных волн.Каждое кольцо изображается в виде
Box с круглой рамкой. Его размер и непрозрачность изменяются с помощью graphicsLayer:scaleX и scaleY постепенно увеличиваются от 1f до 1.8f.alpha плавно переходит от 1f к 0f.Вместе они образуют расширяющийся, затухающий круг.
В центре находится сплошной белый круг с указанным значком (например, с меткой местоположения). Он служит статичной точкой привязки, в то время как анимированные кольца расходятся в стороны.
#PixelPerfect #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4😁4👾1
OptionSet — это протокол, который представляет параметры в виде битов (битовой маски). Он также предоставляет вашему типу операции, подобные операциям с множествами, такие как объединение и пересечение. OptionSet позволяет объединять несколько значений.Это отличное решение, если вам нужно предоставить какие-то параметры, настройки, разрешения, стили и т. д.
Давайте напишем код:
struct TasksListOptions: OptionSet {
let rawValue: Int
static let showFilter = TasksListOptions(rawValue: 1 << 0)
static let showSearch = TasksListOptions(rawValue: 1 << 1)
static let showSort = TasksListOptions(rawValue: 1 << 2)
static let showLayoutSelector = TasksListOptions(rawValue: 1 << 3)
}Каждый вариант
rawValue представляет собой один бит в одном и том же целом числе: 1 << 0 — это 0001, 1 << 1 — это 0010, 1 << 2 — это 0100 и так далее. При объединении вариантов Swift объединяет их биты с помощью оператора побитового ИЛИ (|), поэтому showFilter + showSort становится 0101. Таким образом, несколько вариантов объединяются в одно компактное число, в котором каждый бит чётко обозначает отдельный вариант.Как уже упоминалось, замечательной особенностью
OptionSet является возможность комбинировать параметры. Один из распространённых сценариев — объявление предустановок по умолчанию с комбинированными значениями. Например, можно создать all параметр для представления всех параметров. В нашем примере мы можем создать предустановки для разных типов списков:struct TasksListOptions: OptionSet {
...
static let today: TasksListOptions = [.showFilter, .showSearch]
static let allTasks: TasksListOptions = [.showFilter, .showSearch, .showSort, .showLayoutSelector]
}Теперь ваша модель представления может использовать один из этих шаблонов для включения или отключения некоторых функций.
@Observable
final class TasksListViewModel {
private let options: TasksListOptions
}
#буст #MiddlePath #iOS
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
NavigationPathNavigationStack и NavigationPath в SwiftUI предоставляют мощный и гибкий способ выполнять программную навигацию в приложении. Когда вы управляете навигацией, часто возникает необходимость программно открывать (push) и закрывать (pop) экраны. NavigationPath позволяет делать это, сохраняя типобезопасность и гибкость.🔹
NavigationStack(root:)Инициализатор по умолчанию задаёт корень навигационной иерархии и управляет путём навигации “за кулисами”. Если вы хотите получить больший контроль и управлять навигацией программно, можно хранить путь в переменной
@State и передавать его в инициализатор NavigationStack(path:root:).Параметр path должен быть
Binding<Data>, и есть два способа его использования.Первый способ — использовать массив определённого типа, который реализует протокол
Hashable. Это удобно, если весь стек навигации основан на одном типе данных.@State private var path: [Color] = []
NavigationStack(path: $path) {
List {
ForEach(colors, id: \.self) { color in
Button {
path.append(color)
} label: {
...
}
}
}
.navigationDestination(for: Color.self) { color in
VStack {
color
...
Button("Pop to root") {
path.removeAll()
}
}
...
}
}
В примере выше навигационный стек поддерживается массивом объектов
Color, который выступает в роли NavigationPath. Каждый раз, когда элемент добавляется в path, модификатор navigationDestination(for:) показывает соответствующий экран. Вызов path.removeAll() очищает стек и возвращает пользователя к корневому экрану.Этот подход идеально подходит для чистой, типобезопасной навигации с минимальной настройкой, особенно если вы работаете с одним типом данных.
Когда вы находитесь в корневом экране, массив пуст.
При переходе вперёд — он заполняется элементами, где последний элемент массива соответствует текущему экрану.
Чтобы открыть новый экран — добавляем элемент, чтобы вернуться назад — удаляем последний.
NavigationPath для нескольких типовЕсли навигационный стек может содержать разные типы данных (например,
Color, String или пользовательские типы), лучше использовать NavigationPath. Он работает как type-erased список данных, но при этом сохраняет достаточно информации, чтобы SwiftUI знал, какой экран показать для каждого типа.@State private var path = NavigationPath()
NavigationStack(path: $path) {
List {
Section("Colors") {
ForEach(colors, id: \.self) { color in
Button {
path.append(color)
} label: {
...
}
}
}
Section("Genres") {
ForEach(genres, id: \.self) { genre in
Button {
path.append(genre)
} label: {
...
}
}
}
}
.navigationDestination(for: Color.self) { color in
VStack {
...
Button("Pop to root") {
path.removeLast(path.count)
}
}
...
}
.navigationDestination(for: String.self) { genre in
VStack {
...
Button("Pop to root") {
path.removeLast(path.count)
}
}
...
}
}
С
NavigationPath вы можете добавлять разные типы данных в стек. Для каждого типа нужно задать отдельный navigationDestination(for:destination:), чтобы описать, как отображать соответствующий экран.Если вы добавите значение в
NavigationPath, но не определите navigationDestination для его типа,ошибки компиляции не будет — однако пользователь увидит пустой экран с предупреждением.
Такой подход более гибкий, особенно для приложений, навигация в которых зависит от различных моделей данных.
#PixelPerfect #MiddlePath #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
AppCode — мощная IDE от JetBrains для iOS-разработки, и если ты хочешь писать код быстрее и чище — эти хоткеи must have.
Shift + F6Переименовывает переменные, классы, методы — и IDE сама обновит все упоминания.
Ctrl + Alt + V (Cmd + Option + V на Mac)Выделяешь выражение → IDE создаёт переменную автоматически.
Ctrl + Alt + M (Cmd + Option + M)Отлично помогает разгрести длинные функции и вынести повторяющийся код.
Ctrl + Alt + N (Cmd + Option + N)Обратно «встраивает» метод или переменную прямо в место вызова.
Alt + Delete (Cmd + Delete)Проверит, где элемент используется, и безопасно удалит его без сюрпризов.
#буст #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4
Новый язык дизайна Apple представил эффект светящейся анимированной обводки, которая изящно и динамично подсвечивает формы и компоненты. Давайте рассмотрим, как воссоздать этот эффект в SwiftUI с помощью многоразовых расширений.
🔹 Расширения для View
extension View {
@MainActor
func intelligenceBackground<S: InsettableShape>(in shape: S) -> some View {
background(shape.intelligenceStroke())
}
@MainActor
func intelligenceOverlay<S: InsettableShape>(in shape: S) -> some View {
overlay(shape.intelligenceStroke())
}
}🔹 Базовая реализация для фигур
extension InsettableShape {
@MainActor
func intelligenceStroke(
lineWidths: [CGFloat] = [6, 9, 11, 15],
blurs: [CGFloat] = [0, 4, 12, 15],
updateInterval: TimeInterval = 0.4
) -> some View {
IntelligenceStrokeView(
shape: self,
lineWidths: lineWidths,
blurs: blurs,
updateInterval: updateInterval
)
.allowsHitTesting(false)
}
}🔹 Рендеринг слоёв свечения
private struct IntelligenceStrokeView<S: InsettableShape>: View {
let shape: S
let lineWidths: [CGFloat]
let blurs: [CGFloat]
let updateInterval: TimeInterval
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var stops: [Gradient.Stop] = .intelligenceStyle
var body: some View {
let layerCount = min(lineWidths.count, blurs.count)
let gradient = AngularGradient(stops: stops, center: .center)
ZStack {
ForEach(0..<layerCount, id: \.self) { i in
shape
.strokeBorder(gradient, lineWidth: lineWidths[i])
.blur(radius: blurs[i])
.animation(
reduceMotion ? nil : .easeInOut(duration: 0.5 + Double(i) * 0.2),
value: stops
)
}
}
.task {
while !Task.isCancelled {
stops = .intelligenceStyle
try? await Task.sleep(for: .seconds(updateInterval))
}
}
}
}🔹 Цветовая палитра
private extension Array where Element == Gradient.Stop {
static var intelligenceStyle: [Gradient.Stop] {
let colors = [
Color(red: 188/255, green: 130/255, blue: 243/255),
Color(red: 245/255, green: 185/255, blue: 234/255),
Color(red: 141/255, green: 159/255, blue: 255/255),
Color(red: 255/255, green: 103/255, blue: 120/255),
Color(red: 255/255, green: 186/255, blue: 113/255)
]
return colors
.map { Gradient.Stop(color: $0, location: Double.random(in: 0...1)) }
.sorted { $0.location < $1.location }
}
}🔹 Использование
// Фон
Text("Текст")
.padding(22)
.intelligenceBackground(in: .capsule)
// Наложение
Text("Текст")
.padding(22)
.intelligenceOverlay(in: .rect(cornerRadius: 22))
🔹 Заключение
Эта реализация показывает, как объединить несколько обводок, размытий и анимированных градиентов для достижения эффекта свечения, аналогичного интерфейсу Apple Intelligence. Результат работает с любым объектом
InsettableShape. Его можно использовать для современной и выразительной подсветки кнопок, карточек или текстовых контейнеров.#PixelPerfect #MiddlePath #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
adb shell input keyevent – управление устройством с помощью системных событийОтправляйте системные события на Android-устройство через ADB – имитируйте нажатия аппаратных кнопок, жесты и другие действия без физического доступа к устройству.
# Базовые кнопки
adb shell input keyevent 3 # HOME
adb shell input keyevent 4 # BACK
adb shell input keyevent 26 # POWER (вкл/выкл)
adb shell input keyevent 24 # VOLUME_UP
adb shell input keyevent 25 # VOLUME_DOWN
# Медиа и специальные
adb shell input keyevent 85 # PLAY/PAUSE
adb shell input keyevent 86 # STOP
adb shell input keyevent 87 # NEXT
adb shell input keyevent 88 # PREVIOUS
adb shell input keyevent 164 # MUTE
1. Автоматизация тестов:
# Сценарий: открыть приложение, сделать действия, вернуться домой
adb shell am start -n com.yourapp/.MainActivity
sleep 2
adb shell input keyevent 4 # BACK
adb shell input keyevent 3 # HOME
2. Тестирование обработки прерываний:
# Во время работы приложения
adb shell input keyevent 26 # POWER (блокировка)
sleep 2
adb shell input keyevent 26 # POWER (разблокировка)
3. Управление медиа в фоне:
adb shell input keyevent 85 # PLAY/PAUSE музыки
Скриншот через комбинацию:
adb shell input keyevent 120 # SYSRQ (скриншот)
Перезагрузка устройства:
adb shell input keyevent 116 # POWER + перезагрузка через меню
Какие keyevent вы используете чаще всего?
Библиотека мобильного разработчика
#буст #MiddlePath #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Основной конструктор не может в себе содержать какую-либо логику по инициализации свойств (исполняемый код). Он предназначен исключительно для объявления свойств и присвоения им полученных значений. Поэтому вся логика может быть помещена в блок инициализации — блок кода, обязательно выполняемый при создании объекта независимо от того, с помощью какого конструктора этот объект создаётся. Помечается он словом init.
class Person(val name: String, var age: Int) {
var id: Int = 0
// require выдает ошибку с указанным текстом, если условие в левой части false
init {
require(name.isNotBlank(), { "У человека должно быть имя!" })
require(age > -1, { "Возраст не может быть отрицательным." })
}
constructor(name: String, age: Int, id: Int) : this(name, age) {
if (id > 0) this.id = id * 2
}
}По сути блок инициализации — это способ настроить переменные или значения, а также проверить, что были переданы допустимые параметры. Код в блоке инициализации выполняется сразу после создания экземпляра класса, т.е. сразу после вызова основного конструктора. В классе может быть один или несколько блоков инициализации и выполняться они будут последовательно.
class Person(val name: String, var age: Int) {
// сначала вызывается основной конструктор и создаются свойства класса
// далее вызывается первый блок инициализации
init {
...
}
// после первого вызывается второй блок инициализации
init {
...
}
// и т.д.
}Блок инициализации может быть добавлен, даже если у класса нет основного конструктора. В этом случае его код будет выполнен раньше кода вторичных конструкторов.
#буст #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
⚡️ Команда дня:
Хочешь понять, какие зависимости твоего проекта устарели — и стоит ли их обновлять? Вот команда, которая быстро всё покажет:
📌 Что делает:
— Проверяет все пакеты в
— Показывает текущие, доступные и последние версии библиотек
— Помогает понять, где можно безопасно обновиться
Пример вывода:
👀 Полезно, когда:
— Готовишь проект к релизу и хочешь убедиться, что всё актуально
— Нужно проверить, не тянет ли зависимость старые версии других пакетов
— Хочешь поддерживать проект «в форме» 💪
💡 Совет:
Чтобы обновить всё возможное, просто выполни:
А вы как часто обновляете зависимости в своих проектах?💬
🐸 Библиотека мобильного разработчика
#буст #MiddlePath
flutter pub outdatedХочешь понять, какие зависимости твоего проекта устарели — и стоит ли их обновлять? Вот команда, которая быстро всё покажет:
flutter pub outdated
— Проверяет все пакеты в
pubspec.yaml— Показывает текущие, доступные и последние версии библиотек
— Помогает понять, где можно безопасно обновиться
Пример вывода:
Package Current Upgradable Resolvable Latest
http 1.2.0 1.3.0 1.3.0 1.3.0
provider 6.0.0 6.1.0 6.1.0 7.0.0
— Готовишь проект к релизу и хочешь убедиться, что всё актуально
— Нужно проверить, не тянет ли зависимость старые версии других пакетов
— Хочешь поддерживать проект «в форме» 💪
Чтобы обновить всё возможное, просто выполни:
flutter pub upgrade --major-versions
А вы как часто обновляете зависимости в своих проектах?
#буст #MiddlePath
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🥱1
Хотите сделать ваше приложение доступным для всех пользователей? Этот промпт поможет реализовать полноценную поддержку accessibility.
Implement comprehensive accessibility features for a mobile app that includes:
— Add proper content descriptions for all UI elements
— Implement logical focus order and navigation
— Support screen readers (TalkBack/VoiceOver)
— Ensure sufficient color contrast and text sizing
— Add accessibility labels and hints
— Support voice control and switch access
— Test with accessibility services enabled
— Добавьте
Implement custom accessibility actions для сложных UI компонентов— Добавьте
Add haptic feedback for key interactions для невизуального взаимодействия— Добавьте
Support dynamic font sizing without breaking layouts для адаптивности текста#буст #MiddlePath
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
adb shell dumpsys meminfo – детальный анализ использования памяти приложениемМощный инструмент для мониторинга использования памяти вашим приложением в реальном времени. Помогает находить утечки памяти и оптимизировать потребление RAM.
1. PSS (Proportional Set Size):
• Реальная память, занимаемая приложением
• Включает разделяемую память (пропорционально)
2. Private Dirty:
• Память, принадлежащая только вашему приложению
• Не может быть выгружена в swap
3. Java Heap:
• Память, управляемая Dalvik/ART
• Ключевой показатель для поиска утечек
Мониторинг в реальном времени:
# Каждые 2 секунды обновлять информацию
watch -n 2 "adb shell dumpsys meminfo com.yourapp.package"
Сравнение состояний:
# До и после выполнения операции
adb shell dumpsys meminfo com.yourapp.package > before.txt
# Выполняем действия в приложении
adb shell dumpsys meminfo com.yourapp.package > after.txt
diff before.txt after.txt
Поиск утечек в Activity:
# Проверить, не остались ли Activity в памяти
adb shell dumpsys meminfo | grep -E "Activity|View|Context"
Только Java Heap:
adb shell dumpsys meminfo com.yourapp.package | grep -A 10 "Java Heap"
Нативные allocations:
adb shell dumpsys meminfo com.yourapp.package | grep -A 5 "Native Heap"
Applications Memory Usage (in KB)
Uptime: 1234567 Realtime: 1234567
** MEMINFO in pid 1234 [com.yourapp] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 12345 12000 345 100 15000 14000 1000
Dalvik Heap 5678 5600 78 50 6000 5800 200
🔧 Продвинутое использование:
Профилирование конкретной операции:
#!/bin/bash
echo "Память до:" > mem_log.txt
adb shell dumpsys meminfo com.yourapp.package >> mem_log.txt
# Запускаем тестовый сценарий
adb shell am start -n com.yourapp/.TestActivity
echo "Память после:" >> mem_log.txt
adb shell dumpsys meminfo com.yourapp.package >> mem_log.txt
Pro-совет: Используйте с procstats для долгосрочного мониторинга:
bash
adb shell dumpsys procstats --hours 1 com.yourapp.package
Как вы отслеживаете использование памяти в своих приложениях?
#буст #MiddlePath #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Меню в SwiftUI часто используются для быстрых одноразовых команд: выберите пункт, выполните действие — и всё готово. Но что, если вы хотите, чтобы меню функционировало не как простой селектор, а как мини-панель настроек, где пользователи могут последовательно переключать несколько пунктов, прежде чем сделать окончательный выбор? По умолчанию меню закрывается, как только пользователь нажимает на пункт, но с помощью подходящего API это поведение можно изменить.
Обычно при добавлении Menu к метке — например, к значку с тремя точками или кнопке — каждое касание внутри меню выполняет действие и немедленно закрывает меню. Это ожидаемый рабочий процесс «выбрать и закрыть».
Но есть сценарии, в которых может быть предпочтительнее другая модель взаимодействия:
В таких случаях поведение закрытия по умолчанию не является идеальным.
SwiftUI расширяет меню с помощью модификатора
menuActionDismissBehavior(_:). Это даёт вам возможность точно контролировать, должно ли касание внутри меню приводить к его закрытию или оставаться открытым.Модификатор принимает один параметр типа
MenuActionDismissBehavior, который представляет собой перечисление, определяемое примерно следующим образом:public enum MenuActionDismissBehavior {
case automatic // system-default behaviour
case enabled // explicitly force dismissal on each tap
case disabled // keep the menu open after taps
}Применение модификатора выглядит так:
Menu("Options") {
Button("Toggle A") { /* … */ }
Button("Toggle B") { /* … */ }
Divider()
Button("Done") { /* … */ }
}
.menuActionDismissBehavior(.disabled)При использовании
.disabled меню остаётся открытым после каждого действия, позволяя пользователю выполнить несколько действий, прежде чем решить закрыть его.Представьте, что у вас есть набор функций, которые пользователь может включить или отключить, и вы хотите, чтобы пользователь мог сделать несколько выборов в меню, прежде чем закрыть его.
struct FeatureToggleMenu: View {
@State private var featureA = false
@State private var featureB = false
@State private var featureC = false
var body: some View {
Menu {
Section {
Toggle("Feature A", isOn: $featureA)
Toggle("Feature B", isOn: $featureB)
Toggle("Feature C", isOn: $featureC)
}
.menuActionDismissBehavior(.disabled)
Button("Apply Changes") {
// commit logic here
}
} label: {
Label("Settings", systemImage: "gearshape")
}
}
}В этом макете:
Модификатор
menuActionDismissBehavior(_:) — это удобный API для преобразования типичных меню SwiftUI в более надежные мини-интерфейсы для настроек, переключателей и многошаговых рабочих процессов. Продуманное использование этого может привести к более понятному и интуитивно понятному пользовательскому интерфейсу, когда вам нужно больше, чем простое взаимодействие «выбрать и применить».#буст #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1😁1
Please open Telegram to view this post
VIEW IN TELEGRAM
ToolbarContent и @ToolbarContentBuilderПо мере роста проектов SwiftUI одна из частых проблем — управление сложными иерархиями представлений. Даже простой экран может быстро превратиться в десятки вложенных модификаторов. После снятия лимита в 10 вложенных представлений стало проще писать глубоко вложенные
body, но код стал труднее читать и сопровождать.🔹 Разбираем крупные реализации body
Когда
body растягивается на десятки строк, страдает читаемость. Лучше разбивать большие представления SwiftUI на мелкие подпредставления или выделять повторно используемые части в вычисляемые свойства или функции. Это сохраняет лаконичность и упрощает понимание, тестирование и повторное использование.Тот же принцип применим к невизуальным элементам, например панелям инструментов, которые быстро разрастаются при добавлении множества кнопок и пунктов меню.
🔹 Проблема с панелями инструментов
Модификатор
.toolbar позволяет создавать кнопки, меню и элементы управления, адаптируемые под разные платформы. Но если элементов становится много, код внутри .toolbar { ... } быстро теряет читаемость.Перенести логику в вычисляемое свойство нельзя —
.toolbar ожидает содержимое, соответствующее ToolbarContent.🔹
ToolbarContent и @ToolbarContentBuilderSwiftUI решает это с помощью:
•
ToolbarContent — протокола для элементов панели инструментов;•
@ToolbarContentBuilder — билдера, создающего набор элементов панели.Объединив их, можно вынести содержимое панели инструментов в отдельный блок, сделав
body чище и понятнее.Пример
struct DemoView: View {
@State private var message = "Hello, world!"
var body: some View {
NavigationStack {
Text(message)
.font(.title2)
.padding()
.navigationTitle("Home")
.toolbar { toolbarContent }
}
}
@ToolbarContentBuilder
private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
Button { message = "Left tapped" } label: {
Label("Left", systemImage: "line.3.horizontal")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button { message = "Right tapped" } label: {
Label("Right", systemImage: "star")
}
}
}
}🔹 Заключение
Использование
@ToolbarContentBuilder для отделения содержимого панели инструментов от основного окна имеет ряд преимуществ:• Улучшена читаемость: Ваш
body текст по-прежнему сосредоточен на вёрстке, а логика панели инструментов реализована в другом месте.• Лучшая организация: Группировка элементов панели инструментов в отдельном блоке позволяет с первого взгляда оценить их структуру и расположение.
• Масштабируемость: Когда на панели инструментов появляется несколько кнопок, меню или условная логика, поддерживать её в рабочем состоянии становится намного проще.
#PixelPerfect #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Хотите, чтобы ваше приложение работало даже без интернета? Этот промпт поможет реализовать надежный офлайн-режим и синхронизацию данных.
Implement offline-first approach for a mobile app that includes:
— Set up local database (Room/SQLite/CoreData)
— Implement data synchronization strategy
— Handle conflict resolution for concurrent modifications
— Manage offline queue for pending operations
— Add network connectivity detection
— Implement cache management and expiration policies
— Provide user feedback for sync status
— Добавьте
Implement reactive UI updates using observables для автоматического обновления интерфейса— Добавьте
Add retry mechanisms with exponential backoff для надежной синхронизации— Добавьте
Support large file caching with storage management для работы с медиа в офлайне#буст #MiddlePath
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱2🔥1🤔1
ЭЛТ-мониторы — это размытые края, линии сканирования и лёгкое свечение. Такой эффект можно воспроизвести в Compose с помощью
GraphicsLayer, градиентов и размытия.🔹 Базовый принцип
Мы один раз записываем контент во внеэкранный буфер и многократно перерисовываем его разными слоями.
val graphicsLayer = rememberGraphicsLayer()
Box(Modifier.drawWithContent {
graphicsLayer.record { drawContent() }
}) {
content()
}
Теперь
drawLayer(graphicsLayer) можно использовать в любых эффектах.🔹 Линии сканирования
Создаём повторяющиеся градиенты — вертикальные и горизонтальные:
private fun DrawScope.drawScanLines(alpha: Float, blend: BlendMode) {
val c = Colors.Black.copy(alpha)
drawRect(
brush = Brush.verticalGradient(
0f to c, 0.4f to c, 0.4f to Colors.Transparent, 1f to Colors.Transparent,
tileMode = TileMode.Repeated, endY = 10f
),
blendMode = blend
)
}Добавляем их поверх слоя:
.drawBehind {
layer {
drawLayer(graphicsLayer)
drawScanLines(alpha = 1f, blend = BlendMode.DstOut)
}
}DstOut вычитает градиент и создаёт характерный "CRT-срез".🔹 Размытие и свечение
Для реалистичного свечения рисуем несколько слоёв с разным blur/scale/alpha:
val blurLayers = listOf(
Triple(5.dp, .3f, 1.02f to 1.03f),
Triple(0.dp, .8f, 1f to 1f),
Triple(10.dp, .6f, 1.001f to 1f),
)
Каждый слой:
Box(
Modifier
.matchParentSize()
.blur(blur, BlurredEdgeTreatment.Unbounded)
.graphicsLayer { scaleX = scale.first; scaleY = scale.second; this.alpha = alpha }
.drawBehind {
layer {
drawLayer(graphicsLayer)
drawScanLines(1f, BlendMode.DstOut)
}
}
)
🔹 Дрожание экрана
var shake by remember { mutableStateOf(Offset.Zero) }
LaunchedEffect(Unit) {
while (true) {
shake = Offset(
Random.nextFloat() * Random.nextInt(-1, 1),
Random.nextFloat() * Random.nextInt(-1, 1),
)
delay(32)
}
}И применяем:
.graphicsLayer {
translationX = shake.x
translationY = shake.y
}#PixelPerfect #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM