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

设置@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:45:35