【SwiftUI模块】0031、SwiftUI搭建Safari浏览器底部标签栏

SwiftUI模块系列 - 已更新31篇
SwiftUI项目 - 已更新3个项目
往期Demo源码下载

技术:SwiftUI、SwiftUI4.0、Safari、浏览器、底部标签栏
运行环境:
SwiftUI4.0 + Xcode14 + MacOS12.6 + iPhone Simulator iPhone 14 Pro Max

概述

使用SwiftUI搭建Safari浏览器底部标签栏

详细

一、运行效果

请添加图片描述

二、项目结构图

在这里插入图片描述

三、程序实现 - 过程

思路:
1.创建列表模块 展示- 多张图片 - 使用ScrollView搭建
2.创建底部的按钮模块 - BottomBar
3.使用ViewModifier-视图修改器 处理 监听滚动偏移量的值发生改变的处理 比如当值达到某一个区间。就置底部。当向上滚动一段区间就恢复到悬浮区域
4.处理搜索 - 添加搜索模块页面
5.搭建搜索模块页面 以及额外自定义填充部分

1.创建一个项目命名为 SafariTabBar

在这里插入图片描述
在这里插入图片描述

1.1.引入资源文件和颜色

随机图片6张

在这里插入图片描述

扫描二维码关注公众号,回复: 14523790 查看本文章

2. 创建一个虚拟文件New Group 命名为 View

在这里插入图片描述
在这里插入图片描述

3. 创建一个虚拟文件New Group 命名为 ViewModel

在这里插入图片描述
在这里插入图片描述

4. 创建一个文件New File 选择SwiftUI View类型 命名为Home

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

5. 创建一个文件New File 选择SwiftUI View类型 命名为BottomBarViewModel 删除预览视图、并且设置类型是class 继承ObservableObject

主要是:作为视图模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Code

ContentView - 主窗口

主要是展示主窗口Home

//
//  ContentView.swift
//  SafariTabBar
//
//  Created by 李宇鸿 on 2022/9/24.
//

import SwiftUI

struct ContentView: View {
    
    
    var body: some View {
    
    

        //因为我们需要底部边缘…
        //正在使用几何读取器…
        GeometryReader{
    
     proxy in
            Home(proxy:proxy)
                
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        ContentView()
    }
}

Home - 主页

思路:
1.创建列表模块 展示- 多张图片 - 使用ScrollView搭建
2.创建底部的按钮模块 - BottomBar
3.使用ViewModifier-视图修改器 处理 监听滚动偏移量的值发生改变的处理 比如当值达到某一个区间。就置底部。当向上滚动一段区间就恢复到悬浮区域
4.处理搜索 - 添加搜索模块页面
5.搭建搜索模块页面 以及额外自定义填充部分

//
//  Home.swift
//  SafariTabBar
//
//  Created by 李宇鸿 on 2022/9/24.
//

import SwiftUI

struct Home: View {
    
    
    var proxy : GeometryProxy
    
    @StateObject var bottomViewModel = BottomBarViewModel()
    @Environment(\.colorScheme) var colorScheme
    
    //  键盘焦点状态. .
    @FocusState var showKeyboard : Bool
    
    var body: some View {
    
    
        ZStack{
    
    
            
            let bottomEdge = proxy.safeAreaInsets.bottom
            
            //   样本ScrollView……
            ScrollView(.vertical,showsIndicators: false){
    
    
                VStack(spacing:15){
    
    
                    ForEach(1...6,id:\.self){
    
     index in
                        Image("post\(index)")
                            .resizable()
                            .aspectRatio(    contentMode:.fill)
                            .frame(width: proxy.size.width - 30,height: 250)
                            .cornerRadius(8)
                        
                    }
                }
                .padding()
                .padding(.bottom,70)
                // 创建抵消修饰符……
                .modifier(OffsetModifier())
                .environmentObject(bottomViewModel)
                
            }
            //从0开始…
			//为scrollView设置坐标空间…
            .coordinateSpace(name: "TabScroll")
            
            
            // SearchView...
            // 搜索视图
            VStack{
    
    
                
                HStack{
    
    
                    Button  {
    
    
                        
                    } label: {
    
    
                        Image(systemName: "book")
                            .font(.title)
                            .foregroundColor(.primary)
                    }
                    
                    Spacer()

                    Button("Cancel"){
    
    
                        // 关闭键盘…
                        showKeyboard.toggle()
                    }
                    .foregroundColor(.primary)

                }
                //底部栏调整的最大高度…
                .frame(height: 40)
                // padding bottom bottom bar size...
                // 60 + extra 10 = 70
                .padding(.bottom,70)
                
                // Now Your Extra Conent...
                // 现在你的额外内容…
                
                if showKeyboard {
    
    
                    Text("Favourite's")
                        .font(.title.bold())
                        .frame(maxWidth:.infinity,alignment: .leading)
                        .padding(.top)
                }
                
            }
            .padding(.horizontal)
            .frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .top)
            .background(
                colorScheme == .dark ? Color.black : Color.white
            )
            
            .opacity(showKeyboard ? 1 : 0)
            
            //你也可以使用
            // SwiftUI3.0
            // SafeAreaView……
            //但是当搜索字段被点击时,一个新的页面将会可见…
            //所以它不会是完美的…
            
            // BottomBar...
            // 底栏
            BottomBar(showKeyboard: _showKeyboard,bottomEdge:bottomEdge)
            // 设置对象……
                .environmentObject(bottomViewModel)
                .padding(.top,50)
            //向下移动。
                .offset(y: bottomViewModel.tabState == .floating ? 0 : (bottomEdge == 0 ? 15 : bottomEdge))
                .padding(.bottom,bottomEdge == 0 ? 15 : 0)
        }
    }
}

struct Home_Previews: PreviewProvider {
    
    
    static var previews: some View {
    
    
        ContentView()
    }
}

struct BottomBar: View {
    
    
    @EnvironmentObject var bottomViewModel : BottomBarViewModel
    // NameSpace for Animation...
    // 动画
    @Namespace var animation
    
    @FocusState var showKeyboard : Bool
    
    var bottomEdge : CGFloat

    var body: some View{
    
    
        ZStack{
    
    
            RoundedRectangle(cornerRadius: bottomViewModel.tabState == .floating ? 12 : 0)
                .fill(.regularMaterial)
            // 浮动显示暗黑色 。 否则显示亮色
                .colorScheme(bottomViewModel.tabState == .floating  ? .dark : .light)
            
            HStack(spacing:15){
    
    
                
                // 隐藏在扩展……
                if bottomViewModel.tabState == .floating {
    
    
                    Button  {
    
    
                        
                    } label: {
    
    
                        Image(systemName: "chevron.left")
                            .font(.title2)
                            .foregroundColor(.primary)
                    }
                    
                    Button  {
    
    
                        
                    } label: {
    
    
                        Image(systemName: "chevron.right")
                            .font(.title2)
                            .foregroundColor(.primary)
                    }

                }
                
// 搜索 + 文字展示模块
                HStack{
    
    
                    Image(systemName: "magnifyingglass")
                        .font(.callout)
                        .foregroundColor(.primary)
                    
                    if bottomViewModel.tabState == .floating {
    
    
                        TextField("",text: $bottomViewModel.searchText)
                            .matchedGeometryEffect(id: "SearchField", in: animation)
// 点击TextField 处理是否显示键盘的状态
                            .focused($showKeyboard)
                        // KeyBoard Button
                            .submitLabel(.go)
                    }
                    else{
    
    
                        Text(bottomViewModel.searchText)
                            .matchedGeometryEffect(id: "SearchField", in: animation)

                    }
                    
                    
                    Image(systemName: "lock")
                        .symbolVariant(.fill)
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .offset(y: bottomViewModel.tabState == .floating ? 0 : (bottomEdge == 0 ? 0 : -10))
                // 扩展时的最大宽度…
                .frame(maxWidth: bottomViewModel.tabState == .floating ? nil : 200)
                
                
                if bottomViewModel.tabState == .floating {
    
    
                    Button  {
    
    
                        
                    } label: {
    
    
                        Image(systemName: "line.3.horizontal")
                            .font(.title2)
                            .foregroundColor(.primary)
                    }


                    
                    Button  {
    
    
                        
                    } label: {
    
    
                        Image(systemName: "square.on.square")
                            .font(.title2)
                            .foregroundColor(.primary)
                    }

                }
                
            }
            .colorScheme(bottomViewModel.tabState == .floating  ? .dark : .light)
            .padding()
        }
        .frame(height: 60)
        .padding([.horizontal],bottomViewModel.tabState == .expanded ? 0 : 15)
        // 移动视图…
        .frame(maxHeight:.infinity,alignment: showKeyboard ? .top : .bottom)
        // 当键盘弹出 弹出最顶部
        // 当展开返回浮动…
        .onTapGesture {
    
    
            withAnimation(.easeOut.speed(1.5)) {
    
    
                bottomViewModel.tabState = .floating
            }
        }
        .animation(.easeOut, value: showKeyboard)
    }
}


//  抵消修饰符……
// 监听偏移的数值
struct OffsetModifier: ViewModifier {
    
    
    @EnvironmentObject var model : BottomBarViewModel
    func body(content: Content) -> some View {
    
    
        content
            .overlay(
                // 几何阅读器获取偏移量…
                GeometryReader{
    
    proxy -> Color in
                    let minY = proxy.frame(in: .named("TabScroll")).minY
                    DispatchQueue.main.async {
    
    
                        // 检查和切换状态..
                        // 时间偏移量……
                        let durationOffset : CGFloat = 35
                        
                        if minY < model.offset {
    
    
                            print("up")
// 向下滚动 偏移量是负数
                            if model.offset < 0 &&  -minY  > (model.lastStoredOffset + durationOffset) {
    
    
                                withAnimation(.easeOut.speed(1.5)){
    
    
                                    // 更新状态……
                                    model.tabState = .expanded
                                }
                                
                                model.lastStoredOffset = -model.offset

                            }
                        }
                        
                        if minY > model.offset &&  -minY  < (model.lastStoredOffset - durationOffset) {
    
    
                            print("down")
                            withAnimation(.easeOut.speed(1.5)){
    
    
                                 // 更新状态
                                model.tabState = .floating
                            }
                            // 最后回采抵消……
                            model.lastStoredOffset = -model.offset
                        }
                   
                        
                        model.offset = minY
                    }
                    
                    return Color.clear
                }
                ,alignment: .top
            )
    }
}

BottomBarViewModel - 视图模型

//
//  BottomBarViewModel.swift
//  SafariTabBar
//
//  Created by 李宇鸿 on 2022/9/24.
//

import SwiftUI

class BottomBarViewModel: ObservableObject {
    
    
    // 所有属性……
    @Published var searchText = "iJustine"
    
    // Offset...
    @Published var offset : CGFloat = 0
    @Published var lastStoredOffset : CGFloat = 0
    @Published var tabState : BottomState = .floating

}


// 枚举状态…
enum BottomState{
    
    
    case floating //  浮动
    case expanded //  扩大
}

猜你喜欢

转载自blog.csdn.net/qq_42816425/article/details/126824110