技术: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 // 扩大
}