# FlexAd

Source: https://docs.buzzvil.com/docs/buzzbenefit-ios/v6/flexad
Platform: iOS
SDK major version: v6
Language: Korean (ko)

FlexAd 지면 추가하고 설정하고 SDK 인터페이스를 사용해 부가 기능을 구현하는 방법을 알아보세요.

## 주요 특징

FlexAd는 기존 네이티브 지면을 업그레이드한 새로운 광고 지면입니다. VAST 동영상, 정적 이미지, 네트워크 광고 등 다양한 광고 포맷을 하나의 지면에서 우선순위 기반으로 통합 지원하여 광고를 송출하여 앱 수익화를 가속할 수 있도록 하는 지면입니다. 유저가 더 많은 광고에 참여할 수 있도록 유도하는 다이나믹한 기능들도 제공합니다. ![alt text](https://docs.buzzvil.com/api/content-image/buzzbenefit-ios/v6/image/11-flexad/01-flex_ad_ui.png)

FlexAd는 다양한 광고 포맷을 단일 지면에서 자동으로 스위칭하며 다음과 같은 기능을 제공합니다:

- **다양한 광고 포맷**: VAST 동영상, 정적 이미지, 네트워크 광고를 하나의 지면에서 통합 지원
- **자동 광고 갱신**: 유저가 광고 참여를 완료하면 자동으로 다음 광고로 전환
- **시청 추적**: Viewability 기반 자동 일시정지/재개 (50% 이상 노출 기준)

## 연동

FlexAd를 앱에 연동하는 방법을 확인해 보세요.

### 1 단계. 준비하기

- Buzzvil SDK 버전

  - 단일형(`normal`, 기본값): v6.7.0 이상
  - 캐러셀형(`carousel`): v6.8.0 이상

- [시작하기](https://docs.buzzvil.com/docs/buzzbenefit-ios/v6/getting-started) 적용 완료

- FlexAd 지면에 사용할 Unit ID (이하 `YOUR_FLEXAD_UNIT_ID`)

### 2 단계. 광고 레이아웃 구성하기

FlexAd 뷰 구조를 확인하고 코드에서 `BuzzFlexAdView`를 추가하세요. ![alt text](https://docs.buzzvil.com/api/content-image/buzzbenefit-ios/v6/image/11-flexad/02-flex_ad_layout.png)

#### 뷰 구조

`BuzzFlexAdView`는 다음과 같은 구성으로 이루어집니다:

| 영역            | 설명                                 | 크기          |
| ------------- | ---------------------------------- | ----------- |
| **광고 콘텐츠 영역** | 로딩 상태, VAST 동영상, 정적 이미지 등이 표시되는 영역 | **16:9 비율** |
| **베네핏허브 CTA** | 베네핏허브로 이동하는 하단 버튼 (자동 포함)          | 약 40pt      |
| **구분선**       | 아래 콘텐츠와의 경계선 (자동 포함)               | 1pt         |

따라서 **광고 콘텐츠는 16:9이지만, 전체 `BuzzFlexAdView` 높이는 16:9 + 약 41pt**입니다.

#### 코드로 구성하는 경우

```swift
import UIKit
import BuzzvilSDK

class FlexAdViewController: UIViewController {
    private let buzzFlexAdView = BuzzFlexAdView()

    override func viewDidLoad() {
        super.viewDidLoad()

        // FlexAd 뷰 설정
        view.addSubview(buzzFlexAdView)

        // 제약 조건 설정 (wrap_content 형태)
        buzzFlexAdView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            buzzFlexAdView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            buzzFlexAdView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            buzzFlexAdView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
            // 높이는 자동 계산됨 (16:9 콘텐츠 + 하단 CTA + 구분선)
        ])

        // 그 외 추가 콘텐츠 설정
        setupAdditionalContent()
    }
}
```

> ✏️ **참고**
>
> - `BuzzFlexAdView`의 높이는 지정하지 않습니다. 뷰가 필요한 높이만 자동으로 차지합니다.
> - 광고 콘텐츠가 16:9 비율로 자동 관리되며, 하단 CTA와 구분선이 자동으로 포함됩니다.
> - `BuzzFlexAdView` 아래에 추가 콘텐츠를 배치할 때는 충분한 여백을 확보하세요.

### 3 단계. 광고 보여주기

`BuzzFlex` 객체를 생성하고 Delegate를 설정한 후 광고를 로드하세요. `bind()`는 Delegate 콜백 내에서 호출합니다.

```swift
import UIKit
import BuzzvilSDK

class FlexAdViewController: UIViewController {
    private let buzzFlexAdView = BuzzFlexAdView()

    // 기본값: 단일형(normal)
    private let buzzFlex = BuzzFlex(unitId: YOUR_FLEXAD_UNIT_ID)
    // 캐러셀형으로 표시하려면 아래처럼 생성하세요 (v6.8.0 이상)
    // private let buzzFlex = BuzzFlex(unitId: YOUR_FLEXAD_UNIT_ID, type: .carousel)

    override func viewDidLoad() {
        super.viewDidLoad()

        setupViews()

        // 1. Delegate 설정 (반드시 load() 이전에)
        buzzFlex.delegate = self

        // 2. (Optional) 주요 색상 설정
        buzzFlex.setPrimaryColor(.systemBlue)

        // 3. load() 호출 (Delegate가 설정된 상태에서)
        buzzFlex.load()
    }
}

// MARK: - BuzzFlexDelegate
extension FlexAdViewController: BuzzFlexDelegate {
    func buzzFlexOnSuccess() {
        // load() 완료 후 호출됨
        // 여기서 bind() 호출
        buzzFlexAdView.bind(buzzFlex)
    }

    func buzzFlexOnFailure(_ error: Error) {
        // 광고 할당에 실패했습니다
        if let buzzError = error as? BuzzError {
            switch buzzError {
            case .agePolicy:
                // age policy 위반: UI 숨김
                buzzFlexAdView.isHidden = true

            case .emptyResponse:
                // 할당 가능한 광고 없음: 일시적 상황으로 bind() 호출
                buzzFlexAdView.bind(buzzFlex)

            default:
                // 기타 에러: bind() 호출
                buzzFlexAdView.bind(buzzFlex)
            }
        }
    }

    func buzzFlexOnClicked() {
        // 유저가 광고를 클릭했습니다
    }
}
```

> ⚠️ **중요**
>
> - `delegate`를 반드시 `load()` **이전에** 설정하세요.
> - `bind()`는 `buzzFlexOnSuccess()` 또는 `buzzFlexOnFailure()` 콜백 내에서만 호출하세요.
> - `BuzzFlex`의 생명주기는 `BuzzFlexAdView`가 자동으로 관리하므로, `handleResume()`, `handlePause()` 등을 명시적으로 호출할 필요가 없습니다.
> - `buzzFlexOnSuccess()`는 최초 광고 할당에만 호출되며, 이후 자동 갱신은 UI 업데이트만 수행합니다.

### 4 단계. 기본 연동 테스트하기

모든 기본 연동 단계를 완료한 후 다음을 확인하세요:

1. 광고가 정상적으로 할당되고 표시되는지 확인
2. 광고 참여 후 자동으로 다음 광고로 전환되는지 확인
3. 베네핏허브 진입점이 정상적으로 동작하는지 확인

## 추가 구현

### 주요 색상 커스터마이징

FlexAd UI의 주요 색상(Primary Color)을 설정할 수 있습니다:

```swift
let buzzFlex = BuzzFlex(unitId: YOUR_FLEXAD_UNIT_ID)

// 색상 설정
buzzFlex.setPrimaryColor(.systemBlue)

buzzFlex.delegate = ...
buzzFlex.load()
```

리워드 pill, progress bar 등 UI 요소의 색상이 설정한 primary color로 변경됩니다.

## 광고 운영 정책 (필수 준수 사항)

### 광고 요청 정책

- 광고 요청은 앱 화면이 켜지는 시점에 최초 1회만 진행해주세요.
- 이후 광고 재요청(재할당)은 화면 전환 등 명확한 사용자 액션이 있을 때만 허용됩니다.
- 일정 시간 간격으로 자동으로 광고를 리프레시하는 방식은 금지됩니다.

> 과도한 광고 요청은 사용자 경험을 저해하고, 광고 효율 및 수익에 악영향을 미칠 수 있습니다.

## 오류 처리

FlexAd 연동 중 발생할 수 있는 오류를 처리하는 방법을 안내합니다.

### 오류 코드

`buzzFlexOnFailure()` 콜백에서 반환되는 `Error`를 `BuzzError`로 캐스팅하여 오류의 원인을 파악할 수 있습니다.

| 오류 코드                      | 설명                  | 원인                                                                                          | 해결 방법                                                                                              |
| -------------------------- | ------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `.notInitialized`          | SDK 미초기화            | [시작하기](https://docs.buzzvil.com/docs/buzzbenefit-ios/v6/getting-started)에서 SDK 초기화를 완료하지 않음 | SDK 초기화 확인                                                                                         |
| `.serverError`             | 서버 오류 (HTTP 500)    | 버즈빌 서버 내부 오류                                                                                | 네트워크 상태 확인 후 재시도                                                                                   |
| `.clientError`             | 클라이언트 오류 (HTTP 400) | 잘못된 요청 또는 만료된 토큰                                                                            | Unit ID 확인, 로그인 상태 재확인                                                                             |
| `.connectionTimeout`       | 네트워크 타임아웃           | 네트워크 연결 불안정 또는 느린 연결                                                                        | 네트워크 상태 확인                                                                                         |
| `.emptyResponse`           | 광고 미할당              | 할당할 수 있는 광고가 없음                                                                             | FlexAd 지면 활성화 여부 확인                                                                                |
| `.waitingForResponse`      | 응답 대기 중             | 이전 요청이 아직 진행 중                                                                              | 충분한 시간 경과 후 재시도                                                                                    |
| `.agePolicy`               | Google 정책 미충족       | 14세 이하 사용자로 판별됨                                                                             | 나이 정보 재확인                                                                                          |
| `.privacyPolicyNotGranted` | 개인정보 제공 동의 미흡       | 유저가 개인정보 수집에 동의하지 않음                                                                        | 유저에게 개인정보 수집 동의 요청                                                                                 |
| `.unknown`                 | 미확인 오류              | 특정할 수 없는 예외 상황                                                                              | [Troubleshooting](https://docs.buzzvil.com/docs/buzzbenefit-ios/v6/troubleshooting) 참고, 버즈빌 담당자 문의 |

agePolicy 오류시 지면을 제거하거나, 추가적인 동작을 커스텀해주세요.

### 오류 처리 예시

```swift
extension FlexAdViewController: BuzzFlexDelegate {
    func buzzFlexOnFailure(_ error: Error) {
        guard let buzzError = error as? BuzzError else { return }
        switch buzzError {
        case .emptyResponse:
            // 광고 미할당: UI 숨김 처리
            buzzFlexAdView.isHidden = true

        case .connectionTimeout:
            // 네트워크 오류: 재시도 버튼 제공
            showRetryButton()

        case .serverError:
            // 서버 오류: 사용자에게 안내
            let alert = UIAlertController(
                title: "알림",
                message: "서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.",
                preferredStyle: .alert
            )
            alert.addAction(UIAlertAction(title: "확인", style: .default))
            present(alert, animated: true)

        default:
            // 기타 오류
            print("FlexAd 로드 실패: \(error.localizedDescription)")
        }
    }

    func buzzFlexOnSuccess() {
        // 성공 처리
        buzzFlexAdView.isHidden = false
    }

    func buzzFlexOnClicked() {
        // 클릭 처리
    }
}
```

> ✏️ **참고**
>
> - 광고 미할당(`.emptyResponse`)은 FlexAd 지면이 활성화되지 않았거나, 할당할 수 있는 광고가 일시적으로 없는 정상적인 상황입니다.
> - 자세한 문제 해결 방법은 [Troubleshooting](https://docs.buzzvil.com/docs/buzzbenefit-ios/v6/troubleshooting) 페이지를 참고하세요.

## 자주 하는 질문

| 질문                         | 답변                                                                                                                         |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 광고가 할당되지 않으면 어떻게 하나요?      | `buzzFlexOnFailure()` 콜백에서 에러 타입을 확인할 수 있습니다. 네트워크 연결 상태를 확인하거나, FlexAd 지면이 활성화되어 있는지 버즈빌 담당자(<help@buzzvil.com>)에게 문의하세요. |
| 광고 참여 후 자동으로 다음 광고로 전환되나요? | 네, FlexAd는 유저가 광고 참여를 완료하면 자동으로 다음 광고로 전환합니다. 별도의 구현이 필요하지 않습니다.                                                           |
| 특정 광고 포맷만 사용할 수 있나요?       | FlexAd는 최적화된 광고 경험을 제공하기 위해 모든 광고 포맷을 함께 사용하도록 설계되었습니다. 특정 포맷만 선별하는 방식은 지원하지 않습니다.                                         |
| 광고 클릭 시 커스텀 동작을 추가할 수 있나요? | `buzzFlexOnClicked()` 콜백에서 로그 기록이나 분석만 수행하는 것을 권장합니다. 광고의 자동 동작과 충돌할 수 있으므로 추가적인 비즈니스 로직 구현은 피해주세요.                        |
| Benefithub광고만 나와요          | 연동 확인 및 로그 확인을 부탁드립니다. 연동, 셋팅, 서버 오류일 경우 Benefithub inhouse 광고만 노출될 수 있습니다.                                                |
