import SwiftUI
struct Post: Identifiable {
var id = UUID().uuidString
var postImage: String
}
import SwiftUI
struct SnapCarousel<Content: View, T: Identifiable>: View {
var content: (T) -> Content
var list: [T]
// properties
var spacing: CGFloat
var trailingSpace: CGFloat
@Binding var index: Int
init(spacing: CGFloat = 15, trailingSpace: CGFloat = 100, index: Binding<Int>, items: [T], @ViewBuilder content: @escaping (T) -> Content) {
self.list = items
self.spacing = spacing
self.trailingSpace = trailingSpace
self._index = index
self.content = content
}
@GestureState var offset: CGFloat = 0
@State var currentIndex: Int = 0
var body: some View {
GeometryReader { proxy in
let width = proxy.size.width - (trailingSpace - spacing)
let adjustMentWidth = (trailingSpace / 2) - spacing
HStack(spacing: spacing) {
ForEach(list) { item in
content(item)
.frame(width: proxy.size.width - trailingSpace)
}
}
.padding(.horizontal, spacing)
.offset(x: (CGFloat(currentIndex) * -width) + (currentIndex != 0 ? adjustMentWidth : 0) + offset)
.gesture(
DragGesture()
.updating($offset, body: { value, out, _ in
out = value.translation.width
})
.onEnded({ value in
let offsetX = value.translation.width
let progress = -offsetX / width
let roundIndex = progress.rounded()
// currentIndex += Int(roundIndex)
currentIndex = max(min(currentIndex + Int(roundIndex), list.count - 1), 0)
currentIndex = index
})
.onChanged({ value in
let offsetX = value.translation.width
let progress = -offsetX / width
let roundIndex = progress.rounded()
index = max(min(currentIndex + Int(roundIndex), list.count - 1), 0)
})
)
}
.animation(.easeInOut, value: offset == 0)
}
}
-
SnapCarousel
是一个通用结构体,它接收两个类型参数:Content
:表示视图的内容,必须符合View
协议。T
:表示数据模型,并且必须遵循Identifiable
协议,这样SwiftUI 能够识别和管理数据集中的每个元素。-
content
: 一个闭包,用于生成视图内容。 -
list
: 一个数组,包含要在轮播中显示的元素。 -
使用
DragGesture
来捕捉用户的拖动。 -
当手势更新时,通过
updating
方法来设置偏移量。 -
在手势结束时计算出当前应该显示的索引,并更新
currentIndex
和绑定的index
。 -
在手势变化时,也会更新绑定的
index
。
-
import SwiftUI
struct Banner: View {
@State var currentIndex: Int = 0
@State var posts: [Post] = []
@State var currentTab = "Slide Show"
@Namespace var animation
var body: some View {
VStack(spacing: 15) {
VStack(alignment: .leading, spacing: 12) {
Button {
}label: {
Label {
Text("Back")
.fontWeight(.semibold)
} icon: {
Image(systemName: "chevron.left")
.font(.title2.bold())
}
.foregroundColor(.primary)
}
Text("My Wishes")
.font(.title)
.fontWeight(.black)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
// segment Control
HStack(spacing: 0) {
TabButton(title: "Slide Show", animation: animation, currentTab: $currentTab)
TabButton(title: "List", animation: animation, currentTab: $currentTab)
}
.background(Color.black.opacity(0.04), in: RoundedRectangle(cornerRadius: 10))
.padding(.horizontal)
// Snap Carousel
SnapCarousel(index: $currentIndex, items: posts) { post in
GeometryReader { proxy in
let size = proxy.size
Image(post.postImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: size.width)
.cornerRadius(12)
}
}
.padding(.vertical, 40)
// Indicator
HStack(spacing: 10) {
ForEach(posts.indices, id: \.self) { index in
Circle()
.fill(Color.black.opacity(currentIndex == index ? 1 : 0.1))
.frame(width: 10, height: 10)
.scaleEffect(currentIndex == index ? 1.4 : 1)
.animation(.spring(), value: currentIndex == index)
}
}
.padding(.bottom, 40)
}
.frame(maxHeight: .infinity, alignment: .top)
.onAppear {
for index in 1...4 {
posts.append(Post(postImage: "banner\(index)"))
}
}
}
}
struct Banner_Previews: PreviewProvider {
static var previews: some View {
Banner()
}
}
// TabButton
struct TabButton: View {
var title: String
var animation: Namespace.ID
@Binding var currentTab: String
var body: some View {
Button {
withAnimation(.spring()) {
currentTab = title
}
}label: {
Text(title)
.fontWeight(.bold)
.foregroundColor(currentTab == title ? .white : .black)
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.background(
ZStack {
if currentTab == title {
RoundedRectangle(cornerRadius: 10)
.fill(.black)
.matchedGeometryEffect(id: "TAB", in: animation)
}
}
)
}
}
}
标签:index,轮播,spacing,width,swiftui,currentIndex,var,currentTab
From: https://blog.csdn.net/qq_63007445/article/details/143402941