使用分栏,当点击左边大分类时,右边的项目将自动定位跳转,当上下拉动右边项目时,左侧分类将自动定位。这种常见的分类联动下面将使用SwiftUI实现。
1、使用ScrollViewReader实现定位:
@Namespace var topID
@Namespace var bottomID
var body: some View {
ScrollViewReader { proxy in
ScrollView {
Button("跳转到底部") {
withAnimation {
proxy.scrollTo(bottomID)//调用scrollTo跳转到ID:bottomID位置
}
}
.id(topID) //当前顶部id
VStack(spacing: 0) {
ForEach(0..<100) { i in
color(fraction: Double(i) / 100)
.frame(height: 32)
}
}
Button("回到顶部") {
withAnimation {
proxy.scrollTo(topID)//跳转到顶部id位置
}
}
.id(bottomID)//当前底部id
}
}
}
func color(fraction: Double) -> Color {
Color(red: fraction, green: 1 - fraction, blue: 0.5)
}
2、利用GeometryReader判断当前位置
我们需要使用:
.coordinateSpace(name: "scroll")
为视图的坐标空间指定一个名称,以便其他代码可以操作与命名空间相关的点和大小等维度。然后通过:
GeometryProxy.frame(in: .named("scroll")).origin.y
获取当前视图离 "scroll" 的坐标y值,以判断是否到达分类切换的位置。关于GeometryReader可以参考:http://www.55mx.com/ios/117.html
3、完整代码
struct FoodView: View {
@State var actionID = 1 //当前默认ID
@State var IDCandChange = true
var body: some View {
ScrollViewReader { scrollProxy in
HStack{
List{
catScrollTo("推荐", scrollID: 1, scrollProxy: scrollProxy)
catScrollTo("猪肉", scrollID: 2, scrollProxy: scrollProxy)
catScrollTo("牛肉", scrollID: 3, scrollProxy: scrollProxy)
catScrollTo("鸡肉", scrollID: 4, scrollProxy: scrollProxy)
catScrollTo("羊肉", scrollID: 5, scrollProxy: scrollProxy)
catScrollTo("蛋类", scrollID: 6, scrollProxy: scrollProxy)
catScrollTo("其它肉类", scrollID: 7, scrollProxy: scrollProxy)
catScrollTo("蔬菜", scrollID: 8, scrollProxy: scrollProxy)
catScrollTo("鱼类", scrollID: 9, scrollProxy: scrollProxy)
}
.listStyle(PlainListStyle())
.frame(width: 120)
Divider()
VStack{
GeometryReader{ geometry in
ScrollView(.vertical,showsIndicators: false) {
VStack{
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "推荐", id: 1)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "猪肉", id: 2)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "牛肉", id: 3)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "鸡肉", id: 4)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "羊肉", id: 5)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "蛋类", id: 6)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "其它肉类", id: 7)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "蔬菜", id: 8)
SubCatView(actionID: $actionID, IDCandChange: $IDCandChange, geometry: geometry, name: "鱼类", id: 9)
}
.padding(.bottom,geometry.size.height * 0.8)//MARK: 处理动画“回弹”问题,后面可能不需要这个
}.coordinateSpace(name: "scroll")
}
}
.padding(.trailing)
}
}
}
func catScrollTo(_ name:String, scrollID:Int, scrollProxy: ScrollViewProxy) -> some View{
Button(name) {
withAnimation(.easeInOut){
IDCandChange = false //不要滚动改状态
scrollProxy.scrollTo(scrollID, anchor:.top)
actionID = scrollID
//延时执行
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
IDCandChange = true //滚动可改状态
}
}
}.h5.background(actionID == scrollID ? .gray : .clear)
}
struct SubCatView:View{
@Binding var actionID:Int
@Binding var IDCandChange:Bool
let geometry:GeometryProxy
var name:String
let id:Int //当前锚点ID
var gridWidth: CGFloat{ geometry.size.width / 3.33333 }
var body: some View{
VStack{
ZStack{
Divider().frame(width:gridWidth * 2)
Text(name).h3.padding(12).background(.white)
}.opacity(0.75).padding(.horizontal,22).padding(.vertical,8)
LazyVGrid(columns:[GridItem(.fixed(gridWidth)), GridItem(.fixed(gridWidth)), GridItem(.fixed(gridWidth))], spacing: 10) {
ForEach(0..<8,id: .self){ i in
Text(name + "(i)")
}
}
}
.id(id) //锚点
.background(GeometryReader { geo in
Color.clear.onChange(of: geo.frame(in: .named("scroll")).origin.y){ i in
if IDCandChange && (i < 0 && i > -150) && actionID != id{
withAnimation{
actionID = id
}
print("修改了ID:",actionID,i)
}
}
})
}
}
}
很简单粗糙的UI,主要是为了实现功能。有不明白的小伙伴,欢迎留言评论!
在http://www.55mx.com/ios/170.html这个文章中我们介绍了可以让TabView垂直滚动,所以我们实例化这个代码。
import SwiftUI
//分类数据结构
struct Categories{
var categories = [Categorie]()
//单个分类
struct Categorie:Identifiable{
var id:Int
var name:String
var image:String?
fileprivate init(_ name:String,image:String?,id:Int){
self.id = id
self.name = name
self.image = image
}
}
init(){}
private var uniqueCateId = 0//ID初始化
mutating func addCate(_ name:String , image:String?){
uniqueCateId += 1//顺序定义id
categories.append(Categorie(name, image: image, id: uniqueCateId))
}
}
class CategoriesViewModel:ObservableObject{
@Published private(set) var cate:Categories
init(){
cate = Categories()
cate.addCate("推荐", image: nil)
cate.addCate("猪肉", image: "cate0")
cate.addCate("牛肉", image: "cate0")
cate.addCate("鸡肉", image: "cate0")
cate.addCate("羊肉", image: "cate0")
cate.addCate("蛋类", image: "cate0")
cate.addCate("其它肉类", image: "cate0")
cate.addCate("蔬菜", image: "cate0")
cate.addCate("鱼类", image: "cate0")
}
}
struct FoodView: View {
typealias Categorie = Categories.Categorie
@StateObject var catData = CategoriesViewModel()
@State private var selector = 1
var body: some View{
HStack(spacing:0){
ScrollView(.vertical,showsIndicators: false){
VStack(spacing:0){
ForEach(catData.cate.categories){ categorie in
catScrollTo(categorie:categorie)
}
}
}.frame(width: 110)
Divider()
GeometryReader { proxy in
let gridWidth = proxy.size.width / 3.33333
TabView(selection: $selector) {
ForEach(catData.cate.categories){ categorie in
SubCatView(categorie:categorie,gridWidth:gridWidth).tag(categorie.id)
}
.rotationEffect(.degrees(-90)) // -90度旋转内容
.frame(width: proxy.size.width,height: proxy.size.height)
}
.frame(
width: proxy.size.height, // 高与宽 调换
height: proxy.size.width //宽与高 调换
)
.rotationEffect(.degrees(90), anchor: .topLeading) // 旋转整个TabView
.offset(x: proxy.size.width) // 偏移回到屏幕边界
.tabViewStyle(
PageTabViewStyle(indexDisplayMode: .never)//不显示切换项
)
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
}
}
}
func catScrollTo(categorie:Categorie) -> some View{
VStack(spacing:0){
Button(action: {
withAnimation{
selector = categorie.id
}
}, label: {
Text(categorie.name)
.modifier(CatScrollStyle(isIDEqual: selector == categorie.id))
})
Divider()
}
}
//分类样式
private struct CatScrollStyle: ViewModifier{
let isIDEqual:Bool
func body(content: Content) -> some View {
ZStack{
if isIDEqual{
Color.gray.opacity(0.1)
.overlay(Color.red.opacity(0.8).frame(width:3), alignment: .leading)
}
content
.foregroundColor(isIDEqual ? .red : .black.opacity(0.8))
.padding(18)
}
}
}
//右侧分类视图
private func SubCatView(categorie:Categorie,gridWidth:CGFloat) -> some View{
VStack{
if catData.cate.categories.first?.id != categorie.id ,let name = categorie.name{
ZStack{
Divider().opacity(0.75).frame(width:gridWidth * 1.5)
Text(name).h6.padding(12).background(.white)
}
}
LazyVGrid(columns:[GridItem(.fixed(gridWidth)), GridItem(.fixed(gridWidth)), GridItem(.fixed(gridWidth))], spacing: 10) {
ForEach(0..<12,id: .self){ i in
category(categorie.name + "(i)", image: "cate0", gridWidth: gridWidth).padding(.bottom,30)
}
}
.padding(.top,15)
Spacer()
}
}
@ViewBuilder
private func category(_ name: String , image:String?,gridWidth:CGFloat) -> some View{
if let image = image {
VStack(spacing:15){
Image(uiImage: UIImage(named: image, in: Bundle(path: "RecommendCategoryIcon"), with: nil)!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
Text(name).clear
}
}else{
Text(name).h6
.padding(.vertical,5)
.frame(width:gridWidth)
.overlay(Capsule(style: .continuous).stroke(.gray.opacity(0.3),lineWidth: 1))
}
}
}
上下拖动或者点击左侧分类都可以实现视图切换。
将图片放到资源文件夹里就可以显示出来了 。
这是我目前正在使用,且比较完美的一步方式,篇幅有限,请移步:http://www.55mx.com/ios/178.html
除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/169
《【SwiftUI实战】使用ScrollViewReader制作一个可以跳转与联动的分类视图》的网友评论(0)