Библиотека мобильного разработчика | Android, iOS, Swift, Retrofit, Moshi, Chuck
9.65K subscribers
1.62K photos
78 videos
52 files
4.42K links
Все самое полезное для мобильного разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4adec1b17b35b6c0d8389
Download Telegram
🔍 Неочевидные возможности отладчика в Xcode

Большинство iOS-разработчиков используют Xcode Debugger только для банальных breakpoint и просмотра переменных. Но в нём есть куда больше фишек, которые реально экономят время.

🔹 Conditional breakpoints

Можно остановить выполнение не всегда, а только при выполнении условия.
Например, поставить брейкпоинт на метод и задать условие userId == 42. Тогда дебаггер не будет дёргать вас на каждом вызове.

🔹 Exception Breakpoint

Очень полезно для поиска падений — Xcode сразу остановит выполнение в момент выброса исключения, до того как приложение упадёт. Настраивается через вкладку Breakpoints Navigator → + Exception Breakpoint.

🔹 Symbolic breakpoints

Позволяют ловить вызовы по имени метода или функции, даже если кода у вас нет (например, методы UIKit). Можно отследить момент, когда система вызывает viewDidAppear: или layoutSubviews.

🔹 Debug view hierarchy

Через кнопку "Debug View Hierarchy" можно визуально исследовать всю иерархию вьюх, включая невидимые и перекрытые элементы. Отличный способ поймать «невидимую кнопку», которая блокирует тап.

🔹 LLDB команды прямо в консоли

🔘 po object — печатает объект с вызовом description.
🔘 expr — позволяет выполнять произвольный Swift/ObjC код прямо во время остановки.
🔘 watchpoint set variable foo — можно следить за изменением конкретной переменной.

🔹 Runtime Issues

Xcode сам подсказывает баги вроде «UI update вне main thread» или «дважды добавили constraint». Не игнорируйте эти подсказки — они часто спасают кучу времени.

Что из этого вы используете? 💬

🐸 Библиотека мобильного разработчика

#буст #MiddlePath #iOS
Please open Telegram to view this post
VIEW IN TELEGRAM
4
✂️ Обрезка в Jetpack Compose

Обрезка — это удаление частей контента за пределами заданной формы. Представьте, что вы используете формочки для печенья: всё, что находится внутри формочки, остаётся, а всё, что снаружи, удаляется.

В Compose это делается с помощью Modifier.clip функции:

Image(
painter = painterResource(R.drawable.avatar),
contentDescription = null,
modifier = Modifier
.size(72.dp)
.clip(CircleShape)
)


Здесь изображение обрезается по кругу, независимо от фактических границ растрового изображения.

📌 Пользовательские Формы

Если встроенных фигур (CircleShape, RoundedCornerShape и т. д.) недостаточно, вы можете создать собственную Shape и нарисовать свой собственный контур. Например:

class SquishedOvalShape : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
return Outline.Generic(
Path().apply {
addOval(Rect(0f, size.height / 4f, size.width, size.height))
}
)
}
}


Примените его, как и любую другую форму:

Modifier.clip(SquishedOvalShape())


🐸 Библиотека мобильного разработчика

#PixelPerfect #MiddlePath #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
2
🔍 Сompanion object (также Singleton)

Объекты можно объявлять внутри класса, при этом нет каких-либо ограничений по их количеству. Но только один объект можно пометить ключевым словом companion object в рамках одного класса.

Синглтон-свойство companion object достигается за счет того, что он создается внутри класса в качестве статического поля. Он будет инициализирован при первом обращении к нему или при создании первого экземпляра класса, в котором он объявлен.

Важно отметить, что companion object будет инициализирован первым, а затем уже будет создан экземпляр класса:

class MyClass {
init {
// Выполняется всегда после инициализации companion object
}

companion object {
init {
// Выполняется всегда перед блоком init содержащего класса
}
}
}

val myClass = MyClass()


Такому объекту можно не указывать свое имя, и обращаться к методам и свойствам объекта через имя содержащего его класса без явного указания имени объекта.

class SomeClass {

companion object {
fun create()
}
}

val someClass = SomeClass.create()


Компилируется в public static final class на Java. Работает подобно ключевому слову static в Java.

🐸 Библиотека мобильного разработчика

#буст #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
👏3
⚙️ Оптимизация времени запуска мобильного приложения

Заметили, что ваше приложение долго запускается? Этот промпт поможет найти и устранить узкие места в процессе старта.

📝 Промпт:

Analyze and optimize mobile app startup time that includes:

— Measure cold/warm/hot start durations
— Identify main thread blockers during launch
— Optimize application class initialization
— Reduce dynamic feature module loading time
— Implement lazy loading for heavy components
— Optimize dependency injection setup
— Improve splash screen and initial UI rendering


💡 Расширения:

— Добавьте Implement asynchronous resource loading для параллельной загрузки ресурсов
— Добавьте Add startup tracing and performance monitoring для постоянного отслеживания
— Добавьте Optimize pre-dexing and multidex configuration для больших приложений

🐸 Библиотека мобильного разработчика

#буст #MiddlePath
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🔴 Как правильно показывать Alerts в SwiftUI

В SwiftUI alert — это способ показать критически важную информацию или запросить решение у пользователя.

Используй их только для действительно важных случаев: ошибок, подтверждений (особенно разрушительных действий), запросов разрешений.

🧠 Основы

Самый простой способ — через isPresented (булево значение):

@State var isPresented = false

VStack {
// UI
}
.alert("Alert Title", isPresented: $isPresented) {
Button("OK") { /* action */ }
Button("Cancel") { }
}


Алёрт появляется, когда isPresented = true, и автоматически закрывается после действия пользователя.

💬 Дополнительное сообщение

Можно добавить поясняющий текст:

.alert("Alert Title", isPresented: $isPresented) {
Button("OK") { }
} message: {
Text("Подробнее о проблеме")
}


Это помогает пользователю понять контекст и принять осознанное решение.

📦 Работа с данными

Для подтверждения действий над конкретными объектами есть параметр presenting:

.alert("Delete Document", isPresented: $isPresented, presenting: document) { document in
Button("Delete", role: .destructive) { delete(document) }
Button("Cancel", role: .cancel) { }
} message: { document in
Text("Удалить '\(document.name)'? Это действие нельзя отменить.")
}


Так SwiftUI сохраняет корректные данные на время отображения диалога.

❗️ Работа с ошибками

Если твои ошибки реализуют LocalizedError, SwiftUI умеет красиво показывать их в алёртах:

.alert(isPresented: $isPresented, error: error) {
Button("Retry") { }
Button("Cancel") { }
} message: { error in
Text(error.recoverySuggestion ?? "Попробуй позже.")
}


Рекомендации

Используй краткие заголовки и ясные действия.
Помечай кнопки ролями: .cancel, .destructive.
Не перегружай интерфейс — если это не критично, лучше показать баннер или inline-сообщение.

💬 А как вы показываете ошибки пользователю — через alert, banner или sheet?

🐸 Библиотека мобильного разработчика

#АрхитектурныйКод #MiddlePath #iOS
Please open Telegram to view this post
VIEW IN TELEGRAM
2
🔴 Индикатор пульса в Jetpack Compose

Индикатор пульса — это простой, но эффективный элемент пользовательского интерфейса, который помогает визуализировать состояние подключения или активности. В отличие от индикатора загрузки, он передает идею о сигнале, исходящем из центральной точки, что особенно полезно для отображения состояния 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 — это протокол, который представляет параметры в виде битов (битовой маски). Он также предоставляет вашему типу операции, подобные операциям с множествами, такие как объединение и пересечение. 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