75142913在线留言
【SwiftUI实战】使用ScrollViewReader制作一个可以跳转与联动的分类视图_IOS开发_网络人

【SwiftUI实战】使用ScrollViewReader制作一个可以跳转与联动的分类视图

Kwok 发表于:2021-10-20 12:20:22 点击:58 评论: 0

使用分栏,当点击左边大分类时,右边的项目将自动定位跳转,当上下拉动右边项目时,左侧分类将自动定位。这种常见的分类联动下面将使用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、完整代码

SwiftUI实战使用ScrollViewReader制作一个可以跳转与联动的分类视图

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,主要是为了实现功能。有不明白的小伙伴,欢迎留言评论!

二、使用垂直滚动的TabView视图

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))
        }
    }
}

上下拖动或者点击左侧分类都可以实现视图切换。

SwiftUI实战使用ScrollViewReader制作一个可以跳转与联动的分类视图

将图片放到资源文件夹里就可以显示出来了 。

第三种:利用LazyVStack的无限onAppear与onDisappear特性

这是我目前正在使用,且比较完美的一步方式,篇幅有限,请移步:http://www.55mx.com/ios/178.html

 

除非注明,网络人的文章均为原创,转载请以链接形式标明本文地址:https://www.55mx.com/post/169
标签:联动跳转SwiftUITabViewKwok最后编辑于:2021-11-19 11:19:43
0
感谢打赏!

《【SwiftUI实战】使用ScrollViewReader制作一个可以跳转与联动的分类视图》的网友评论(0)

本站推荐阅读

热门点击文章