SwiftUI使用SwiftUI开发一个APP

使用SwiftUI开发一个APP - 页面跳转navigatio

2021-08-27  本文已影响0人  LazyGunner

之前的文章介绍了列表视图和无限加载瀑布流,今天的主要内容是讲在列表视图上方增加一个搜索输入框,并跳转到搜索页面完成搜索的实现。

0. 调整文件夹结构

因为后面多了一些页面,所以相比之前的所有文件都丢到一个文件夹的粗暴方式,还是更文明的把View和Model都整理到文件夹中比较好

image

1. 搜索框以及搜索页面跳转

因为首页瀑布流和搜索结果的瀑布流中,资源列表的展示都是相同的。所以,我将之前的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()
    }
}
  1. 通过 TextView作为搜索的输入框,绑定输入到searchText变量上,并设置一个onCommit事件的处理函数,这里用来跳转到搜索页面。

  2. 在输入框上叠加 搜索图标 和 清空图片按钮

  3. 点击事件,设置isEditing变量为 true, 用来展示 取消按钮

  4. 取消按钮

  5. 首页瀑布流

  6. 隐藏导航栏

  7. onAppear 生命周期,清空输入框内容 和 设置isEditing 为 false,主要用于从搜索页面返回时重置搜索框

  8. 通过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")
    }
}
  1. 首先页面结构和 目前的HomeView一致,都是顶部一个搜索框,下面是瀑布流。
  2. 不一样的是在 页面刚一进入和输入框提交的时候,都调用了viewModel 的 getResourceList()方法。
image

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:

  1. 折叠代码快捷键: CMD + Option + ← (折叠),CMD + Option + → (打开)
上一篇 下一篇

猜你喜欢

热点阅读