之前的文章介绍了列表视图和无限加载瀑布流,今天的主要内容是讲在列表视图上方增加一个搜索输入框,并跳转到搜索页面完成搜索的实现。
0. 调整文件夹结构
因为后面多了一些页面,所以相比之前的所有文件都丢到一个文件夹的粗暴方式,还是更文明的把View和Model都整理到文件夹中比较好
image1. 搜索框以及搜索页面跳转
因为首页瀑布流和搜索结果的瀑布流中,资源列表的展示都是相同的。所以,我将之前的ResourceListView作为一个通用的瀑布流View放在了一个独立文件中,供HomeView 和 SearchView两个视同集成。
首先我们来看一下HomeView的结构:
// HomeView.swift
// FoloPro
//
// Created by GUNNER on 2021/8/15.
//
import SwiftUI
struct HomeView: View {
@StateObject var viewModel = ResourceViewModel()
@State var searchText = ""
@State var isEditing = false
@State var isCommit = false
var searchView = SearchView()
var body: some View {
NavigationView {
VStack{
HStack{
TextField("搜索", text: $searchText, onCommit: {
print(searchText)
self.isCommit = true
}) // 1
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.horizontal, 10)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 16)
if isEditing {
Button(action: {
self.searchText = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 16)
}
}
}
) // 2
.onTapGesture {
self.isEditing = true
} // 3
NavigationLink(destination:SearchView(searchText: searchText), isActive: $isCommit) {
} // 8
if isEditing {
Button(action: {
self.isEditing = false
self.searchText = ""
// 关闭键盘
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}) {
Text("取消")
}
.padding(.trailing, 20)
.transition(.move(edge: .trailing))
.animation(.default)
} // 4
}
ResourceListView(viewModel: viewModel) // 5
}.navigationBarHidden(true) // 6
.onAppear() {
searchText = ""
isEditing = false
} // 7
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
-
通过 TextView作为搜索的输入框,绑定输入到searchText变量上,并设置一个onCommit事件的处理函数,这里用来跳转到搜索页面。
-
在输入框上叠加 搜索图标 和 清空图片按钮
-
点击事件,设置isEditing变量为 true, 用来展示 取消按钮
-
取消按钮
-
首页瀑布流
-
隐藏导航栏
-
onAppear 生命周期,清空输入框内容 和 设置isEditing 为 false,主要用于从搜索页面返回时重置搜索框
-
通过NavigationLink完成页面跳转,变成完成页面跳转是通过 isActive参数来控制的,详见官方文档https://developer.apple.com/documentation/swiftui/navigationlink。同时我在跳转到SearchView的同时,通过SearchView的构造函数中的searchText参数完成 参数传递。
看一下效果:
image image接下来看一下 SearchView的代码
// SearchView.swift
// FoloPro
//
// Created by GUNNER on 2021/8/15.
//
import SwiftUI
struct SearchView: View {
@State var searchText = ""
@State var isEditing = false
@State var isCommit = false
@StateObject var viewModel = SearchResourceViewModel()
var body: some View {
VStack{
HStack{ // 1
TextField("搜索", text: $searchText, onCommit: {
viewModel.queryStr = searchText
viewModel.currentPage = 1
viewModel.getResourceList() // 2
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.padding(.horizontal, 10)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 16)
if isEditing {
Button(action: {
self.searchText = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 16)
}
}
}
)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
self.isEditing = false
self.searchText = ""
// 关闭键盘
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}) {
Text("取消")
}
.padding(.trailing, 20)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
ResourceListView(viewModel: viewModel)
}.onAppear() {
viewModel.queryStr = searchText
viewModel.getResourceList()
}
}
}
struct SearchView_Previews: PreviewProvider {
static var previews: some View {
SearchView(searchText: "lost")
}
}
- 首先页面结构和 目前的HomeView一致,都是顶部一个搜索框,下面是瀑布流。
- 不一样的是在 页面刚一进入和输入框提交的时候,都调用了viewModel 的 getResourceList()方法。
2. StateObject 和 Protocol
上文提到过,因为HomeView 和 SearchView 页面结构是相同的,所以就都集成了ResourceListView瀑布流视图。但是有一个问题是HomeView 请求的接口还有传参 和 SearchView是不同的,也就是初始化 ResourceListView(viewModel: viewModel) 中的viewModel是不同的。
这里第一时间想到了,通过定义一个ResourceModel Protocol,让SearchResourceViewModel 和 ResourceViewModel 都实现这个Protocol,在ResourceListView中将 viewModel 的类型直接定义成 ResourceModel 这个Protocol的类型,就可以了。
ResourceModelProtocol 文件:
protocol ResourceModel: ObservableObject {
var resourceList: [Resource] {
get
set
}
func loadMoreContentIfNeeded(currentItem item: Resource?)
}
ResourceListView文件:
@StateObject var viewModel: ResourceModel
@Environment(\.colorScheme) var colorScheme
但是遇到了一个报错: Protocol 'ResourceModel' as a type cannot conform to 'ObservableObject' . 看来不能直接这样用,通过网上搜索解决方案找到了大神的解答,将代码改为如下,就可以了。参考地址: https://stackoverflow.com/questions/59503399/how-to-define-a-protocol-as-a-type-for-a-observedobject-property
@StateObject var viewModel: Model
好了,页面跳转和搜索就好了。后续的功能要做底部导航栏、详情页和类目页了,敬请期待
Tips:
- 折叠代码快捷键: CMD + Option + ← (折叠),CMD + Option + → (打开)
网友评论