Search code examples
iosuitableviewswiftui-list

SwiftUI LazyVStack vs UITableView CPU Usage


I have two identical projects. One was written with SwiftUI and the other with UIKit. But while UIKit consumes 5% CPU, SwiftUI consumes 26% CPU. Does anyone know how I can reduce the CPU used by SwiftUI?

My original question was List vs UITableView. But I saw that LazyVStack was much more efficient than List, thanks to @Paulw11. But it's still far behind UITableView.

import SwiftUI

struct ContentView: View {
    
    @State private var progress: Double = 0.0
        
    var body: some View {
        VStack {
            ScrollView {
                LazyVStack {
                    ForEach(1...1000, id: \.self) { value in
                        Text("Hello, world!")
                    }
                }
            }
            Slider(value: $progress, in: 0.0...1.0, step: 0.01)
        }
        .onChange(of: progress, perform: { value in
            print(value)
        })
    }
}
import UIKit

final class ViewController: UIViewController {
    
    private let array: Array<String> = Array(repeating: "Hello, world!", count: 1000)
    
    @IBOutlet weak var tableView: UITableView!
    
    @IBOutlet weak var slider: UISlider!
    
    @IBAction func sliderChanged(_ sender: UISlider) {
        print(sender.value)
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        array.count[enter image description here][1]
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
        cell.textLabel?.text = array[indexPath.row]
        return cell
    }
}

UITableView Project Photo

SwiftUI Project Photo

Download sample projects


Solution

  • @hyouuu The most important part is the progress value. Avoid using the @Published value as much as possible in ViewModels. Instead, split the views into subviews and manually set a value to the ViewModel as in the example.

    import SwiftUI
    import Combine
    
    final class ViewModel: ObservableObject {
        
        var array = Array(1...1000)
        var text = "Hello, world!"
        
        var progress: Double = 0.0 {
            didSet {
                print("new value",progress)
            }
        }
    }
    
    struct ContentView: View {
        
        @StateObject var viewModel = ViewModel()
        
        var body: some View {
            VStack {
                Scroll(viewModel: viewModel)
                Slide(viewModel: viewModel)
            }
        }
    }
    
    struct Scroll: View {
        
        @StateObject var viewModel: ViewModel
        
        var body: some View {
            List {
                ForEach(viewModel.array, id: \.self) { value in Text(viewModel.text)  }
            }
        }
    }
    
    struct Slide: View {
        
        @StateObject var viewModel: ViewModel
        @State var progress:Double = 0.0
        
        var body: some View {
            Slider(value: $progress, in: 0.0...1.0, step: 0.01)
                .onChange(of: progress) { newValue in
                    viewModel.progress = newValue
                }
        }
    }