> ## 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 (v4)?

> 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

🔒

**Secure API Access:** To benefit from v4.1 secure API endpoints, pass the `sessionToken` parameter when showing the profile widget (required in upcoming SDK v4). This enables automatic routing to v4.1 secure endpoints. [Learn more about v4.1 →](https://docs.gameball.co/api-reference/introduction-v4.1.md)

```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)")
    }
}
```

***

## Request Parameters

**customerId** `String`

Unique permanent identifier of the customer whose profile you want to display. Optional (omit for guest mode).

**openDetail** `String`

Opens a specific widget section (e.g., `"details_earn"`, `"details_redeem"`).\
Defaults to the main profile view.

**hideNavigation** `Bool`

Hides the widget's navigation bar. Defaults to `false`.

**showCloseButton** `Bool`

Displays a floating close button. Defaults to `false`.

**closeButtonColor** `String`

Hex value (e.g., `"#FF5733"`) for close button color. Only used when `showCloseButton = true`. Defaults to `#CECECE`.\
The close button is drawn as a vector and tinted with this color, so it stays crisp at any screen scale.

**sessionToken** `String`

Optional per-request session token. Overrides the global SDK token. Required in upcoming SDK v4.

**mobile** `String`

Customer mobile number, used for channel merging.

**email** `String`

Customer email address, used for channel merging.

**externalLinkCallback** `((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.

**widgetEventCallback** `(([String: Any]?) -\> Void)`

Receives events the widget posts, such as game completion.

***

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

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

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

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

***

### Handle External Links

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.

***

### Channel Merging

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

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.

***

## Dismiss the Widget

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

> [card] Customer Info
>
> Points balance, tier, progress, and personal details

> [card] Reward Campaigns
>
> Available rewards, challenges, badges, and earning opportunities

> [card] Leaderboard
>
> Ranking, competition, and top performers

> [card] Transaction History
>
> Full breakdown of points earned and redeemed

> [!NOTE]
>
> All widget themes, layout options, and branding styles can be customized from the Gameball Dashboard.

***

## Best Practices

**1. Make It Discoverable**

Add the profile entry point in a visible place (e.g. tab bar, profile screen, side menu).

**2. Use Notification Badges**

Indicate new rewards or achievements to drive engagement.

**3. Deep Link Intelligently**

Use `openDetail` to open specific sections after qualifying user actions (e.g., after earning points).

**4. Handle Errors Gracefully**

Always provide fallback UI or messaging for network interruptions.

> [!TIP]
>
> Showing the profile widget right after a reward is earned significantly boosts customer engagement.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.