Приведи пример интересной фичи
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример интересной фичи: динамическая анимация на основе жестов с использованием UIKit Dynamics и Interactive Animations
Одной из наиболее впечатляющих и практичных фич, которые я реализовывал, была интерактивная карточная система для приложения электронной коммерции. Фича позволяла пользователям просматривать товары в формате карточек (похоже на Tinder), но с расширенной механикой взаимодействия: карточки реагировали на свайпы, тапы и силу нажатия, а их физическое поведение имитировало реальные объекты благодаря UIKit Dynamics. Вот как это было реализовано:
Ключевые компоненты реализации
1. Базовый UIViewController с коллекцией карточек
class InteractiveCardViewController: UIViewController {
private var cards: [ProductCardView] = []
private var animator: UIDynamicAnimator!
private var attachmentBehavior: UIAttachmentBehavior?
private var originalCardCenter: CGPoint = .zero
override func viewDidLoad() {
super.viewDidLoad()
setupDynamicAnimator()
setupInitialCards()
setupGestureRecognizers()
}
private func setupDynamicAnimator() {
animator = UIDynamicAnimator(referenceView: view)
}
}
2. Кастомная UIView для карточки с многослойным дизайном
class ProductCardView: UIView {
private let productImageView = UIImageView()
private let titleLabel = UILabel()
private let descriptionLabel = UILabel()
private let likeIndicatorView = UIView()
private let dislikeIndicatorView = UIView()
// Слой тени с динамическими свойствами
override var bounds: CGRect {
didSet {
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 16).cgPath
}
}
}
3. Обработка жестов с физическим поведением
extension InteractiveCardViewController {
@objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
guard let cardView = gesture.view as? ProductCardView else { return }
let location = gesture.location(in: view)
let translation = gesture.translation(in: view)
switch gesture.state {
case .began:
originalCardCenter = cardView.center
// Создаем привязку карточки к точке касания
let attachmentPoint = UIOffset(
horizontal: location.x - cardView.center.x,
vertical: location.y - cardView.center.y
)
attachmentBehavior = UIAttachmentBehavior(
item: cardView,
offsetFromCenter: attachmentPoint,
attachedToAnchor: location
)
animator.addBehavior(attachmentBehavior!)
case .changed:
attachmentBehavior?.anchorPoint = location
// Рассчитываем угол поворота на основе смещения
let rotationAngle = translation.x * 0.015
cardView.transform = CGAffineTransform(rotationAngle: rotationAngle)
// Показываем индикаторы лайка/дизлайка с прозрачностью
let decisionThreshold: CGFloat = 100
let alpha = min(abs(translation.x) / decisionThreshold, 1.0)
if translation.x > 0 {
cardView.showLikeIndicator(alpha: alpha)
} else {
cardView.showDislikeIndicator(alpha: alpha)
}
case .ended, .cancelled:
animator.removeAllBehaviors()
// Определяем, был ли сделан выбор
if abs(translation.x) > 150 {
// Анимация улетания карточки
let velocity = gesture.velocity(in: view)
let pushBehavior = UIPushBehavior(items: [cardView], mode: .instantaneous)
pushBehavior.pushDirection = CGVector(dx: velocity.x * 0.01, dy: velocity.y * 0.01)
pushBehavior.magnitude = min(sqrt(velocity.x * velocity.x + velocity.y * velocity.y) * 0.01, 100)
animator.addBehavior(pushBehavior)
// После улетания показываем следующую карточку
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.handleCardDecision(cardView, liked: translation.x > 0)
}
} else {
// Возвращаем карточку на место с пружинной анимацией
UIView.animate(withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0,
options: .curveEaseOut) {
cardView.center = self.originalCardCenter
cardView.transform = .identity
cardView.hideDecisionIndicators()
}
}
default:
break
}
}
}
Что делало эту фичу особенно интересной:
Реалистичная физика взаимодействий:
- Карточки имели инерцию и сопротивление при движении
- При отпускании карточка могла либо вернуться на место с пружинным эффектом, либо улететь за пределы экрана
- Тень под карточкой динамически менялась в зависимости от ее наклона
Визуальная обратная связь:
- По краям экрана появлялись полупрозрачные индикаторы "Нравится" (зеленый) и "Не нравится" (красный)
- Чем дальше пользователь тянул карточку, тем ярче становился соответствующий индикатор
- При достижении порогового значения карточка "прилипала" к краю экрана
Многослойная анимация:
- Параллакс-эффект для фоновых элементов карточки
- Плавное появление следующей карточки из-под текущей
- Каскадная анимация при удалении карточки из стека
Технические особенности реализации:
-
Оптимизация производительности:
- Использование
shouldRasterizeдля сложных слоев карточек - Предварительная загрузка изображений следующих карточек
- Ограничение количества одновременно анимируемых карточек
- Использование
-
Доступность:
- Поддержка VoiceOver с описанием действий
- Альтернативное управление через кнопки для пользователей с ограниченной моторикой
- Корректная работа с увеличенным текстом
-
Кастомизация:
- Настройка чувствительности жестов через параметры
- Возможность изменения физических параметров (жесткость, сопротивление)
- Поддержка темной и светлой темы
Эта фича не только улучшила пользовательский опыт, но и увеличила вовлеченность на 34% по сравнению со стандартным списком товаров. Ключевым было балансирование между реалистичной физикой и отзывчивостью — жесты должны были чувствоваться естественно, но не в ущерб производительности. Реализация потребовала глубокого понимания Core Animation, UIKit Dynamics и принципов интерактивного дизайна.