在Simple Swift UI应用中通过TextField定义变量,然后将其传递给观察对象的问题



我在一个非常简单的应用程序上遇到了这个错误。我认为这可能是一个常见的错误,与我的代码序列中的问题有关,但是尽管检查了错误的各种实例,我还是找不到一个类似的案例来解决这个问题。

这个应用程序是一个练习的改编(来自Greg Lim的SwiftUI for Absolute Beginners),对Github API进行搜索调用并返回用户列表。在最初的代码中,它提供了一个硬编码的搜索值,然后在Github中查找它返回的所有用户。我想做的是把它变成一个搜索应用,能够动态地提供数据,这样你就可以选择你想要查找的用户。

我已经把它改编成一个单一的ContentView,这样它就可以很容易地运行。

我已经调整了可观察对象类"FetchUsers",使请求接受一个参数——例如搜索词——并且我已经为用户创建了一个textField来输入什么将提供这个。然后,我创建了一个@Binding实例,使这个值在一个新视图中可用。然而,我遇到的问题是,我不能在新视图中实例化我的"fetch"对象。

当我尝试在realallydetailedview中这样做时,我得到这个错误:不能在属性初始化器中使用实例成员'username';属性初始化器在'self'可用之前运行。正如我所理解的,当创建这个视图时,我有一个值要传递给它。

我可以看到,这是与我在错误的顺序初始化属性有关,但我不清楚如何解决这个问题?


import SwiftUI
//import URLImage
struct Result: Codable{
var items: [User] }
struct User: Codable{
public var login: String
public var url: String
public var avatar_url: String
public var html_url: String }

struct ContentView: View {

@State var username: String = ""

var body: some View {
NavigationView {

NavigationLink(destination: DetailView(username: $username)) {

Form {
Section {
Text("Show detail")
}
Section {
TextField("Enter your username", text: $username)
Text("Your username is (username)")
}
}
}
}
}
}
struct DetailView: View {

@Binding var username: String
//        @ObservedObject var fetch = FetchUsers(name: username)

var body: some View {

let newUsername = username

VStack(alignment: .leading) {

Form {
Section {
Text("Username is:(username)")
Text("New username is:(newUsername)")
}

NavigationLink(destination: ReallyDetailedView(username: username)) {
Button(action: {
print("Get Data button pressed...")
}) {
Text("Get Data")
}
}
//                    Text("(user.url)")
.foregroundColor(Color.gray)
}
}
}
}
class FetchUsers: ObservableObject {
@Published var items = [User]()
var name: String = ""
init(name: String) {
self.name = name
let url = URL(string: "https://api.github.com/search/users?q=(name)")!
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.items = decodedData.items
}
} else {
print("No data")
}
} catch {
print("Error: (error.localizedDescription ?? "unknown error")")
}
}.resume()
}
}

struct ReallyDetailedView: View {

@Binding var username: String

@ObservedObject var fetch = FetchUsers(name: username)

//    init(username: String) {
//          self.username = username
//    }


var body: some View {
var newUsername = username

VStack(alignment: .leading) {
Text("Username is:(username)")
Text("New username is:(newUsername)")

}

//        Display the users found using the search term with their photos
//        List(fetch.items, id: .login) { user in
//            Link(destination: URL(string: user.html_url)!){
//                HStack(alignment: .top){
//                    URLImage(URL(string:user.avatar_url)!){ image in image.resizable().frame(width: 50, height: 50)
//                    }
}
}

顺便说一下,我确实认为我可以在两个视图中完成所有这些,但我创建了第三个视图,以便完全确定在我进行数据调用之前已经建立了username属性。尽管如此,我似乎误解了要求的内容。

任何建议,或链接到例子,实现我正在努力做的,将不胜感激。

尝试从init执行搜索并不会像您发现的那样真正工作。

相反,有一个search函数。你可以在详细视图的.onAppear闭包中调用它:

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)
}
NavigationLink(destination: DetailView(username: $username)) {
Text("Show detail for (username)")
}
}
}
}
}
struct DetailView: View {

@Binding var username: String
@ObservedObject var fetchUsers = FetchUsers()

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)
}

}
}
struct UserDetailView: View {

var user: User

var body: some View {
Form {
Text(user.login).font(.headline)
Text("Score = (user.score)")
}
}
}

为了完整起见,下面是我用https://app.quicktype.io生成的Codable结构。我添加了Hashable的一致性,这样我就可以使用ForEach

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
}
}
// MARK: - User
struct User: Codable, Hashable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String
let url, htmlURL, followersURL: String
let followingURL, gistsURL, starredURL: String
let subscriptionsURL, organizationsURL, reposURL: String
let eventsURL: String
let receivedEventsURL: String
let type: String
let siteAdmin: Bool
let score: Int
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case htmlURL = "html_url"
case followersURL = "followers_url"
case followingURL = "following_url"
case gistsURL = "gists_url"
case starredURL = "starred_url"
case subscriptionsURL = "subscriptions_url"
case organizationsURL = "organizations_url"
case reposURL = "repos_url"
case eventsURL = "events_url"
case receivedEventsURL = "received_events_url"
case type
case siteAdmin = "site_admin"
case score
}
}

相关内容

最新更新