75142913在线留言
SwiftUI实现轮播图效果(旋转木马)_IOS开发_网络人

SwiftUI实现轮播图效果(旋转木马)

Kwok 发表于:2021-10-12 16:12:58 点击:65 评论: 0

轮播图片是APP中最常见的一种视图展现方式,我们可以通过内置的TabView轻松实现:

struct ContentView: View {
    let width = UIScreen.main.bounds.width
    let colors: [Color] = [.red, .blue, .green, .pink, .purple]
    @State private var selection: Color = .red
    var body: some View {
        TabView(selection: $selection) {
            ForEach(colors, id: .self) {
                $0.tag($0)
                    .frame(width: width - 20, height: ($0 == selection) ? 200 : 150)
            }
        }
        .frame(height: 200)
        .tabViewStyle(PageTabViewStyle())
        .animation(.spring(),value: selection)
    }
}

虽然可以实现基本的效果,但想控制更多却不容易,所以我自己写了一下可以对其参数控制的轮播:

一、Model与ViewModel

import SwiftUI
    //轮播图Model
struct CarouselCard: Identifiable,Codable{
    var id:     Int
    var name:   String = ""
        //安全初始化
    fileprivate init(name: String, id: Int) {
        self.name = name
        self.id = id
    }
}

    //轮播图ViewModel
class CarouselViewModel: ObservableObject{
        //轮播内容
    var items = [CarouselCard]()
    
    private func addItems(named name: String){
        let item = CarouselCard(name: name, id: items.count)
        items.insert(item, at: item.id)
    }
    init() {
        if items.isEmpty {
            for item in (0...5) {
                addItems(named: "第(item)个")
            }           
        }
    }
}

二、View

struct Carousel: View {
    @StateObject var carousels = CarouselViewModel()
    @State var screenDrag:CGFloat = 1 //拖放时偏移
    @State var activeCard = 0 //当前展示项
    var numberOfItems:CGFloat{ CGFloat(carousels.items.count) } //轮播项总数
    var cardWidth:CGFloat{ UIScreen.main.bounds.width - (CarouselConstants.widthOfHiddenCards * 2) - (CarouselConstants.spacing * 2 ) }
    
    var body: some View {
        let totalCanvasWidth: CGFloat = (cardWidth * numberOfItems) + (numberOfItems - 1) * CarouselConstants.spacing //容器总宽度=卡片*宽度 + 总间距
        let xOffsetToShift = (totalCanvasWidth - UIScreen.main.bounds.width) / 2
        let leftPadding = CarouselConstants.widthOfHiddenCards + CarouselConstants.spacing
        let totalMovement = cardWidth + CarouselConstants.spacing
            //当前正确位置
        let activeOffset = xOffsetToShift + (leftPadding) - (totalMovement * CGFloat(activeCard))
            //下一个位置
        let nextOffset = xOffsetToShift + (leftPadding) - (totalMovement * CGFloat(activeCard) + 1)
            //最终确定偏移位置
        let calcOffset = (activeOffset != nextOffset) ? activeOffset + screenDrag : activeOffset
        
        return HStack(spacing: CarouselConstants.spacing) {
            ForEach(carousels.items) { item in
                Text(item.name)
                    .frame(width: UIScreen.main.bounds.width - (CarouselConstants.widthOfHiddenCards * 2) - (CarouselConstants.spacing * 2),
                           height: (item.id == activeCard) ? CarouselConstants.cardHeight : CarouselConstants.cardHeight - 30)
                    .foregroundColor(.white)
                    .background(.black)
                    .cornerRadius(8)
                    .shadow(color: Color.gray, radius:4, x:0, y:4)
                    .onAppear{
                        activeCard = Int(numberOfItems / 2)//跳转到中间位置
                    }
            }
        }
        .background(Color.white.edgesIgnoringSafeArea(.all))
        .offset(x:calcOffset)
        .gesture(panGesture())//拖动手势
        .frame(maxWidth: .infinity)
        .animation(.spring(),value:activeCard)//监听动画iOS 15
    }
    
    @GestureState private var gesturePanOffset:CGFloat = .zero //手势结束会恢复初值
    //手势定义
    private func panGesture() -> some Gesture{
        DragGesture()
            .updating($gesturePanOffset){ lastestGestureValue, _, _ in
                screenDrag =  lastestGestureValue.translation.width
            }
            .onEnded{ value in
                screenDrag = 0
                let moveOffset = UIScreen.main.bounds.width / 4 //超过屏幕宽度(计算后的值)才发生偏移
                let lastIndex = carousels.items.endIndex - 1//数组最后一个索引值
                    //向右拖动
                if value.translation.width > moveOffset{
                    activeCard = (activeCard <= 0) ? lastIndex : activeCard - 1
                }
                    //向左拖动
                if -value.translation.width > moveOffset{
                    activeCard = activeCard >= lastIndex ? 0 : activeCard + 1
                }
                let impactMed = UIImpactFeedbackGenerator(style: .medium)
                impactMed.impactOccurred()
            }
    }
        //参数控制器
    private struct CarouselConstants{
        static let spacing:            CGFloat = 12 //与隐藏卡片的左右间距
        static let widthOfHiddenCards: CGFloat = 20 //被隐藏卡片的宽度(左、右)
        static let cardHeight:         CGFloat = 160 //卡片高度
    }
}

SwiftUI实现轮播图效果旋转木马

除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/166
标签:轮播SwiftUITabViewKwok最后编辑于:2021-10-12 16:12:23
2
感谢打赏!

《SwiftUI实现轮播图效果(旋转木马)》的网友评论(0)

本站推荐阅读

热门点击文章