SwiftUI中如何将TextField内容传入函数拼接URL并请求数据
解决SwiftUI中View输入值传递给Combine ViewModel的问题
想要将ResultView中TextField的inputText传入DownloadWithCombineViewModel的getPosts函数,拼接成请求URL获取PostModel数据,但ViewModel无法访问到inputText,以下是具体解决方法:
原问题代码
import Combine import SwiftUI struct PostModel: Identifiable, Codable { let id: String let url: String } class DownloadWithCombineViewModel: ObservableObject { @Published var posts: [PostModel] = [] var cancellables = Set<AnyCancellable>() init() { getPosts() } func getPosts() { guard let url = URL(string: "https://example\(inputText)") else { return } URLSession.shared.dataTaskPublisher(for: url) .receive(on: DispatchQueue.main) .tryMap(handleOutput) .decode(type: PostModel.self, decoder: JSONDecoder()) .replaceError(with: PostModel(id: "", url: "")) .sink(receiveValue: { [weak self] returnedPosts in self?.posts = [returnedPosts] }) .store(in: &cancellables) } func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data { guard let response = output.response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300 else { throw URLError(.badServerResponse) } return output.data } } struct ResultView: View { @State var inputText = "" @State var showResult = false @StateObject var vm = DownloadWithCombineViewModel() var body: some View { NavigationView { VStack { TextField("Enter", text: $inputText) Button("Show Result") { showResult.toggle() } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) if showResult { List { ForEach(vm.posts) { post in VStack(alignment: .leading) { Text(post.url) .font(.headline) } .frame(maxWidth: .infinity, alignment: .leading) .padding() } } } } } } }
核心修改方案
修改点说明
- 让
getPosts函数接收外部传入的inputText参数,消除对ViewModel内部未定义变量的依赖 - 移除ViewModel初始化时自动调用
getPosts的逻辑,改为由View手动触发请求 - 在按钮点击事件中,将View的
inputText传入ViewModel的请求方法,同时控制结果展示
修改后完整代码
import Combine import SwiftUI struct PostModel: Identifiable, Codable { let id: String let url: String } class DownloadWithCombineViewModel: ObservableObject { @Published var posts: [PostModel] = [] var cancellables = Set<AnyCancellable>() // 移除初始化时自动调用getPosts的逻辑 init() {} // 修改函数,接收外部传入的inputText参数 func getPosts(inputText: String) { // 拼接URL并做有效性校验,无效时清空数据 guard let url = URL(string: "https://example\(inputText)") else { posts = [] return } URLSession.shared.dataTaskPublisher(for: url) .receive(on: DispatchQueue.main) .tryMap(handleOutput) .decode(type: PostModel.self, decoder: JSONDecoder()) .replaceError(with: PostModel(id: "", url: "请求出错或数据无效")) .sink(receiveValue: { [weak self] returnedPost in self?.posts = [returnedPost] }) .store(in: &cancellables) } func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data { guard let response = output.response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300 else { throw URLError(.badServerResponse) } return output.data } } struct ResultView: View { @State var inputText = "" @State var showResult = false @StateObject var vm = DownloadWithCombineViewModel() var body: some View { NavigationView { VStack { TextField("Enter", text: $inputText) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() Button("Show Result") { // 点击按钮时传入inputText并发起请求 vm.getPosts(inputText: inputText) showResult.toggle() } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(10) if showResult { List { ForEach(vm.posts) { post in VStack(alignment: .leading) { Text(post.url) .font(.headline) } .frame(maxWidth: .infinity, alignment: .leading) .padding() } } } } } } }
额外优化建议
- 给TextField添加输入验证逻辑,确保内容符合URL格式要求后再发起请求
- 添加加载状态提示,避免用户在请求过程中重复点击按钮
- 细化错误处理,区分无效URL、网络错误、数据解析错误等不同场景
内容的提问来源于stack exchange,提问作者Stakanovec
相关产品推荐
相关产品推荐

