Problem
This only occurs when building a target for MacCatalyst. It works fine on iPhones (real devices and simulators).
I make a parent list inside of a NavigationView, call it “Query View”.
Each item in the QueryView is a NavigationLink. One of these NavigationLink’s destination view is a view containing another list (Model View). When navigating to the ModelView, its event list typically freezes up after an initial scroll or two. After it freezes, the only way to unfreeze it is to either pop out of the view and push back in, resize the window, or sometimes add/remove something from the list.
Minimal Reproducible Example
The steps below are for a minimal reproducible example:
- New Xcode project using the UIKit LifeCycle and SwiftUI Interface
- Tick the MacCatalyst option
- Add scene delegate code
- Add all other code into one file
Notes
• Sometimes its scrolls fine for a little bit, but eventually it always freezes.
• The CPU is 0% after freeze but then goes to a typical %15 percent when trying to scroll while frozen.
• The list is the only piece of UI that freezes, everything else is functional.
Scene Delegate Code
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let rootVM = RootView.ViewModel()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: RootView(vm: rootVM))
self.window = window
window.makeKeyAndVisible()
}
}
All Other Code
import SwiftUI
struct RootView: View {
@ObservedObject var vm:ViewModel
var body: some View {
NavigationView {
List {
ForEach(vm.roles, id: \.rawValue) { role in
NavigationLink(
role.rawValue,
destination: viewForRole(role: role)
)
}
}
}
.navigationBarTitle("Roles")
}
@ViewBuilder func viewForRole(role: ViewModel.Role) -> some View {
if role == .admin {
QueryView(vm: vm.queryVM)
}else if role == .moderator {
Text("Coming soon!")
}else{
Text(role.rawValue)
}
}
}
extension RootView {
class ViewModel:ObservableObject {
let queryVM = QueryView.ViewModel()
@Published var roles:[Role]
enum Role:String, CaseIterable {
case admin = "Admin"
case moderator = "Moderator"
}
init() {
self.roles = Role.allCases
}
}
}
struct QueryView:View {
@ObservedObject var vm:ViewModel
var body: some View {
VStack(alignment: .center) {
TextField("Search", text: $vm.searchValue)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(maxWidth: 250)
.padding()
List {
ForEach(vm.models, id: \.self) { model in
NavigationLink(model, destination: ModelView(vm: ModelView.ViewModel()))
}
}
Button("Add Model") {
vm.addModel()
}
.padding()
}
.navigationBarTitle("Query View")
}
}
extension QueryView {
class ViewModel:ObservableObject {
@Published var models:[String]
@Published var searchValue:String
init() {
self.searchValue = "Just for looks..."
self.models = [String]()
for i in 0..<20 {
models.append("Model \(i+1)")
}
}
func addModel() {
self.models.append("Model \(models.count + 1)")
}
}
}
struct ModelView:View {
@ObservedObject var vm:ViewModel
var body: some View {
HStack {
List {
ForEach(vm.events, id: \.self) { event in
Text(event)
}
}
.frame(maxWidth: 300)
Text("Other Model Form")
.frame(maxWidth: .infinity)
}
.navigationBarTitle("Model View")
}
}
extension ModelView {
class ViewModel:ObservableObject {
var events:[String]
init() {
self.events = [String]()
for i in 0..<30 {
events.append("Event \(i+1)")
}
}
}
}