尝试为简单的SwiftUI应用程序检索web数据添加搜索栏时出现问题



我有一个小项目,这是一个Swift UI练习的扩展,从Greg Lim的书开始对Github进行web调用Swift UI:https://github.com/ethamoos/GitProbe

我一直在用它来练习基本技能,并尝试添加其他可能在现实世界的应用程序中有用的功能。

与最初的练习相比,我的主要更改是添加了选择查找哪个用户的选项(这在以前是硬编码的),并允许用户输入此选项。因为这会返回大量数据,所以我现在想使结果List .可搜索,以便用户可以过滤结果。

我一直在这里遵循这个教程:https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-a-search-bar-to-filter-your-data

,但我已经意识到,这是基于返回的数据是字符串,因此搜索是一个字符串。

我返回JSON解码成用户数据对象的列表,所以直接搜索不工作。我假设我可以调整这个匹配字符串搜索对我的自定义对象,但我不确定如何做到这一点。

为了让你对我的意思有一个概念,这里是代码:

import SwiftUI
import URLImage

struct Result: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [User]

enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct User: Codable, Hashable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String

enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
}
}

class FetchUsers: ObservableObject {
@Published var users = [User]()

func search(for user:String) {
var urlComponents = URLComponents(string: "https://api.github.com/search/users")!
urlComponents.queryItems = [URLQueryItem(name: "q", value: user)]
guard let url = urlComponents.url else {
return
}
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let data = data {
let decodedData = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.users = decodedData.items
}
} else {
print("No data")
}
} catch {
print("Error: (error)")
}
}.resume()
}
}
struct ContentView: View {
@State var username: String = ""

var body: some View {
NavigationView {

Form {
Section {
Text("Enter user to search for")
TextField("Enter your username", text: $username).disableAutocorrection(true)
.autocapitalization(.none)
}
NavigationLink(destination: UserView(username: username)) {
Text("Show detail for (username)")
}
}
}
}
}
struct UserView: View {

@State var username: String
@ObservedObject var fetchUsers = FetchUsers()
@State var searchText = ""

var body: some View {
List {
ForEach(fetchUsers.users, id:.self) { user in
NavigationLink(user.login, destination: UserDetailView(user:user))
}
}.onAppear {
self.fetchUsers.search(for: username)
}
.searchable(text: $searchText)
.navigationTitle("Users")

}
/// With suggestion added 

/// The search results
private var searchResults: [User] {
if searchText.isEmpty {
return fetchUsers.users // your entire list of users if no search input
} else {
return fetchUsers.search(for: searchText) // calls your search method passing your search text
}
}
}
struct UserDetailView: View {

var user: User

var body: some View {
Form {
Text(user.login).font(.headline)
Text("Git iD = (user.id)")
URLImage(URL(string:user.avatarURL)!){ image in
image.resizable().frame(width: 50, height: 50)
}
}
}
}

如果有任何帮助,我将不胜感激。

您的UserListView没有正确构造。我不明白为什么你需要一个里面有空白文本的ScrollView ?

所以我从视图中删除了searchText到FetchUsers类,这样我们就可以延迟服务器请求,从而避免不必要的多次调用。请根据您的需要调整它(查看Apple的Debounce文档)。现在一切都应该正常了。

import Combine
class FetchUsers: ObservableObject {

@Published var users = [User]()

@Published var searchText = ""

var subscription: Set<AnyCancellable> = []

init() {
$searchText
.debounce(for: .milliseconds(500), scheduler: RunLoop.main) // debounces the string publisher, delaying requests and avoiding unnecessary calls.
.removeDuplicates()
.map({ (string) -> String? in
if string.count < 1 {
self.users = [] // cleans the list results when empty search
return nil
}
return string
}) // prevents sending numerous requests and sends nil if the count of the characters is less than 1.
.compactMap{ $0 } // removes the nil values
.sink { (_) in
//
} receiveValue: { [self] text in
search(for: text)
}.store(in: &subscription)
}

func search(for user:String) {
var urlComponents = URLComponents(string: "https://api.github.com/search/users")!
urlComponents.queryItems = [URLQueryItem(name: "q", value: user.lowercased())]
guard let url = urlComponents.url else {
return
}

URLSession.shared.dataTask(with: url) {(data, response, error) in

guard error == nil else {
print("Error: (error!.localizedDescription)")
return
}

guard let data = data else {
print("No data received")
return
}

do {
let decodedData = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.users = decodedData.items
}
} catch {
print("Error: (error)")
}
}.resume()
}
}
struct UserListView: View {

@State var username: String
@ObservedObject var fetchUsers = FetchUsers()
var body: some View {

NavigationView {
List {
ForEach(fetchUsers.users, id:.self) { user in
NavigationLink(user.login, destination: UserDetailView(user:user))
}
}
.searchable(text: $fetchUsers.searchText) // we move the searchText to fetchUsers
.navigationTitle("Users")
}
}
}

我希望这对你有帮助!:)

最后,我想我已经弄明白了——感谢Andre的建议。

我需要正确地过滤我的数据,然后返回剩余的。

以下是更正(删节)版本:

import SwiftUI
import URLImage

struct Result: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [User]

enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct User: Codable, Hashable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String

enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
}
}
class FetchUsers: ObservableObject {
@Published var users = [User]()

func search(for user:String) {
var urlComponents = URLComponents(string: "https://api.github.com/search/users")!
urlComponents.queryItems = [URLQueryItem(name: "q", value: user)]
guard let url = urlComponents.url else {
return
//            print("error")
}
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let data = data {
let decodedData = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.users = decodedData.items
}
} else {
print("No data")
}
} catch {
print("Error: (error)")
}
}.resume()
}
}
struct ContentView: View {
@State var username: String = ""

var body: some View {
NavigationView {

Form {
Section {
Text("Enter user to search for")
TextField("Enter your username", text: $username).disableAutocorrection(true)
.autocapitalization(.none)
}
NavigationLink(destination: UserView(username: username)) {
Text("Show detail for (username)")
}
}
}
}
}
struct UserView: View {

@State var username: String
@ObservedObject var fetchUsers = FetchUsers()
@State var searchText = ""

var body: some View {
List {
ForEach(searchResults, id:.self) { user in
NavigationLink(user.login, destination: UserDetailView(user:user))
}
}.onAppear {
self.fetchUsers.search(for: username)
}
.searchable(text: $searchText)
.navigationTitle("Users")
}

var searchResults: [User] {
if searchText.isEmpty {
print("Search is empty")
return fetchUsers.users
} else {
print("Search has a value - is filtering")
return fetchUsers.users.filter { $0.login.contains(searchText) }
}
}
}
struct UserDetailView: View {

var user: User

var body: some View {
Form {
Text(user.login).font(.headline)
Text("Git iD = (user.id)")
URLImage(URL(string:user.avatarURL)!){ image in
image.resizable().frame(width: 50, height: 50)
}
}
}
}

最新更新