Объекты можно объявлять внутри класса, при этом нет каких-либо ограничений по их количеству. Но только один объект можно пометить ключевым словом 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
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
В 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
Индикатор пульса — это простой, но эффективный элемент пользовательского интерфейса, который помогает визуализировать состояние подключения или активности. В отличие от индикатора загрузки, он передает идею о сигнале, исходящем из центральной точки, что особенно полезно для отображения состояния 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