> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gameball.co/llms.txt
> Use this file to discover all available pages before exploring further.

# How Do I Show the Profile Widget in the iOS SDK (v3)?

> Display the Gameball customer profile widget in your iOS app

# Display the Profile Widget

The Gameball profile widget shows customer points, tiers, achievements, leaderboards, available rewards, and full loyalty activity—all inside a native in-app experience.

Use this when the customer taps **Rewards**, **My Points**, **Loyalty**, or any entry point that should open their Gameball profile.

***

## Basic Usage

<div className="security-banner">
  <div className="security-banner-icon">🔒</div>

  <div className="security-banner-content">
    <strong>Secure API Access:</strong> To benefit from v4.1 secure API endpoints, pass the <code>sessionToken</code> parameter when showing the profile widget (required in upcoming SDK v4). This enables automatic routing to v4.1 secure endpoints. <a href="/api-reference/introduction-v4.1">Learn more about v4.1 →</a>
  </div>
</div>

<CodeGroup>
  ```swift Swift (Completion Handler) theme={null}
  import Gameball

  let request = ShowProfileRequest(
      customerId: "customer-123"
  )

  GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { success, errorMessage in
      if success {
          print("Profile displayed successfully")
      } else {
          print("Error: \(errorMessage ?? \"\")")
      }
  }
  ```

  ```swift Swift (With Session Token) theme={null}
  let request = ShowProfileRequest(customerId: "customer-123")

  GameballApp.getInstance().showProfile(
      request,
      presentationStyle: .fullScreen,
      sessionToken: "customer-token"
  ) { _, _ in }
  ```

  ```swift Swift (Async/Await) theme={null}
  import Gameball

  Task {
      do {
          let request = ShowProfileRequest(customerId: "customer-123")
          try await GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen)
      } catch {
          print("Error: \(error.localizedDescription)")
      }
  }
  ```
</CodeGroup>

***

## Request Parameters

<ParamField body="customerId" type="String">
  Unique permanent identifier of the customer whose profile you want to display. Optional in v3.1.1 (omit for guest mode).
</ParamField>

<ParamField body="openDetail" type="String">
  Opens a specific widget section (e.g., `"details_earn"`, `"details_redeem"`).\
  Defaults to the main profile view.
</ParamField>

<ParamField body="hideNavigation" type="Bool">
  Hides the widget's navigation bar. Defaults to `false`.
</ParamField>

<ParamField body="showCloseButton" type="Bool">
  Displays a floating close button. Defaults to `false`.
</ParamField>

<ParamField body="closeButtonColor" type="String">
  Hex value (e.g., `"#FF5733"`) for close button color. Only used when `showCloseButton = true`. Defaults to `#CECECE`.\
  From v3.2.1 the close button is drawn as a vector and tinted with this color, so it stays crisp at any screen scale.
</ParamField>

<ParamField body="sessionToken" type="String">
  Optional per-request session token. Overrides the global SDK token. Required in upcoming SDK v4.
</ParamField>

<ParamField body="mobile" type="String">
  Customer mobile number, used for channel merging. Available from v3.2.0.
</ParamField>

<ParamField body="email" type="String">
  Customer email address, used for channel merging. Available from v3.2.0.
</ParamField>

<ParamField body="externalLinkCallback" type="((String) -> Void)">
  Handler for links the widget tags with `gbExternalBrowser=true`. When provided, the link is delegated to your app; otherwise the SDK opens it in the system browser. Available from v3.2.0.
</ParamField>

<ParamField body="widgetEventCallback" type="(([String: Any]?) -> Void)">
  Receives events the widget posts, such as game completion. Available from v3.2.0.
</ParamField>

***

### Validation Rules

* No validation for `customerId` (guest mode is allowed). Provide `customerId` for authenticated profiles.

***

## Advanced Configuration

### Open a Specific Section

```swift theme={null}
let request = ShowProfileRequest(
    customerId: "customer-123",
    openDetail: "details_earn"
)

GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
```

***

### Guest Mode (v3.1.1+)

```swift theme={null}
// No customerId required
let guestRequest = ShowProfileRequest(
    showCloseButton: true,
    closeButtonColor: "#4CAF50"
)

GameballApp.getInstance().showProfile(guestRequest, presentationStyle: .pageSheet) { _, _ in }
```

***

### Custom Close Button

```swift theme={null}
let request = ShowProfileRequest(
    customerId: "customer-123",
    showCloseButton: true,
    closeButtonColor: "#FF5733"
)

GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
```

***

### Hide Navigation

<Note>
  Best used with `openDetail` to guide users directly to a specific section when navigation is hidden.
</Note>

```swift theme={null}
let request = ShowProfileRequest(
    customerId: "customer-123",
    hideNavigation: true,
    openDetail: "details_earn"
)

GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
```

***

### Handle External Links (v3.2.0+)

Links the widget tags with `gbExternalBrowser=true` open in the system browser by default. Provide an `externalLinkCallback` to handle them yourself instead (for example, to open an in-app browser or route to a custom screen):

```swift theme={null}
let request = ShowProfileRequest(
    customerId: "customer-123",
    externalLinkCallback: { url in
        // Open the link your own way, e.g. an in-app browser or a custom screen
        print("External link: \(url)")
    }
)

GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
```

<Note>
  When `externalLinkCallback` is set, the SDK delegates the link to your app and does not open the system browser.
</Note>

***

### Channel Merging (v3.2.0+)

Pass the customer's `mobile` and `email` so Gameball can merge the customer's channels:

```swift theme={null}
let request = ShowProfileRequest(
    customerId: "customer-123",
    mobile: "+201234567890",
    email: "customer@example.com"
)

GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
```

***

## Widget Events (v3.2.0+)

Pass a `widgetEventCallback` to react to events the widget posts, such as a customer finishing a game. Each event arrives as a `[String: Any]` dictionary with a top-level `type` and a nested `metadata` dictionary:

```swift theme={null}
let request = ShowProfileRequest(
    customerId: "customer-123",
    widgetEventCallback: { event in
        guard let event = event else { return }   // nil means a malformed payload
        let type = event["type"] as? String       // e.g. "gameCompleted"
        let metadata = event["metadata"] as? [String: Any] ?? [:]

        if type == "gameCompleted" {
            let hasWon = metadata["hasWon"] as? Bool ?? false
            let rewardName = metadata["rewardName"] as? String
            if hasWon {
                // Refresh the customer's balance, show a win animation, etc.
            }
        }
    }
)

GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
```

### `gameCompleted` Metadata

| Field          | Type      | Description                                                                                                                                                                              |
| :------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hasWon`       | `Bool`    | Whether the customer won a reward this round                                                                                                                                             |
| `rewardType`   | `String?` | Reward category: `Default`, `Friend`, `Bonus`, `CustomText`, `Streak`, `NoReward`                                                                                                        |
| `discountType` | `String?` | Coupon kind when the win is a coupon: `Fixed`, `Percentage`, `FreeShipping`, `FreeProduct`, `Custom`, `RechargeFixed`, `RechargePercentage`, `ExternalReward`. `nil` for non-coupon wins |
| `rewardName`   | `String?` | Localized, human-readable reward name                                                                                                                                                    |
| `campaignId`   | `String`  | Challenge or campaign identifier                                                                                                                                                         |
| `campaignType` | `String?` | Game type: `spinTheWheel`, `slotMachine`, `quiz`, `scratchCard`, `matchCards`, `catcher`, `ticTacToe`, `shooter`, `puzzle`, `tapTarget`, `highwayDrive`                                  |

<Note>
  All `gameCompleted` values arrive as `String` or `Bool`. There are no numeric fields.
</Note>

***

## Dismiss the Widget (v3.2.0+)

Dismiss the widget programmatically from your app. The call is a no-op when no widget is showing:

```swift theme={null}
GameballApp.getInstance().hideProfile()
```

The widget can also dismiss itself from the web side by calling `window.GameballWidget.closeWidget()`.

***

## Implementation Examples

### UIKit — Button Action

```swift theme={null}
import UIKit
import Gameball

class ProfileViewController: UIViewController {
    
    @IBAction func showProfileTapped(_ sender: UIButton) {
        let request = ShowProfileRequest(
            customerId: "customer-123",
            showCloseButton: true,
            closeButtonColor: "#4CAF50"
        )
        
        GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { success, errorMessage in
            if let errorMessage = errorMessage {
                self.showAlert(message: errorMessage)
            } else if success {
                print("Profile displayed")
            }
        }
    }
    
    func showAlert(message: String) {
        let alert = UIAlertController(
            title: "Error",
            message: message,
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}
```

***

### SwiftUI Integration

```swift theme={null}
import SwiftUI
import Gameball

struct ProfileView: View {
    @State private var showError = false
    @State private var errorMessage = ""
    
    var body: some View {
        VStack {
            Button("View Rewards & Achievements") {
                showWidget()
            }
            .padding()
        }
        .alert("Error", isPresented: $showError) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(errorMessage)
        }
    }
    
    func showWidget() {
        guard let vc = UIApplication.shared.windows.first?.rootViewController else {
            return
        }

        let request = ShowProfileRequest(
            customerId: "customer-123",
            showCloseButton: true
        )

        Task {
            do {
                try await GameballApp.getInstance().showProfile(
                    request,
                    presentationStyle: .fullScreen,
                    presentingViewController: vc
                )
            } catch {
                errorMessage = error.localizedDescription
                showError = true
            }
        }
    }
}
```

***

### Tab Bar Integration

```swift theme={null}
import UIKit
import Gameball

class MainTabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension MainTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {

        if viewController.tabBarItem.tag == 2 { // Rewards tab
            let request = ShowProfileRequest(
                customerId: "customer-123"
            )
            
            GameballApp.getInstance().showProfile(request, presentationStyle: .fullScreen) { _, _ in }
            return false
        }
        
        return true
    }
}
```

***

## Widget Content

<CardGroup cols={2}>
  <Card title="Customer Info" icon="user">
    Points balance, tier, progress, and personal details
  </Card>

  <Card title="Reward Campaigns" icon="gift">
    Available rewards, challenges, badges, and earning opportunities
  </Card>

  <Card title="Leaderboard" icon="trophy">
    Ranking, competition, and top performers
  </Card>

  <Card title="Transaction History" icon="clock-rotate-left">
    Full breakdown of points earned and redeemed
  </Card>
</CardGroup>

<Info>
  All widget themes, layout options, and branding styles can be customized from the Gameball Dashboard.
</Info>

***

## Best Practices

<Steps>
  <Step title="Make It Discoverable">
    Add the profile entry point in a visible place (e.g. tab bar, profile screen, side menu).
  </Step>

  <Step title="Use Notification Badges">
    Indicate new rewards or achievements to drive engagement.
  </Step>

  <Step title="Deep Link Intelligently">
    Use `openDetail` to open specific sections after qualifying user actions (e.g., after earning points).
  </Step>

  <Step title="Handle Errors Gracefully">
    Always provide fallback UI or messaging for network interruptions.
  </Step>
</Steps>

<Tip>
  Showing the profile widget right after a reward is earned significantly boosts customer engagement.
</Tip>
