반응형
SwiftUI를 통해 광고배너를 만들어봤습니다.
먼저 광고배너를 만들기 위해서 TabView를 채택하여 구현을 했고 구현 목표는 아래와 같았습니다.
- 3초간격으로 화면 이동
- 무한적인 광고배너 ( 1 -> 2 -> 3 -> 1 -> 2-> . . .)
먼저 무한적인 광고배너를 만들기 위해 아래의 글을 참고해서 구현했습니다.
Bidirectional infinite PageView in SwiftUI
I'm trying to make a bidirectional TabView (with .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))) whose datasource will change over time. Below is the code that describes what is expected...
stackoverflow.com
간단 원리 설명
- InfinitePageView는 TabView를 사용하여 세 개의 뷰(이전, 현재, 다음)를 순환합니다.
- 사용자가 스와이프하여 탭을 변경하면 currentTab 상태 변수가 업데이트됩니다.
- currentTab이 0이 아니면 onDisappear 메서드가 호출되어 selection을 업데이트하고 currentTab을 0으로 초기화합니다.
- 이 과정은 이전, 현재, 다음 항목을 동적으로 계산하여 무한히 스크롤할 수 있게 합니다.
- 스와이프가 매우 빠르게 일어날 때 발생할 수 있는 글리치를 방지하기 위해 currentTab이 0이 아닐 때 스와이프를 비활성화합니다.
코드 설명을 위해 주석문을 달아놨습니다.
InfinitePageView
// InfinitePageView는 주어진 선택 항목(T)을 기반으로 이전 및 다음 항목을 보여주는 무한 스크롤 뷰입니다.
// C는 각 페이지의 내용을 나타내는 뷰의 유형입니다. T는 선택 항목의 타입으로 Hashable을 준수해야 합니다.
struct InfinitePageView<C, T>: View where C: View, T: Hashable {
// 현재 선택된 항목을 바인딩으로 받아옵니다.
@Binding var selection: T
// 주어진 선택 항목에서 이전 항목을 계산하는 함수입니다.
let before: (T) -> T
// 주어진 선택 항목에서 다음 항목을 계산하는 함수입니다.
let after: (T) -> T
// 주어진 선택 항목을 기반으로 뷰를 생성하는 클로저입니다.
@ViewBuilder let view: (T) -> C
// 현재 탭의 인덱스를 저장하는 상태 변수입니다.
@State private var currentTab: Int = 0
// 뷰의 본체입니다.
var body: some View {
// 이전 및 다음 선택 항목을 계산합니다.
let previousIndex = before(selection)
let nextIndex = after(selection)
// TabView를 생성하여 선택 항목의 이전, 현재, 다음 항목을 표시합니다.
TabView(selection: $currentTab) {
// 이전 선택 항목을 표시하는 뷰입니다.
view(previousIndex)
.tag(-1)
// 현재 선택 항목을 표시하는 뷰입니다.
view(selection)
.onDisappear() {
// 현재 탭이 변경될 때 선택 항목을 업데이트합니다.
if currentTab != 0 {
selection = currentTab < 0 ? previousIndex : nextIndex
currentTab = 0
}
}
.tag(0)
// 다음 선택 항목을 표시하는 뷰입니다.
view(nextIndex)
.tag(1)
}
// 페이지 인디케이터를 숨기고 페이지 스타일로 TabView를 설정합니다.
.tabViewStyle(.page(indexDisplayMode: .never))
// 탭이 0이 아닐 때 스와이프를 비활성화하여 빠른 스와이프 시 발생하는 글리치를 방지합니다.
.disabled(currentTab != 0) // FIXME: workaround to avoid glitch when swiping twice very quickly
}
}
ADBannerViewModel
import SwiftUI
import Combine
// ADBannerViewModel은 광고 배너의 상태를 관리하는 뷰 모델입니다.
class ADBannerViewModel: ObservableObject {
// 현재 선택된 광고 인덱스를 퍼블리시하여 변경 사항을 구독할 수 있게 합니다.
@Published var adIndex = 0
// 광고 이미지를 제공하는 모델 인스턴스를 생성합니다.
private let model = ADBannerModel()
// 타이머를 관리하기 위한 변수입니다.
private var timer: Timer?
// 광고 이미지 배열을 반환하는 계산 프로퍼티입니다.
var adImages: [String] {
return model.adImages
}
// 주어진 인덱스를 올바른 범위로 수정하는 함수입니다.
// 광고 이미지 배열의 길이에 맞춰 인덱스를 순환시킵니다.
func correctedIndex(for index: Int) -> Int {
let count = model.adImages.count
return (count + index) % count
}
// 자동 스크롤을 시작하는 함수입니다.
func startAutoScroll() {
// 기존 타이머가 있으면 중지합니다.
stopAutoScroll()
// 3초마다 반복되는 타이머를 설정합니다.
timer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in
// self가 nil이 아니면 강한 참조로 전환하여 사용합니다.
guard let self = self else { return }
// 광고 인덱스를 다음 인덱스로 업데이트합니다.
self.adIndex = self.correctedIndex(for: self.adIndex + 1)
}
}
// 자동 스크롤을 중지하는 함수입니다.
func stopAutoScroll() {
// 타이머를 무효화하고 nil로 설정합니다.
timer?.invalidate()
timer = nil
}
}
ADBannerView
import SwiftUI
// ADBannerView 구조체
// ADBannerView는 광고 배너를 무한 스크롤로 보여주는 뷰입니다.
struct ADBannerView: View {
// ADBannerViewModel 인스턴스를 상태 객체로 선언하여 뷰 모델을 관리합니다.
@StateObject private var viewModel = ADBannerViewModel()
var body: some View {
// InfinitePageView를 사용하여 광고 이미지를 무한 스크롤로 보여줍니다.
InfinitePageView(
// 현재 선택된 광고 인덱스를 바인딩합니다.
selection: $viewModel.adIndex,
// 이전 광고 인덱스를 계산하는 클로저입니다.
before: { viewModel.correctedIndex(for: $0 - 1) },
// 다음 광고 인덱스를 계산하는 클로저입니다.
after: { viewModel.correctedIndex(for: $0 + 1) },
// 주어진 인덱스에 해당하는 광고 이미지를 보여주는 뷰를 생성하는 클로저입니다.
view: { index in
ZStack {
withAnimation {
// 인덱스에 해당하는 광고 이미지를 표시합니다.
Image(viewModel.adImages[index])
.resizable()
}
// 광고 배너 항목 뷰를 추가하여 현재 페이지 인덱스와 총 페이지 수를 표시합니다.
ADBannerItemView(pageIndex: $viewModel.adIndex, pageTotal: .constant(viewModel.adImages.count))
}
}
)
// 광고 배너 뷰의 높이를 170으로 설정합니다.
.frame(height: 170)
// 뷰가 나타날 때 자동 스크롤을 시작합니다.
.onAppear {
viewModel.startAutoScroll()
}
// 뷰가 사라질 때 자동 스크롤을 중지합니다.
.onDisappear {
viewModel.stopAutoScroll()
}
}
}
ADBannerItemView
import SwiftUI
// ADBannerItemView는 현재 광고 배너의 페이지 인덱스와 총 페이지 수를 표시하는 뷰입니다.
struct ADBannerItemView: View {
// 현재 페이지 인덱스를 바인딩으로 받아옵니다.
@Binding var pageIndex: Int
// 총 페이지 수를 바인딩으로 받아옵니다.
@Binding var pageTotal: Int
var body: some View {
VStack {
HStack {
// 광고 배너(AD) 숫자 표시를 위한 캡슐 형태의 배경을 생성합니다.
Capsule()
.foregroundColor(Color(.secondarySystemBackground)) // 배경 색상 설정
.frame(width: 40, height: 15) // 캡슐의 크기 설정
.padding() // 캡슐 주위에 패딩을 추가
.overlay(
// 페이지 수 계산 및 텍스트로 표시
Text("\(pageIndex+1)/\(pageTotal)") // 현재 페이지 인덱스와 총 페이지 수를 표시
.foregroundColor(.primary) // 텍스트 색상 설정
.font(.system(size: 10)) // 텍스트 폰트 크기 설정
)
Spacer() // 왼쪽에 배치된 요소와 오른쪽에 배치될 요소 사이에 여백 추가
}
Spacer() // 상단에 배치된 요소와 하단에 배치될 요소 사이에 여백 추가
}
}
}
반응형
'iOS > SwiftUI' 카테고리의 다른 글
[ SwiftUI ] Custom Tab Bar 만들기 (0) | 2024.07.02 |
---|---|
[ SwiftUI ] SearchBar 만들기 (0) | 2024.06.22 |
[ SwiftUI ] SwiftUI #Previews (0) | 2024.04.09 |