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

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

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

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

РКН: https://gosuslugi.ru/snet/67a4adec1b17b35b6c0d8389
Download Telegram
👶 Как задержать запросы сервера на поисковый запрос пользователя в SwiftUI & Combine

Практический пример:

Поиск является обычной функцией во многих приложениях. Когда пользователь вводит поисковый запрос, мы часто хотим избежать отправки нового запроса на сервер при каждом изменении запроса. Вместо этого мы могли бы отложить запрос и отправить его только после того, как пользователь перестанет печатать на определенное время.

Реализация поиска в SwiftUI


struct SearchView: View {

@State private var userSearchInput: String = «»

var body: some View {
VStack {
// Show loading indicator, error, or results
}
.searchable(text: $userSearchInput)
.onChange(of: userSearchInput) { _, newValue in
viewModel.updateSearch(userSearchInput: newValue)
}
}
}


1. Мы используем модификатор поиска SwiftUI для настройки отображения поля поиска.
2. Мы используем переменную @State для передачи в качестве привязки к модификатору, доступному для поиска. Она будет обновляться автоматически по мере изменения пользователем поискового запроса.
3. Мы подписываемся на изменения в поиске, чтобы уведомить нашу модель просмотра о том, что ей может потребоваться обновить результаты.

Реализация в ViewModel

Далее посмотрим, как может быть реализована ViewModel:


class SearchViewModel {
private var userSearchInputSubject = PassthroughSubject()
private var cancellables = Set()

init() {
userSearchInputSubject
.debounce(for: 0.5, scheduler: RunLoop.main)
.sink { [weak self] searchInput in
self?.reload(with: searchInput)
}
.store(in: &cancellables)
}

func updateSearch(userSearchInput: String?) {
userSearchInputSubject.send(userSearchInput)
}
}


Используя оператор debounce, мы гарантируем, что запросы на сервер будут отправляться только тогда, когда пользователь перестанет печатать на полсекунды, что сокращает количество ненужных запросов и повышает общую производительность нашего приложения.

#гайд #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍1
🫗 Разбираемся в стилях кнопок SwiftUI с эффектом «жидкого стекла»

В iOS 18 в SwiftUI появились liquid glass-стили — полупрозрачные, «стеклянные» кнопки и элементы.

Обычная кнопка:

Button("Add", action: addItem)


— рисуется в стандартном системном стиле.

Если применить .glass:

Button("Add", action: addItem)
.buttonStyle(.glass)


кнопка получает эффект стекла, глубину и анимацию нажатия. Цвет можно задать через .tint, но система полностью контролирует размеры и отступы.

Есть вариант .glassProminent для основных действий, но пока он не работает в Xcode 26.0 beta.

Отдельно существует модификатор .glassEffect(), который можно применить к любому виду:

Text("Hello")
.padding()
.glassEffect()


Это просто придаёт представлению полупрозрачный фон, без анимаций и встроенного стиля — всё остальное нужно оформить вручную.

В Toolbar многие кнопки автоматически получают стеклянный стиль, особенно в слотах подтверждения или отмены, и отключить это пока нельзя.

Стоит отметить, что стеклянные кнопки не заменяют пользовательские ButtonStyles. Если нам нужны фирменные кнопки для конкретного приложения или динамические кнопки, внешний вид которых зависит от состояния, то пользовательские стили по-прежнему будут подходящим инструментом.

Но если мы хотим использовать системный внешний вид и соответствовать развивающейся эстетике Apple, то стеклянные стили — это быстрый способ добиться желаемого.

А вы уже использовали .glass или .glassEffect()? Делитесь впечатлениями в комментариях 💬

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

#MiddlePath #SwiftUI #iOS
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍1
🔍 Управление навигацией в 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
Эффект свечения в стиле 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