Назад к задачам
Junior — Senior
6

Оптимизация экрана загрузки списка пользователей

Компании, где спрашивали:

SmartWay
Получайте помощь с лайвкодингом в реальном времени с Sobes Copilot
Условие задачи

Модуль реализован как экран с ползунком для выбора количества запрашиваемых пользователей, кнопкой для инициирования загрузки и несколькими представлениями для отображения результата. Полный список выводится полностью, без постраничной подгрузки.

Требуется изучить текущую реализацию и исправить все найденные недочёты.

struct ContentView: View {
    @ObservedObject private var vm = ViewModel()
    @State private var userCount = 3.0

    var body: some View {
        ZStack(alignment: .top) {
            Background(count: $userCount)
                .frame(width: UIScreen.main.bounds.width, height: 115)

            VStack {
                Slider(
                    value: $userCount,
                    in: 3...100,
                    step: 1
                ) { _ in }
                .tint(.purple)

                Button("Запросить \(Int(userCount)) пользователей") {
                    Task {
                        await vm.downloadNames(count: Int(userCount))
                    }
                }
                .padding()
                .foregroundStyle(.white)
                .background(.purple)
                .clipShape(.buttonBorder)

                ProxyView(viewModel: vm)
            }
        }
    }
}

struct Background: View {
    @Binding var count: Double
    var backgroundColor: Color {
        switch count {
        case ..<10: return .green
        case 10..<50: return .yellow
        default: return .red
        }
    }

    var body: some View {
        Rectangle()
            .fill(backgroundColor)
    }
}

struct ProxyView: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        SuccessView(viewModel: viewModel)
    }
}

struct SuccessView: View {
    @ObservedObject var viewModel: ViewModel
    @State private var loading: Bool = false

    var body: some View {
        VStack {
            Text("Total users fetched: \(viewModel.names.count)")
            
            if loading { ProgressView() }

            List(viewModel.names, id: \ .self) { name in
                Text(name)
            }
        }
        .onReceive(viewModel.publisher) { value in
            loading = value
        }
    }
}

class ViewModel: ObservableObject {
    let networkService = NetworkService()
    let value = CurrentValueSubject<Bool, Never>(false)

    @Published var names: [String] = []
    var publisher: AnyPublisher<Bool, Never> {
        value.eraseToAnyPublisher()
    }

    func downloadNames(count: Int) async {
        value.send(true)
        async let ids = networkService.fetchUserIds(count: count)

        for id in await ids {
            networkService.fetchName(id: id) { name in
                self.names.append(name)
            }
        }

        value.send(false)
    }
}

class NetworkService {
    func fetchName(id: Int, completion: @escaping (String) -> Void) {
        let names = ["Александр", "Максим", "Иван", "Артём", "Дмитрий", "Никито", "Михаил", "Даниил", "Егор"]
        let surnames = ["Смирное", "Иванов", "Кузнецов", "Соколов", "Попов", "Лебедев", "Козлов", "Новиков", "Морозов"]

        DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 1) {
            completion(names.randomElement()! + " " + surnames.randomElement()!)
        }
    }

    func fetchUserIds(count: Int) async -> [Int] {
        try? await Task.sleep(for: .seconds(1))
        return Array(1...count)
    }
}

# Preview {
    ContentView()
}