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

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

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

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

РКН: https://gosuslugi.ru/snet/67a4adec1b17b35b6c0d8389
Download Telegram
📌 Что такое 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
🔍 Управление навигацией в SwiftUI с помощью NavigationPath

NavigationStack и NavigationPath в SwiftUI предоставляют мощный и гибкий способ выполнять программную навигацию в приложении. Когда вы управляете навигацией, часто возникает необходимость программно открывать (push) и закрывать (pop) экраны. NavigationPath позволяет делать это, сохраняя типобезопасность и гибкость.

🔹 NavigationStack(root:)

Инициализатор по умолчанию задаёт корень навигационной иерархии и управляет путём навигации “за кулисами”. Если вы хотите получить больший контроль и управлять навигацией программно, можно хранить путь в переменной @State и передавать его в инициализатор NavigationStack(path:root:).

Параметр path должен быть Binding<Data>, и есть два способа его использования.

1️⃣ Типобезопасная навигация

Первый способ — использовать массив определённого типа, который реализует протокол 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() очищает стек и возвращает пользователя к корневому экрану.

Этот подход идеально подходит для чистой, типобезопасной навигации с минимальной настройкой, особенно если вы работаете с одним типом данных.

Когда вы находитесь в корневом экране, массив пуст.
При переходе вперёд — он заполняется элементами, где последний элемент массива соответствует текущему экрану.
Чтобы открыть новый экран — добавляем элемент, чтобы вернуться назад — удаляем последний.

2️⃣ Универсальный 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
💡 Горячие клавиши для рефакторинга в AppCode

AppCode — мощная IDE от JetBrains для iOS-разработки, и если ты хочешь писать код быстрее и чище — эти хоткеи must have.

1️⃣ Переименование (Rename)

Shift + F6

Переименовывает переменные, классы, методы — и IDE сама обновит все упоминания.

2️⃣ Извлечение переменной (Extract Variable)

Ctrl + Alt + V (Cmd + Option + V на Mac)

Выделяешь выражение → IDE создаёт переменную автоматически.

3️⃣ Извлечение метода (Extract Method)

Ctrl + Alt + M (Cmd + Option + M)

Отлично помогает разгрести длинные функции и вынести повторяющийся код.

4️⃣ Инлайн (Inline)

Ctrl + Alt + N (Cmd + Option + N)

Обратно «встраивает» метод или переменную прямо в место вызова.

5️⃣ Безопасное удаление (Safe Delete)

Alt + Delete (Cmd + Delete)

Проверит, где элемент используется, и безопасно удалит его без сюрпризов.

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

#буст #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
4
Эффект свечения в стиле Apple Intelligence

Новый язык дизайна 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 – имитируйте нажатия аппаратных кнопок, жесты и другие действия без физического доступа к устройству.

✏️ Основные keyevent коды:

# Базовые кнопки
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
📎 Блок инициализации (init блок)

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