最近忙得前言都不想喵两句了...希望此demo对你有帮助~
效果图:
核心实现原理:
AppBar(index: $index, offset: $offset)
控制按钮逻辑;GeometryReader
包裹你按钮对应的所有视图,将灵活的首选大小返回到其父布局,offset
控制偏移量。
实现代码如下:
import SwiftUI
struct ContentView: View {
var body: some View {
Home()
}
}
struct Home: View {
@State var index = 0
@State var offset: CGFloat = 0
var width = UIScreen.main.bounds.width
@State var offsetMoved: CGFloat = 0
var body: some View {
VStack(spacing: 0) {
AppBar(index: $index, offset: $offset)
GeometryReader{g in
HStack(spacing: 0) {
Scape()
.frame(width: g.frame(in : .global).width)
Code()
.frame(width: g.frame(in : .global).width)
Cat()
.frame(width: g.frame(in : .global).width)
}
.offset(x: self.offset + self.offsetMoved)
.highPriorityGesture(DragGesture()
.onEnded({ value in
if value.translation.width > 150 { // minimum drag...
print("right")
self.changeView(left: false)
} else if value.translation.width < -150 {
print("left")
self.changeView(left: true)
}
self.offsetMoved = 0 // 重置微调
})
.onChanged({ value in
self.offsetMoved = value.translation.width // 微调
}))
}
}
.animation(.default)
.edgesIgnoringSafeArea(.all)
}
func changeView(left: Bool) {
// tab
if left {
if self.index != 2 {
self.index += 1
}
} else {
if self.index != 0 {
self.index -= 1
}
}
// view
if self.index == 0 {
self.offset = 0
}
else if self.index == 1 {
self.offset = -self.width
}
else {
self.offset = -self.width*2
}
// change the width based on the size of the tabs...
}
}
struct AppBar: View {
@Binding var index: Int
@Binding var offset: CGFloat
let imageWH: CGFloat = 25
var width = UIScreen.main.bounds.width
var body: some View {
VStack(alignment: .leading, content: {
Text("Segment Tab")
.font(.title)
.foregroundColor(.white)
.padding(.leading)
.padding(.bottom)
HStack {
Button(action: {
self.index = 0
self.offset = 0
}) {
VStack(spacing: 8){
HStack(spacing: 12) {
Image(systemName: "leaf")
.resizable()
.foregroundColor(self.index == 0 ? .white : Color.white.opacity(0.7))
.frame(width: imageWH, height: imageWH)
Text("Scape")
.foregroundColor(self.index == 0 ? .white : Color.white.opacity(0.7))
}
Capsule()
.foregroundColor(self.index == 0 ? Color.white : Color.clear)
.frame(width: 32, height: 4)
Spacer().frame(maxWidth: .infinity, maxHeight: 0)
}
}
Button(action: {
self.index = 1
self.offset = -self.width
}) {
VStack(spacing: 8){
HStack(spacing: 12) {
Image(systemName: "laptopcomputer")
.resizable()
.foregroundColor(self.index == 1 ? .white : Color.white.opacity(0.7))
.frame(width: imageWH+8, height: imageWH)
Text("Code")
.foregroundColor(self.index == 1 ? .white : Color.white.opacity(0.7))
}
Capsule()
.foregroundColor(self.index == 1 ? Color.white : Color.clear)
.frame(width: 32, height: 4)
Spacer().frame(maxWidth: .infinity, maxHeight: 0)
}
}
Button(action: {
self.index = 2
self.offset = -self.width*2
}) {
VStack(spacing: 8){
HStack(spacing: 12) {
Image("Cat_Footprint")
// .resizable()
.foregroundColor(self.index == 2 ? Color.white : Color.white.opacity(0.7))
.frame(width: imageWH, height: imageWH)
Text("Cat")
.foregroundColor(self.index == 2 ? .white : Color.white.opacity(0.7))
}
Capsule()
.foregroundColor(self.index == 2 ? Color.white : Color.clear)
.frame(width: 32, height: 4)
Spacer().frame(maxWidth: .infinity, maxHeight: 0)
}
}
}
})
.padding(.top, (UIApplication.shared.windows.first?.safeAreaInsets.top)! + 15)
.padding(.horizontal)
.padding(.bottom, 10)
.background(Color(.systemBlue))
}
}
struct Cat: View {
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
ForEach(1...8, id: \.self) {i in
HStack(spacing: 0) {
Spacer().frame(width: 16)
Image("cat_\(i)")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 200)
.cornerRadius(15)
.padding(.top)
.padding(.horizontal)
Spacer().frame(width: 16)
}
}
}
}.padding(.bottom, 18)
}
}
struct Code: View {
var body: some View {
GeometryReader{_ in
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
ForEach(1...4, id: \.self) {i in
HStack(spacing: 0) {
Spacer().frame(width: 16)
Image("code_\(i)")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 200)
.cornerRadius(15)
.padding(.top)
.padding(.horizontal)
Spacer().frame(width: 16)
}
}
}
}.padding(.bottom, 18)
}
.background(Color.white)
}
}
struct Scape: View {
var body: some View {
GeometryReader{_ in
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 0) {
ForEach(1...5, id: \.self) {i in
HStack(spacing: 0) {
Spacer().frame(width: 16)
Image("scape_\(i)")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 200)
.cornerRadius(15)
.padding(.top)
.padding(.horizontal)
Spacer().frame(width: 16)
}
}
}
}.padding(.bottom, 18)
}
.background(Color.white)
}
}