I am making a swiftUI app. I am using UIKit's UICollectionView for some functionally and performance requirement. My cells are swiftUI views wrapped in UIHostingController. If I launch the app and start scrolling, Initially UICollectionView runs smooth but if I scroll further to the last element, My UICollectionView starts lagging. It is behaving the same every time I relaunch the app, Initially smooth and slow after few seconds of scrolling. If you check the debug navigator in Xcode the memory section keep on increasing as you scroll from top to bottom and bottom to top. I am not able to trace what is consuming so much memory.
I have created following demo to explain my problem. I was not able to achieve the same amount of lag as in the original app but if you keep scrolling you can see the memory level rising in debug navigator.
UICollectionView, UIViewRepresentable
struct UICollectionViewWrapper: UIViewRepresentable {
var width: CGFloat
var height: CGFloat
@ObservedObject var viewModel: ViewModel
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .clear
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.alwaysBounceVertical = true
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell")
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
//
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var parent: UICollectionViewWrapper
init(_ parent: UICollectionViewWrapper) {
self.parent = parent
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parent.viewModel.songs.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
let song = parent.viewModel.songs[indexPath.row]
cell.setUpCell(width: parent.width, height: parent.height, song: song)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: parent.width, height: parent.height)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
UICollectionViewCell
class CollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpCell(width: CGFloat,
height: CGFloat,
song: Song
) {
let host = UIHostingController(rootView: CoverView(song: song))
let uiView = host.view!
uiView.frame = CGRect(x: 0, y: 0, width: width, height: height)
addSubview(uiView)
}
}
ViewModel
class ViewModel: ObservableObject {
@Published var songs = [Song]()
func loadSongs() {
for _ in 0..<100 {
let song = Song(name: "Fix You",
artist: "ColdPlay",
album: "X&Y 2005",
lyrics: """
When you try your best, but you don't succeed
When you get what you want, but not what you need
When you feel so tired, but you can't sleep
Stuck in reverse
And the tears come streaming down your face
When you lose something you can't replace
When you love someone, but it goes to waste
Could it be worse?
Lights will guide you home
And ignite your bones
And I will try to fix you
"""
)
songs.append(song)
}
}
}
SwiftUI Views
struct CoverView: View {
var song: Song
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(.tint)
VStack(alignment: .leading, spacing: 10) {
Text("\(song.artist) - \(song.name)")
.font(.system(size: 30, weight: .bold, design: .rounded))
Text(song.album)
.font(.system(size: 22, weight: .medium, design: .rounded))
Spacer()
Text(song.lyrics)
.font(.system(size: 22, weight: .medium, design: .rounded))
}
.padding()
.padding(.top)
.foregroundColor(.white)
}
}
}
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
GeometryReader { geometry in
UICollectionViewWrapper(width: geometry.size.width,
height: geometry.size.height,
viewModel: viewModel
)
.onAppear {
viewModel.loadSongs()
}
}
}
}
Can you help me solve this issue ? Thanks in advance !
UPDATE:
Made some changes in CollectionViewCell. Performance is improved but I can still see the memory spikes after scrolling for a while and performance goes down after that.
class CollectionViewCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private(set) var host: UIHostingController<CoverView>?
func setUpCell(width: CGFloat,
height: CGFloat,
song: Song
) {
if let host = host {
host.rootView = CoverView(song: song)
host.view.layoutIfNeeded()
} else {
let host = UIHostingController(rootView: CoverView(song: song), ignoreSafeArea: true)
let uiView = host.view!
uiView.frame = CGRect(x: 0, y: 0, width: width, height: height)
addSubview(uiView)
self.host = host
}
}
}