# Historias push para iOS

Las historias push convierten una notificación push expandida en una experiencia de historias a pantalla completa, al estilo de Instagram: imágenes a sangre completa, barras de progreso superiores, avance automático de páginas, toque para navegar y un botón con un enlace profundo (deep link). Se renderizan mediante una Extensión de Contenido de Notificación (Notification Content Extension) que utiliza el módulo independiente `PushwooshNotificationUI`: solo tienes que crear una subclase de un controlador de vista y el SDK se encarga del análisis, la carga de imágenes, el progreso, la temporización, la navegación y los enlaces profundos.

Disponible desde la versión 7.0.46.

<img src="/ios-push-stories-demo.webp" alt="Historias push reproduciéndose en una notificación expandida"/>

## 1. Añade una Extensión de Contenido de Notificación

En Xcode, elige **Archivo > Nuevo > Destino…** (File > New > Target…), selecciona **Extensión de Contenido de Notificación** (Notification Content Extension) y asígnale un nombre (por ejemplo, **StoriesContentExtension**).

<img src="/ios-push-stories-1.webp" alt="Añadiendo un destino de Extensión de Contenido de Notificación en Xcode"/>

## 2. Añade el módulo PushwooshNotificationUI

`PushwooshNotificationUI` es un módulo independiente sin otras dependencias de Pushwoosh, por lo que se mantiene pequeño dentro del proceso de la extensión. Añádelo al **destino de la extensión de contenido** (no al destino de la aplicación).

**Swift Package Manager**

<img src="/ios-push-stories-spm.webp" alt="Añadiendo el paquete PushwooshNotificationUI al destino de la extensión"/>

**CocoaPods**

```ruby
target 'StoriesContentExtension' do
  use_frameworks!

  pod 'PushwooshXCFramework/PushwooshNotificationUI'
end
```

## 3. Crea una subclase del controlador de vista de historias

Reemplaza el cuerpo del `NotificationViewController` generado con una subclase de `PushwooshStoriesViewController`. Esa es toda la integración.

<Tabs>
<TabItem label="Swift">
```swift
import PushwooshNotificationUI

class NotificationViewController: PushwooshStoriesViewController {}
```
</TabItem>
</Tabs>

## 4. Configura el Info.plist de la extensión

En el **Info.plist** de la extensión de contenido, establece las siguientes claves en `NSExtension > NSExtensionAttributes`:

```xml
<key>UNNotificationExtensionCategory</key>
<string>PW_STORIES</string>
<key>UNNotificationExtensionUserInteractionEnabled</key>
<true/>
<key>UNNotificationExtensionDefaultContentHidden</key>
<true/>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1.5</real>
```

<Aside type="note">
`UNNotificationExtensionCategory` debe coincidir con la categoría que envías en el push (`PW_STORIES`). `UNNotificationExtensionInitialContentSizeRatio` establece la relación de altura inicial de la vista expandida.
</Aside>

## 5. Envía una notificación de historias push

Envía una notificación cuya categoría sea `PW_STORIES` y cuyos datos personalizados contengan un bloque `pw_stories`. Utiliza el campo dedicado `ios_category_custom` para la categoría y el campo `data` para el payload de las historias.

```json
{
  "request": {
    "application": "APPLICATION_CODE",
    "auth": "API_ACCESS_TOKEN",
    "notifications": [
      {
        "send_date": "now",
        "content": "Tap to explore",
        "ios_title": "Push Stories",
        "ios_category_custom": "PW_STORIES",
        "ios_root_params": {
          "aps": {
            "mutable-content": 1
          }
        },
        "data": {
          "pw_stories": {
            "pages": [
              {
                "image": "https://example.com/story-1.jpg",
                "duration": 5.0,
                "link": "yourapp://page1",
                "button_title": "Get started",
                "title": "Welcome",
                "subtitle": "Swipe to explore what's new"
              },
              {
                "image": "https://example.com/story-2.jpg",
                "duration": 4.0,
                "link": "yourapp://page2",
                "button_title": "Learn more",
                "title": "Stay in the loop",
                "subtitle": "Updates, tips and more"
              }
            ]
          }
        }
      }
    ]
  }
}
```

Cada página admite los siguientes campos. Solo `image` es obligatorio; el resto son opcionales.

| Campo | Descripción |
|-------|-------------|
| `image` | URL de la imagen a pantalla completa para la página. |
| `duration` | Segundos que la página permanece en pantalla antes de avanzar automáticamente. El valor predeterminado es de unos 5 segundos. |
| `link` | Enlace profundo (deep link) que se abre al tocar el botón de la página. |
| `button_title` | Título del botón de la página. |
| `title` | Texto del título superpuesto en la página. |
| `subtitle` | Texto del subtítulo superpuesto en la página. |

<Aside type="note">
Un bloque `pw_stories` ausente, vacío o malformado no bloquea la extensión, sino que recurre al contenido de notificación predeterminado. `mutable-content: 1` solo es necesario para la ruta opcional de precarga de medios que se describe a continuación.
</Aside>

## Personalización

Sobrescribe las propiedades en tu subclase para ajustar la experiencia. Todas tienen valores predeterminados razonables.

<Tabs>
<TabItem label="Swift">
```swift
import PushwooshNotificationUI

class NotificationViewController: PushwooshStoriesViewController {
    override var storyAspectRatio: CGFloat { 1.5 }     // keep in sync with InitialContentSizeRatio
    override var hapticsEnabled: Bool { true }
    override var longPressToPauseEnabled: Bool { true }
    override var crossfadesBetweenPages: Bool { true }
    override var loopsAfterLastPage: Bool { false }
}
```
</TabItem>
</Tabs>

| Propiedad | Predeterminado | Descripción |
|----------|---------|-------------|
| `storyAspectRatio` | `1.5` | Relación de aspecto (alto ÷ ancho) del área de las historias. Mantenla sincronizada con `UNNotificationExtensionInitialContentSizeRatio`. |
| `hapticsEnabled` | `false` | Reproduce una ligera respuesta táctil al navegar por las zonas de toque. |
| `longPressToPauseEnabled` | `false` | Mantener presionado pausa la página actual; soltar reanuda. |
| `crossfadesBetweenPages` | `false` | Transición suave (cross-dissolve) entre páginas en lugar de un corte brusco. Vuelve a un cambio instantáneo si la opción Reducir Movimiento está activada. |
| `loopsAfterLastPage` | `false` | Reinicia desde la primera página después de que la última termine. |
| `appGroupIdentifier` | `nil` | App Group compartido con una Extensión de Servicio de Notificación (Notification Service Extension) para la precarga de medios (ver más abajo). |

También puedes sobrescribir `showDefaultContent(for:)` para personalizar el contenido de respaldo que se muestra cuando el payload falta o está malformado (por defecto, muestra el cuerpo de la alerta).

### Precarga de medios

Para obtener un primer fotograma instantáneo y sin conexión, comparte un App Group entre tu Extensión de Contenido y una Extensión de Servicio de Notificación. Sobrescribe `appGroupIdentifier` en el controlador de historias y luego precarga los medios desde el `didReceive(_:withContentHandler:)` de tu Extensión de Servicio:

<Tabs>
<TabItem label="Swift">
```swift
import PushwooshNotificationUI

PushwooshStoriesMediaPrefetcher.prefetch(
    userInfo: request.content.userInfo,
    appGroupIdentifier: "group.com.example.app"
) {
    contentHandler(bestAttemptContent)
}
```
</TabItem>
</Tabs>

Habilita la capacidad **App Groups** en ambas extensiones con el mismo identificador de grupo y envía `mutable-content: 1` para que se ejecute la Extensión de Servicio. Sin un App Group, los medios se almacenan en caché en el directorio `tmp` de la extensión.

### Callbacks de ciclo de vida y análisis

Establece `storiesDelegate` para observar los eventos de las historias: impresiones de página, toques de botón, finalización y contenido de respaldo. Adáptate a `PushwooshStoriesDelegate`; cada método es opcional, así que implementa solo los que necesites.

<Tabs>
<TabItem label="Swift">
```swift
import PushwooshNotificationUI

class NotificationViewController: PushwooshStoriesViewController, PushwooshStoriesDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        storiesDelegate = self
    }

    func storiesViewController(_ controller: PushwooshStoriesViewController, didStartWithPageCount pageCount: Int) {}
    func storiesViewController(_ controller: PushwooshStoriesViewController, didShow page: StoryPage, at index: Int) {}
    func storiesViewController(_ controller: PushwooshStoriesViewController, didTapActionFor page: StoryPage, at index: Int) {}
    func storiesViewControllerDidFinish(_ controller: PushwooshStoriesViewController) {}
    func storiesViewControllerDidShowFallback(_ controller: PushwooshStoriesViewController) {}
}
```
</TabItem>
</Tabs>

| Callback | Cuándo se activa |
|----------|----------------|
| `didStartWithPageCount:` | Se ha analizado un payload de historias válido y la reproducción está a punto de comenzar. |
| `didShow:at:` | Una página se ha hecho visible. Úsalo para las impresiones por página. |
| `didTapActionFor:at:` | El usuario ha tocado el botón de llamada a la acción. |
| `storiesViewControllerDidFinish:` | La última página ha terminado de reproducirse. |
| `storiesViewControllerDidShowFallback:` | El payload faltaba o estaba malformado y se ha mostrado el contenido de respaldo. |

El `StoryPage` que se pasa a los callbacks expone los campos `imageURL`, `duration`, `link`, `buttonTitle`, `title` y `subtitle` de la página.

### Navegación

Toca el tercio derecho de la pantalla para ir a la página siguiente y el tercio izquierdo para retroceder. El deslizamiento horizontal no se utiliza intencionadamente, ya que entra en conflicto con el gesto del sistema para descartar notificaciones. Tocar el botón de la página abre su enlace profundo (deep link) y descarta la notificación.

## Referencias

<CardGrid>
  <LinkCard
    title="Referencia de la API del SDK de iOS"
    description="Documentación técnica completa que cubre todas las clases, métodos y propiedades públicas."
    href="https://pushwoosh.github.io/pushwoosh-ios-sdk/"
  />
  <LinkCard
    title="API de Mensajes"
    description="Envía notificaciones, incluyendo los campos data e ios_category_custom, a través de la API de Pushwoosh."
    href="/developer/api-reference/messages-api/"
  />
</CardGrid>