You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么SwiftUI应用TextField每输入一个字符就触发API拉取与ViewModel初始化?

问题根源

你的问题本质是SwiftUI视图刷新机制导致的:
enteredIngredient 是@State属性,每次TextField输入字符修改该值都会触发整个IngredientView的body重新计算。你当前使用的NavigationLink(isActive:destination:label:)接口中,destination闭包的内容会在每次body刷新时直接求值,而非等到跳转触发时才执行,因此每次输入都会直接初始化ViewModel,而你的ViewModel在init方法中直接调用了FetchData()发起网络请求,就出现了输入一个字符就触发一次下载的问题。
另外你的ViewModel当前是普通类,没有遵守ObservableObject协议,网络请求回来的数据变更无法通知SwiftUI刷新DetailsView的UI,后续也会出现页面不更新的问题。

修复步骤

1. 改造ViewModel

将ViewModel改为可观察对象,把需要触发UI刷新的属性用@Published修饰,同时把网络请求调用从init方法移除,避免初始化时直接发起请求:

// ViewModel.swift
import Foundation
import UIKit
import Combine

class ViewModel: ObservableObject {
    
    let dataType: String
    let urlString: String
    @Published var drinks: Drinks = Drinks(drinks: [Cocktail]())
    @Published var ingredients: Ingredients = Ingredients(ingredients: [Ingredient]())
    @Published var alcohols: Alcohols = Alcohols(drinks: [Alcohol]())
    @Published var dataIsFound: Bool = true
    
    init(urlString: String, dataType: String) {
        self.urlString = urlString
        self.dataType = dataType
        // 移除此处的FetchData()调用
    }
    
    // 其余方法(FetchData、buildIngredients)保持不变
}

2. 改造IngredientView跳转逻辑

推荐使用iOS 16+的navigationDestination接口,只有跳转触发时才会初始化目标视图和ViewModel,避免提前执行:

// IngredientView.swift
import SwiftUI

struct IngredientView: View {
    
    @State var enteredIngredient: String = ""
    // 新增路由绑定属性
    @State private var targetIngredient: String?
    
    var body: some View {
        ZStack {
            Color(.systemTeal)
                .edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 5.0) {
                Text("请输入你想要搜索的原料")
                    .padding(50.0)
                    .foregroundColor(.white)
                    .font(.system(size: 20.0))
                    .navigationTitle("按原料搜索")
                
                TextField("原料", text: $enteredIngredient)
                    .textFieldStyle(.roundedBorder)
                    .frame(width: 250.0, height: 60.0, alignment: .center)
                    .foregroundColor(.black)
                
                Button {
                    if !enteredIngredient.isEmpty {
                        targetIngredient = enteredIngredient
                    }
                } label: {
                    Text("继续")
                        .foregroundColor(.white)
                        .font(.system(size: 25.0))
                        .padding()
                        .frame(width: 250.0)
                }
                .background(.blue)
                .clipShape(RoundedRectangle(cornerRadius: 30.0))
                
            }
            // 新增跳转路由配置
            .navigationDestination(item: $targetIngredient) { ingredient in
                DetailsView(viewModel: ViewModel(
                    urlString: "https://www.thecocktaildb.com/api/json/v1/1/search.php?i=\(ingredient)",
                    dataType: "Ingredients"
                ))
            }
        }
    }
}

如果需要兼容iOS 15及更早版本,可以用LazyView包装原有NavigationLink的destination,延迟初始化执行:

// 先定义LazyView工具类
struct LazyView<Content: View>: View {
    let content: () -> Content
    init(@ViewBuilder _ content: @escaping () -> Content) {
        self.content = content
    }
    var body: some View {
        content()
    }
}

// 原有NavigationLink修改为
NavigationLink(isActive: $ingredientIsEntered) {
    LazyView {
        DetailsView(viewModel: ViewModel(urlString: "https://www.thecocktaildb.com/api/json/v1/1/search.php?i=\(enteredIngredient)", dataType: "Ingredients"))
    }
} label: {
    EmptyView()
}

3. 改造DetailsView

用@StateObject持有ViewModel,在视图首次显示时发起网络请求:

// DetailsView.swift
import SwiftUI
import SDWebImageSwiftUI
import UIKit

struct DetailsView: View {
    // 用@StateObject持有可观察的ViewModel
    @StateObject var viewModel: ViewModel
    
    var body: some View {
        // 原有body的列表展示逻辑保持不变
        .onAppear {
            // 仅在视图首次显示时发起请求
            viewModel.FetchData()
        }
    }
}

内容的提问来源于stack exchange,提问作者sjlearmonth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 20:45:09