← Назад к вопросам

Приведи пример интересной фичи

1.2 Junior🔥 161 комментариев
#CI/CD и инструменты разработки

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример интересной фичи: динамическая анимация на основе жестов с использованием 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
        }
    }
}

Что делало эту фичу особенно интересной:

Реалистичная физика взаимодействий:

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

Визуальная обратная связь:

  • По краям экрана появлялись полупрозрачные индикаторы "Нравится" (зеленый) и "Не нравится" (красный)
  • Чем дальше пользователь тянул карточку, тем ярче становился соответствующий индикатор
  • При достижении порогового значения карточка "прилипала" к краю экрана

Многослойная анимация:

  • Параллакс-эффект для фоновых элементов карточки
  • Плавное появление следующей карточки из-под текущей
  • Каскадная анимация при удалении карточки из стека

Технические особенности реализации:

  1. Оптимизация производительности:

    • Использование shouldRasterize для сложных слоев карточек
    • Предварительная загрузка изображений следующих карточек
    • Ограничение количества одновременно анимируемых карточек
  2. Доступность:

    • Поддержка VoiceOver с описанием действий
    • Альтернативное управление через кнопки для пользователей с ограниченной моторикой
    • Корректная работа с увеличенным текстом
  3. Кастомизация:

    • Настройка чувствительности жестов через параметры
    • Возможность изменения физических параметров (жесткость, сопротивление)
    • Поддержка темной и светлой темы

Эта фича не только улучшила пользовательский опыт, но и увеличила вовлеченность на 34% по сравнению со стандартным списком товаров. Ключевым было балансирование между реалистичной физикой и отзывчивостью — жесты должны были чувствоваться естественно, но не в ущерб производительности. Реализация потребовала глубокого понимания Core Animation, UIKit Dynamics и принципов интерактивного дизайна.

Приведи пример интересной фичи | PrepBro