之前的文章介紹了列表視圖和無限加載瀑布流,今天的主要內(nèi)容是講在列表視圖上方增加一個搜索輸入框,并跳轉(zhuǎn)到搜索頁面完成搜索的實現(xiàn)。
0. 調(diào)整文件夾結(jié)構(gòu)
因為后面多了一些頁面,所以相比之前的所有文件都丟到一個文件夾的粗暴方式,還是更文明的把View和Model都整理到文件夾中比較好

1. 搜索框以及搜索頁面跳轉(zhuǎn)
因為首頁瀑布流和搜索結(jié)果的瀑布流中,資源列表的展示都是相同的。所以,我將之前的ResourceListView作為一個通用的瀑布流View放在了一個獨立文件中,供HomeView 和 SearchView兩個視同集成。
首先我們來看一下HomeView的結(jié)構(gòu):
// 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 = ""
// 關(guān)閉鍵盤
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變量上,并設(shè)置一個onCommit事件的處理函數(shù),這里用來跳轉(zhuǎn)到搜索頁面。
在輸入框上疊加 搜索圖標 和 清空圖片按鈕
點擊事件,設(shè)置isEditing變量為 true, 用來展示 取消按鈕
取消按鈕
首頁瀑布流
隱藏導(dǎo)航欄
onAppear 生命周期,清空輸入框內(nèi)容 和 設(shè)置isEditing 為 false,主要用于從搜索頁面返回時重置搜索框
通過NavigationLink完成頁面跳轉(zhuǎn),變成完成頁面跳轉(zhuǎn)是通過 isActive參數(shù)來控制的,詳見官方文檔https://developer.apple.com/documentation/swiftui/navigationlink。同時我在跳轉(zhuǎn)到SearchView的同時,通過SearchView的構(gòu)造函數(shù)中的searchText參數(shù)完成 參數(shù)傳遞。
看一下效果:


接下來看一下 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 = ""
// 關(guān)閉鍵盤
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")
}
}
- 首先頁面結(jié)構(gòu)和 目前的HomeView一致,都是頂部一個搜索框,下面是瀑布流。
- 不一樣的是在 頁面剛一進入和輸入框提交的時候,都調(diào)用了viewModel 的 getResourceList()方法。

2. StateObject 和 Protocol
上文提到過,因為HomeView 和 SearchView 頁面結(jié)構(gòu)是相同的,所以就都集成了ResourceListView瀑布流視圖。但是有一個問題是HomeView 請求的接口還有傳參 和 SearchView是不同的,也就是初始化 ResourceListView(viewModel: viewModel) 中的viewModel是不同的。
這里第一時間想到了,通過定義一個ResourceModel Protocol,讓SearchResourceViewModel 和 ResourceViewModel 都實現(xiàn)這個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' . 看來不能直接這樣用,通過網(wǎng)上搜索解決方案找到了大神的解答,將代碼改為如下,就可以了。參考地址: https://stackoverflow.com/questions/59503399/how-to-define-a-protocol-as-a-type-for-a-observedobject-property
@StateObject var viewModel: Model
好了,頁面跳轉(zhuǎn)和搜索就好了。后續(xù)的功能要做底部導(dǎo)航欄、詳情頁和類目頁了,敬請期待
Tips:
- 折疊代碼快捷鍵: CMD + Option + ← (折疊),CMD + Option + → (打開)