# Push stories para iOS

As push stories transformam uma notificação push expandida em uma experiência de stories em tela cheia, no estilo do Instagram: imagens de ponta a ponta, barras de progresso no topo, avanço automático de páginas, toque para navegar e um botão com um deep link. Elas são renderizadas por uma Notification Content Extension usando o módulo autônomo `PushwooshNotificationUI` — você subclasse um view controller e o SDK cuida da análise, carregamento de imagens, progresso, tempo, navegação e deep links.

Disponível desde a versão 7.0.46.

<img src="/ios-push-stories-demo.webp" alt="Push stories sendo reproduzidas em uma notificação expandida"/>

## 1. Adicione uma Notification Content Extension

No Xcode, escolha **File > New > Target…**, selecione **Notification Content Extension** e dê um nome a ela (por exemplo, **StoriesContentExtension**).

<img src="/ios-push-stories-1.webp" alt="Adicionando um target de Notification Content Extension no Xcode"/>

## 2. Adicione o módulo PushwooshNotificationUI

`PushwooshNotificationUI` é um módulo autônomo sem outras dependências do Pushwoosh, então ele permanece pequeno dentro do processo da extensão. Adicione-o ao **target da extensão de conteúdo** (não ao target do aplicativo).

**Swift Package Manager**

<img src="/ios-push-stories-spm.webp" alt="Adicionando o pacote PushwooshNotificationUI ao target da extensão"/>

**CocoaPods**

```ruby
target 'StoriesContentExtension' do
  use_frameworks!

  pod 'PushwooshXCFramework/PushwooshNotificationUI'
end
```

## 3. Subclasse o view controller de stories

Substitua o corpo do `NotificationViewController` gerado por uma subclasse de `PushwooshStoriesViewController`. Essa é toda a integração.

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

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

## 4. Configure o Info.plist da extensão

No **Info.plist** da extensão de conteúdo, defina as seguintes chaves em `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` deve corresponder à categoria que você envia no push (`PW_STORIES`). `UNNotificationExtensionInitialContentSizeRatio` define a proporção de altura inicial da visualização expandida.
</Aside>

## 5. Envie uma notificação de push stories

Envie uma notificação cuja categoria seja `PW_STORIES` e cujos dados personalizados contenham um bloco `pw_stories`. Use o campo dedicado `ios_category_custom` para a categoria e o campo `data` para o payload dos stories.

```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 suporta os seguintes campos. Apenas `image` é obrigatório; os demais são opcionais.

| Campo | Descrição |
|-------|-------------|
| `image` | URL da imagem em tela cheia para a página. |
| `duration` | Segundos que a página permanece na tela antes de avançar automaticamente. O padrão é cerca de 5 segundos. |
| `link` | Deep link aberto quando o botão da página é tocado. |
| `button_title` | Título do botão da página. |
| `title` | Texto do título sobreposto na página. |
| `subtitle` | Texto do subtítulo sobreposto na página. |

<Aside type="note">
Um bloco `pw_stories` ausente, vazio ou malformado não causa falha na extensão — ele recorre ao conteúdo de notificação padrão. `mutable-content: 1` é necessário apenas para o caminho opcional de pré-cache de mídia descrito abaixo.
</Aside>

## Personalização

Sobrescreva propriedades em sua subclasse para ajustar a experiência. Todas têm padrões sensatos.

<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>

| Propriedade | Padrão | Descrição |
|----------|---------|-------------|
| `storyAspectRatio` | `1.5` | Proporção (altura ÷ largura) da área dos stories. Mantenha-a em sincronia com `UNNotificationExtensionInitialContentSizeRatio`. |
| `hapticsEnabled` | `false` | Reproduz um leve toque tátil na navegação por zona de toque. |
| `longPressToPauseEnabled` | `false` | Pressionar e segurar pausa a página atual; soltar retoma. |
| `crossfadesBetweenPages` | `false` | Transição suave (cross-dissolve) entre as páginas em vez de um corte seco. Recorre a uma mudança instantânea quando a opção Reduzir Movimento está ativada. |
| `loopsAfterLastPage` | `false` | Reinicia a partir da primeira página após a última terminar. |
| `appGroupIdentifier` | `nil` | App Group compartilhado com uma Notification Service Extension para pré-cache de mídia (veja abaixo). |

Você também pode sobrescrever `showDefaultContent(for:)` para personalizar o conteúdo de fallback exibido quando o payload está ausente ou malformado (por padrão, ele mostra o corpo do alerta).

### Pré-cache de mídia

Para um primeiro quadro instantâneo e offline, compartilhe um App Group entre sua Content Extension e uma Notification Service Extension. Sobrescreva `appGroupIdentifier` no controller de stories e, em seguida, pré-baixe a mídia a partir do `didReceive(_:withContentHandler:)` da sua Service Extension:

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

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

Habilite a capacidade **App Groups** em ambas as extensões com o mesmo identificador de grupo e envie `mutable-content: 1` para que a Service Extension seja executada. Sem um App Group, a mídia é armazenada em cache no diretório `tmp` da extensão.

### Callbacks de ciclo de vida e análise

Defina `storiesDelegate` para observar eventos de stories — impressões de página, toques em botões, conclusão e fallback. Conforme-se com `PushwooshStoriesDelegate`; cada método é opcional, então implemente apenas os que você precisa.

<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 | Quando é acionado |
|----------|----------------|
| `didStartWithPageCount:` | Um payload de stories válido foi analisado e a reprodução está prestes a começar. |
| `didShow:at:` | Uma página tornou-se visível. Use-o para impressões por página. |
| `didTapActionFor:at:` | O usuário tocou no botão de chamada para ação. |
| `storiesViewControllerDidFinish:` | A última página terminou de ser reproduzida. |
| `storiesViewControllerDidShowFallback:` | O payload estava ausente ou malformado e o conteúdo de fallback foi exibido. |

O `StoryPage` passado para os callbacks expõe os `imageURL`, `duration`, `link`, `buttonTitle`, `title` e `subtitle` da página.

### Navegação

Toque no terço direito da tela para ir para a próxima página e no terço esquerdo para voltar. O deslize horizontal não é usado intencionalmente, pois entra em conflito com o gesto do sistema para dispensar notificações. Tocar no botão da página abre seu deep link e dispensa a notificação.

## Referências

<CardGrid>
  <LinkCard
    title="Referência da API do SDK para iOS"
    description="Documentação técnica completa cobrindo todas as classes, métodos e propriedades públicas."
    href="https://pushwoosh.github.io/pushwoosh-ios-sdk/"
  />
  <LinkCard
    title="API de Mensagens"
    description="Envie notificações, incluindo os campos data e ios_category_custom, através da API do Pushwoosh."
    href="/developer/api-reference/messages-api/"
  />
</CardGrid>