使用SwiftUI開發(fā)一個APP - 頁面跳轉(zhuǎn)navigation

之前的文章介紹了列表視圖和無限加載瀑布流,今天的主要內(nèi)容是講在列表視圖上方增加一個搜索輸入框,并跳轉(zhuǎn)到搜索頁面完成搜索的實現(xiàn)。

0. 調(diào)整文件夾結(jié)構(gòu)

因為后面多了一些頁面,所以相比之前的所有文件都丟到一個文件夾的粗暴方式,還是更文明的把View和Model都整理到文件夾中比較好

image

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()
    }
}
  1. 通過 TextView作為搜索的輸入框,綁定輸入到searchText變量上,并設(shè)置一個onCommit事件的處理函數(shù),這里用來跳轉(zhuǎn)到搜索頁面。

  2. 在輸入框上疊加 搜索圖標 和 清空圖片按鈕

  3. 點擊事件,設(shè)置isEditing變量為 true, 用來展示 取消按鈕

  4. 取消按鈕

  5. 首頁瀑布流

  6. 隱藏導(dǎo)航欄

  7. onAppear 生命周期,清空輸入框內(nèi)容 和 設(shè)置isEditing 為 false,主要用于從搜索頁面返回時重置搜索框

  8. 通過NavigationLink完成頁面跳轉(zhuǎn),變成完成頁面跳轉(zhuǎn)是通過 isActive參數(shù)來控制的,詳見官方文檔https://developer.apple.com/documentation/swiftui/navigationlink。同時我在跳轉(zhuǎn)到SearchView的同時,通過SearchView的構(gòu)造函數(shù)中的searchText參數(shù)完成 參數(shù)傳遞。

看一下效果:

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

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:

  1. 折疊代碼快捷鍵: CMD + Option + ← (折疊),CMD + Option + → (打開)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容