设置@Published属性时出现无限循环的解决方案咨询
问题解决:SwiftUI MVVM模式下照片列表跳转详情的无限循环问题
我用MVVM模式开发了一个SwiftUI视图,通过列表展示照片,点击任意列表项要显示对应照片的详情视图,但设置ViewModel的selectedPhoto属性时出现了无限循环,要求在不新增详情视图属性的前提下解决这个问题。
现有代码
Photo结构体
struct Photo: Identifiable { var id = UUID() var name: String }
ContentView(updatePhoto方法导致无限循环)
struct ContentView: View { @StateObject private var viewModel = ViewModel() var body: some View { NavigationView { List { ForEach(viewModel.photos) { selectedPhoto in showDetailView(with: selectedPhoto) } } .navigationTitle("Favorite Photo") } } } extension ContentView { func showDetailView(with selectedPhoto: Photo?) -> some View { if let selectedPhoto = selectedPhoto { viewModel.updatePhoto(selectedPhoto) } return DetailView(viewModel: viewModel) } }
ViewModel
class ViewModel: ObservableObject { @Published var photos = [ Photo(name: "Photo 1"), Photo(name: "Photo 2"), Photo(name: "Photo 3") ] @Published var selectedPhoto: Photo? func updatePhoto(_ selectedPhoto: Photo?) { self.selectedPhoto = selectedPhoto } }
DetailView
struct DetailView: View { @ObservedObject private var viewModel: ViewModel init(viewModel: ViewModel) { self.viewModel = viewModel } var body: some View { Text(viewModel.selectedPhoto?.name ?? "Unknown photo name") } }
问题原因
当前代码的核心问题在于:
- 列表渲染时,每个列表项都会调用
showDetailView方法,而这个方法直接执行了viewModel.updatePhoto,修改了@Published标记的selectedPhoto属性 selectedPhoto更新会触发ContentView重新渲染(因为ContentView持有@StateObject类型的viewModel),重新渲染又会再次调用showDetailView,形成无限循环- 同时这种写法错误地把
DetailView直接嵌入列表项中,并非实现点击跳转的正确逻辑
解决方案(不修改DetailView)
我们需要用SwiftUI的导航组件实现点击跳转,只在用户点击时更新selectedPhoto,而非渲染列表时就修改。这里用NavigationLink结合tag和selection的方式,既符合SwiftUI的导航逻辑,又能避免无限循环:
适配iOS 16+的写法
struct ContentView: View { @StateObject private var viewModel = ViewModel() var body: some View { NavigationStack { List { ForEach(viewModel.photos) { photo in NavigationLink( photo.name, tag: photo, selection: $viewModel.selectedPhoto ) } } .navigationTitle("Favorite Photo") .navigationDestination(item: $viewModel.selectedPhoto) { _ in DetailView(viewModel: viewModel) } } } }
兼容iOS 15及以下的写法
struct ContentView: View { @StateObject private var viewModel = ViewModel() var body: some View { NavigationView { List { ForEach(viewModel.photos) { photo in NavigationLink( destination: DetailView(viewModel: viewModel), tag: photo, selection: $viewModel.selectedPhoto ) { Text(photo.name) } } } .navigationTitle("Favorite Photo") } } }
方案说明
- 使用
NavigationLink的tag和selection绑定viewModel.selectedPhoto,只有用户点击列表项时,才会把对应photo赋值给selectedPhoto,避免了渲染时的不必要修改 - 导航到
DetailView时,直接传入原viewModel,DetailView依然通过viewModel.selectedPhoto获取数据,完全不需要新增属性 - 彻底打破了原有的无限循环逻辑,同时实现了正确的点击跳转交互
内容的提问来源于stack exchange,提问作者Fabricio
相关产品推荐
相关产品推荐

