본문 바로가기
iOS/구현

[SwiftUI] pre/next buttons가 있는 이미지 슬라이더 구현하기

by Callie_ 2024. 5. 16.

 

 

 

 

 

 

Topic:

 

- pre/next 버튼으로 이미지 슬라이더 작동

- 이미지 슬라이더

- tapGesture로 버튼 숨기기 및 드러내기

- 인덱스 넘버에 따라 첫번째와 마지막 인덱스 일 때 next 또는 pre 버튼 숨기기

 

 

접근:

 

- 이미지 슬라이드에 들어올 이미지 배열을 만들어서 tabView와 forEach를 활용해야겠다고 생각했다.

// 테스트용 이미지배열
    private var images: [String] = [
        "bottle_coffee",
        "new_menu",
        "main_event_bossam2",
        "new_menu"
    ]

 

테스트용으로 넣어둔 이미지로 확인할 예정이라, 이미지 배열은 UIKit과 똑같이 만들었다.

 

 

 

 

 

 

- tabView를 써서 슬라이드를 만들었는데, 이미지 개수에 따라 버튼을 나타내야한다거나, 이미지 개수를 나타내야한다거나 했기 때문에 인덱스를 일반 변수가 아닌 @State로 작성했다.

- 그리고 이미지 받아오는 순서 등을 확실히 하고 싶어서 tag로 인덱스를 받았다.

struct MainGuideView: View {
        
    // 테스트용 이미지배열
    private var images: [String] = [
        "bottle_coffee",
        "new_menu",
        "main_event_bossam2",
        "new_menu"
    ]
    
    // 이미지슬라이드 인덱스
    @State private var index: Int = 0
    
    var body: some View {
        
        ZStack {
            
            // MARK: - 이미지 (배너)
            
            VStack {
                TabView(selection: $index) {
                    ForEach(0..<images.count, id: \.self) { index in
                        
                        Image(images[index])
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(
                                width: UIScreen.main.bounds.width,
                                height: 500,
                                alignment: .center
                            )
                            .clipped()
                            .cornerRadius(16)
                            .tag(index)

                    }
                    
                }
                .tabViewStyle(
                    PageTabViewStyle(
                        indexDisplayMode: .never
                    )
                )
            } // Vstak
            

        
    } // * Body
} // * View

 

 

 

 

- 기획안 기준, 배너 이미지를 탭했을 때 좌우 버튼이 나와야 했기 때문에, 액션을 감지하는 변수를 또 @State으로 선언했다.

// 사용자 배너 탭액션 감지
    @State private var isTapped: Bool = false

 

// 배너 탭 시 좌우버튼 + 자세히보기 버튼 show
     .onTapGesture {
     isTapped.toggle()
     }

 

onTapGesture가 UIKit의 tapGesutre 받는 거랑 똑같아서, 거기에 사용자 액션 감지를 위한 변수를 토글 처리 해주었다.

 

 

 

 

 

- 인덱스를 활용해서 인덱스가 0일때와 마지막일 때, 각각 왼쪽 또는 오른쪽 버튼을 숨겨주어야 해서 그부분은 아래와 같이 처리했다.

- 그리고 pre/next 버튼을 탭하면 이미지가 인덱스에 맞게 바뀌어야해서 action은 그에 대한 코드.

 

  // MARK: - Buttons (pre/next)
            
            /// 배너 탭 하면 등장
            if isTapped{
                
                HStack {
                    
                    /// 인덱스 번호에 따라 좌우 버튼 숨김
                    /// pre button
                    if index > 0 {
                        Button(action: {
                            if index > 0 {
                                index -= 1
                            }
                        }) {
                            Image(.ic32ArrowBack2)
                                .frame(width: 32, height: 32)
                        }
                    }
                    
                    Spacer()
                    
                    /// next button
                    if index < images.count - 1 {
                        Button(action: {
                            if index < images.count - 1 {
                                index += 1
                            }
                        }) {
                            Image(.ic32ArrowBack2)
                                .rotationEffect(.degrees(-180))
                                .frame(width: 32, height: 32)
                        }
                    }
                    
                } // * HStack (pre/next btn)
                .padding(.horizontal, 26)

 

 

 

 

 

 

 

- 위 접근방식으로 작성한 전체 코드는 아래와 같다.

(본문엔 따로 코멘트를 작성하지 않은 코드도 포함되어 있음.)

struct MainCoffeGuideTableSwiftUIView: View {
        
    // 테스트용 이미지배열
    private var images: [String] = [
        "bottle_coffee",
        "new_menu",
        "main_event_bossam2",
        "new_menu"
    ]
    
    // 이미지슬라이드 인덱스
    @State private var index: Int = 0
    // 사용자 배너 탭액션 감지
    @State private var isTapped: Bool = false
    
    var body: some View {
        
        ZStack {
            
            // BavkView
            background
            
            // MARK: - 이미지 (배너)
            
            VStack {
                TabView(selection: $index) {
                    ForEach(0..<images.count, id: \.self) { index in
                        
                        Image(images[index])
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(
                                width: UIScreen.main.bounds.width,
                                height: 500,
                                alignment: .center
                            ) // -- 아마 수정 필요
                            .clipped()
                            .cornerRadius(16)
                            .tag(index)
                        
                        // 배너 탭 시 좌우버튼 + 자세히보기 버튼 show
                            .onTapGesture {
                                print("banner tapped!")
                                isTapped.toggle()
                            }
                        
                            .overlay (
                                
                                VStack {
                                    
                                    /// 배너 개수 표시
                                    Text("\(index + 1)/ \(images.count)")
                                        .font(Font.Pretendard(type: .Regular, size: 10))
                                        .foregroundStyle(.uiColorWhite)
                                        .frame(width: 30, height: 16, alignment: .center)
                                        .background(Capsule().fill(Color.uiColorBlack).opacity(0.5))
                                        .frame(
                                            maxWidth: .infinity,
                                            maxHeight: .infinity,
                                            alignment: .topTrailing
                                        )
                                        .padding(.top, 24)
                                    
                                    /// 자세히보기 버튼
                                    /// 배너 탭 하면 등장
                                    if isTapped {
                                        Button(action: {
                                            print("자세히보기 버튼")
                                            self.moveAction?(.timeSaleAction)
                                            
                                        }, label: {
                                            
                                            HStack {
                                                
                                                Text("자세히 보기")
                                                    .font(Font.Pretendard(type: .SemiBold, size: 12))
                                                    .foregroundStyle(.uiColorBlack)
                                                
                                                Image(.ic32ArrowBlack)
                                                    .frame(width: 4, height: 8)
                                            }
                                            .padding(.leading, 12)
                                            .padding(.trailing, 16)
                                            .padding(.vertical, 8)
                                            
                                        }) // * Button
                                        .background(.uiColorWhite)
                                        .cornerRadius(10, corners: .allCorners)
                                        .frame(width: 97, height: 32)
                                        .frame(
                                            maxWidth: .infinity,
                                            maxHeight: .infinity,
                                            alignment: .bottomTrailing
                                        )
                                        .padding(.bottom, 40)
                                    }
                                    
                                } // * Vstack (indexNm, 자세히보기)
                                    .padding(.trailing, 25)
                                
                            )
                    }
                    
                }
                .tabViewStyle(
                    PageTabViewStyle(
                        indexDisplayMode: .never
                    )
                )
            } // Vstak
            
            // MARK: - Buttons (pre/next)
            
            /// 배너 탭 하면 등장
            if isTapped{
                
                HStack {
                    
                    /// 인덱스 번호에 따라 좌우 버튼 숨김
                    /// pre button
                    if index > 0 {
                        Button(action: {
                            if index > 0 {
                                index -= 1
                            }
                        }) {
                            Image(.ic32ArrowBack2)
                                .frame(width: 32, height: 32)
                        }
                    }
                    
                    Spacer()
                    
                    /// next button
                    if index < images.count - 1 {
                        Button(action: {
                            if index < images.count - 1 {
                                index += 1
                            }
                        }) {
                            Image(.ic32ArrowBack2)
                                .rotationEffect(.degrees(-180))
                                .frame(width: 32, height: 32)
                        }
                    }
                    
                } // * HStack (pre/next btn)
                .padding(.horizontal, 26)
            }
            
            
        }
        .frame(
            width: UIScreen.main.bounds.width,
            height: 516,
            alignment: .center
        )
        
    } // * Body
} // * View

 

그런데 이렇게 보니까 가독성이 너무 안 좋아서...

 

후에 네트워크 통신하기 위해 코드를 일부분 수정하면서 구조체를 더 쪼개는 작업을 해서 메인으로 코드를 결합하는 구조체를 아래와 같이 수정했다.

import SwiftUI
import Combine

struct MainCoffeeGuideTableSwiftUIView: View {
        
    // 백그라운드 색상
    let background = Color(UIColor(hexCode: "eeeeee"))
        
    // 배너리스트 배열
    var bannerList: [BannerList] = []
    // 이미지슬라이드 인덱스
    @State private var index: Int = 0
    // 사용자 배너 탭액션 감지
    @State private var isTapped: Bool = false
    
    var body: some View {
        
        ZStack {
            
            // BackView
            background
            
            // MARK: - 이미지 슬라이더 (배너)
            
            MainCoffeeGuideImageSlider(
                bannerImages: getImage(),
                title: getTitle(),
                bannerList: bannerList
            )
            
            // MARK: - Buttons (pre/next)
            
            /// 배너 탭 하면 등장
            if isTapped{
                
                MainCoffeeGuideBannerButtons(
                    index: $index,
                    total: getImage().count
                )
                
            }
            
            
        }
        .frame(
            maxWidth: UIScreen.main.bounds.width,
            minHeight: 516,
            maxHeight: 601,
            alignment: .center
        )
        
    } // * Body
    

} // * View


extension MainCoffeeGuideTableSwiftUIView {
    
    /// 이미지배열
    func getImage() -> [String] {
        return bannerList.map { $0.bannerImg ?? "" }
    }
    
    /// 텍스트배열
    func getTitle() -> [String] {
        return bannerList.map { $0.title ?? "" }
    }
    
    /// 링크배열
    func getLink() -> [String] {
        return bannerList.map { $0.linkUrlL ?? "" }
    }
}

 

 

 

 

 

 

 

 

 

 

Referances

 

https://developer.apple.com/documentation/swiftui/fitting-images-into-available-space

 

Fitting images into available space | Apple Developer Documentation

Adjust the size and shape of images in your app’s user interface by applying view modifiers.

developer.apple.com