<!-- Canonical: https://docs.shopstory.live/docs/guide/mobile-sdk-ios -->

# Mobile SDK для native iOS

Эта инструкция относится к `ShopStoryPlayerSDK 0.2.0-preview.2`. Требуются iOS 15+, Xcode 16+ и read-only доступ к пакету. Для другой версии используйте её README и migration notes: [версии и выпуск](<https://docs.shopstory.live/docs/guide/mobile-sdk-releases>).

## Установить SDK

В Xcode откройте **File → Add Package Dependencies**, вставьте URL из выданного доступа и выберите **Exact Version: 0.2.0-preview.2**. Добавьте product `ShopStoryPlayerSDK` в app target. Сохраните `Package.resolved` в репозитории приложения.

Если проект использует CocoaPods, возьмите `Podfile` из инструкции к этой версии пакета. Не добавляйте токен в URL, `Package.swift` или `Podfile`.

До запуска получите `applicationId`, один `feedProductId` с опубликованным VOD и один ID без видео. SDK использует реальные данные проекта; произвольные значения не создают demo-каталог.

## Запустить starter

[Скачать iOS starter](<https://docs.shopstory.live/downloads/shopstory-ios-starter.zip>). В архиве — app target, экран товара и SDK player. README описывает размещение приватного пакета рядом с проектом и запуск без клиентских данных.

После настройки откройте тестовый товар. Для активного видео должен появиться SDK entrypoint; нажатие открывает player. Для товара без видео entrypoint отсутствует. Ошибка SDK сохраняет экран товара рабочим.

Starter проверяет первое воспроизведение и передачу product action приложению. Корзину он не изменяет. Перенос активной сессии в Follow Me подключается отдельно по примеру ниже.

## Configure и video lookup

```swift
import ShopStoryPlayerSDK

private let shopStory = ShopStorySDK.shared
@MainActor
private var shopStoryConfigured = false
@MainActor
private var productStateRequest: ShopStoryProductStreamStateRequest?

@MainActor
func configureShopStory() {
    do {
        try shopStory.configure(
            applicationId: applicationId,
            hostAppVersion: Bundle.main.object(
                forInfoDictionaryKey: "CFBundleShortVersionString"
            ) as? String
        )
        shopStoryConfigured = true
    } catch {
        shopStoryConfigured = false
        hideShopStoryEntrypoint()
        reportIntegrationError(error)
    }
}

@MainActor
func clearShopStoryProduct() {
    productStateRequest?.cancel()
    productStateRequest = nil
    hideShopStoryEntrypoint()
}

@MainActor
func loadShopStory(for product: Product) {
    clearShopStoryProduct()
    guard shopStoryConfigured else { return }

    shopStory.setCurrentSurface(.productDetail)
    productStateRequest = shopStory.loadProductStreamState(
        feedProductId: product.feedProductId
    ) { result in
        switch result {
        case let .success(.active(entrypoint)):
            showShopStoryEntrypoint(entrypoint)
        case .success(.unavailable):
            hideShopStoryEntrypoint()
        case let .failure(error):
            hideShopStoryEntrypoint()
            reportIntegrationError(error)
        }
    }
}
```

Вызывайте конфигурацию один раз при запуске приложения. Ошибка оставляет ShopStory UI скрытым и не прерывает запуск приложения. При смене товара сначала отмените запрос и уберите прежний entrypoint, затем запрашивайте видео нового товара. При закрытии PDP вызовите `clearShopStoryProduct()`. `.unavailable` означает отсутствие доступного видео; `.failure(error)` передавайте в диагностический logger приложения. Полученный entrypoint передавайте в coordinator целиком; не собирайте player URL вручную.

## Full-screen player и Follow Me

Lookup из предыдущего примера только получает entrypoint. Для воспроизведения host app выполняет ещё шесть действий:

1. Root integration owner создаёт и удерживает одну playback session, один `ShopStoryPlayerCoordinator` и floating-player controller.
2. Full-screen container вызывает `coordinator.attach(to:)`, затем `coordinator.load(entrypoint:)`.
3. Coordinator delegate принимает типизированные product actions и lifecycle events.
4. При переходе из player в native PDP host отсоединяет player view без teardown, открывает PDP и монтирует ту же сессию в follow-me container.
5. После каждого route transition host вызывает `setCurrentSurface` и применяет `shouldShowFollowMePlayer()` к активной сессии: показывает или скрывает container и синхронизирует playback state.
6. `teardown()` вызывается при закрытии, завершении сессии или remote disable. Product handoff между full-screen и follow-me не завершает сессию.

Создавайте `ShopStoryPlaybackOwner` после успешного `configure`, когда начинается активная playback session. Root integration owner приложения удерживает этот объект до `stop()`.

player-owner.swift

```swift
import ShopStoryPlayerSDK
import UIKit

@MainActor
final class ShopStoryPlaybackOwner: ShopStoryPlayerCoordinatorDelegate {
    private let sdk = ShopStorySDK.shared
    private let floatingController = ShopStorySDK.shared.makeFloatingPlayerController()
    private(set) lazy var coordinator = ShopStoryPlayerCoordinator(delegate: self)

    func start(entrypoint: ShopStoryProductDetailEntrypoint, in container: UIView) {
        coordinator.attach(to: container)
        coordinator.load(entrypoint: entrypoint)
    }

    func moveToFollowMe(
        in container: UIView,
        surface: ShopStorySurface,
        mediaOrientation: ShopStoryMediaOrientation
    ) {
        coordinator.detach()
        coordinator.attach(to: container)
        reconcile(surface: surface, mediaOrientation: mediaOrientation)
    }

    func shopStoryPlayerCoordinator(
        _ coordinator: ShopStoryPlayerCoordinator,
        didReceive action: ShopStoryProductAction
    ) {
        guard let feedProductId = action.payload.feedProductId,
              !feedProductId.isEmpty else {
            reportInvalidProductAction(action)
            return
        }

        handleProductAction(action.kind, feedProductId: feedProductId)
    }

    func reconcile(
        surface: ShopStorySurface,
        mediaOrientation: ShopStoryMediaOrientation
    ) {
        sdk.setCurrentSurface(surface)

        if sdk.shouldShowFollowMePlayer() {
            let geometry = sdk.followMeGeometry(mediaOrientation: mediaOrientation)
            floatingController.show(using: geometry)
            coordinator.resumePlayback()
        } else {
            coordinator.pausePlayback(reason: .surfaceBlocked)
            floatingController.suppress()
        }
    }

    func shopStoryPlayerCoordinator(
        _ coordinator: ShopStoryPlayerCoordinator,
        didUpdate event: ShopStoryPlayerLifecycleEvent
    ) {
        handlePlayerLifecycle(event)
    }

    func stop() {
        floatingController.close()
        coordinator.teardown()
    }
}
```

`handleProductAction`, `handlePlayerLifecycle` и нативные контейнеры принадлежат приложению. После принятого перехода на PDP приложение монтирует follow-me container и вызывает `moveToFollowMe`. В этот метод передаётся контейнер, в котором SDK-компонент размещает `coordinator.playerView`. При переносе не вызывайте `load` или `teardown`: используется тот же coordinator и его player view. Полная схема контейнеров и их освобождения входит в руководство к поставленной версии SDK.

## Следующий шаг

Подключите [lifecycle и product actions](<https://docs.shopstory.live/docs/guide/mobile-sdk-releases>), затем [аналитику приложения](<https://docs.shopstory.live/docs/guide/analytics-mobile>).
