Skip to content

Instantly share code, notes, and snippets.

@iandreyshev
Created September 27, 2024 08:06
Show Gist options
  • Save iandreyshev/ee79e75583e15dc4bb2bafc2c6ace8a6 to your computer and use it in GitHub Desktop.
Save iandreyshev/ee79e75583e15dc4bb2bafc2c6ace8a6 to your computer and use it in GitHub Desktop.
SwiftUI architecture example
//
// ContentView.swift
// SwiftUIArchitectureSample
//
// Created by Ivan Andreyshev on 26.09.2024.
//
import SwiftUI
// View экрана
//
// Задачи:
// - Рисует экран
//
// Максимально глупый компонент: только рисует дизайн по стейту и передает все события
// от пользователя во ViewModel, чтобы та принимала решения по изменению стейта
//
// Важно чтобы поле viewModel отмечалось аннотацией @StateObject
// с помощью этого будет происходить перерисовка экрана при изменении состояния
//
struct ContentView: View {
@StateObject
private var viewModel = CalcViewModel()
var body: some View {
VStack {
if viewModel.state.isLoadingOnStart {
ProgressView()
Spacer().frame(height: 20)
Text("Пожалуйста, подождите, калькулятор включается...")
.multilineTextAlignment(.center)
} else {
if viewModel.state.isCalculating {
ProgressView()
} else {
Text(viewModel.state.result)
.font(.title)
}
Spacer().frame(height: 10)
Text("Калькулятор считает сумму чисел")
Spacer().frame(height: 40)
VStack {
HStack(spacing: 20) {
numberButtonView(number: 1)
numberButtonView(number: 2)
numberButtonView(number: 3)
}
Spacer().frame(height: 20)
HStack(spacing: 20) {
numberButtonView(number: 4)
numberButtonView(number: 5)
numberButtonView(number: 6)
}
Spacer().frame(height: 20)
HStack(spacing: 20) {
numberButtonView(number: 7)
numberButtonView(number: 8)
numberButtonView(number: 9)
}
Spacer().frame(height: 20)
numberButtonView(number: 0)
Spacer().frame(height: 20)
Button("Посчитать сумму") { viewModel.onSubmit() }
.font(.title)
Spacer().frame(height: 20)
Button("Сброс") { viewModel.onReset() }
.font(.title)
}
}
}
.onAppear {
viewModel.onFirstLoading()
}
}
func numberButtonView(number: Int) -> some View {
return Button(String(number)) { viewModel.onAddNumber(number) }
.font(.title)
.frame(width: 30)
}
}
// Состояние экрана
//
// Задачи:
// - хранить данные для отображения
// - хранить данные для определения бизнес логики
//
struct CalcState {
var isLoadingOnStart = true
var numbers: [Int] = []
var result = "Введите числа"
var isCalculating = false
}
// ViewModel экрана
//
// Задачи:
// - Содержит бизнес-логику экрана (в простейшем случае)
// - Получает вызовы от View и принимает решение что с ними делать
// - Обновляет состояние экрана
//
// Любой ввод от пользователя попадает во ViewModel и внутри нее происходит
// какая-то логика, а затем изменение состояния
//
// Важно чтобы ViewModel наследовалась от ObservableObject, а State отмечался
// аннотацией @Published
//
class CalcViewModel: ObservableObject {
@Published
var state: CalcState = .init()
func onFirstLoading() {
// Task используется когда нужно выполнить длительные операции, чтобы
// не блокировать UI-поток и интерфейс не зависал
Task {
// Имитация первой загрузки
try await Task.sleep(nanoseconds: UInt64(3 * Double(NSEC_PER_SEC)))
state.isLoadingOnStart = false
}
}
func onAddNumber(_ number: Int) {
if state.isCalculating {
return
}
state.numbers.append(number)
state.result = state.numbers
.map { String($0) }
.joined(separator: " + ")
}
func onSubmit() {
// Запрещаем что-то делать, если вычисление уже в процессе
if state.isCalculating {
return
}
state.isCalculating = true
// Task используется когда нужно выполнить длительные операции, чтобы
// не блокировать UI-поток и интерфейс не зависал
Task {
if state.numbers.isEmpty {
state.result = "Числа не вводились"
} else {
// Имитация
// Как будто наше вычисление делается долго
try await Task.sleep(nanoseconds: UInt64(3 * Double(NSEC_PER_SEC)))
var sum = 0
state.numbers.forEach {
sum += $0
}
state.result = "Сумма чисел: \(sum)"
state.numbers = []
}
state.isCalculating = false
}
}
func onReset() {
if state.isCalculating {
return
}
state.numbers = []
state.result = "Введите числа"
}
}
#Preview {
ContentView()
}
@medef00
Copy link

medef00 commented Sep 30, 2024

.onAppear -> .task

func onFirstLoading() async {
    try await Task.sleep(for: .seconds(3))
    state.isLoadingOnStart = false
}

@medef00
Copy link

medef00 commented Sep 30, 2024

150 if -> guard

@medef00
Copy link

medef00 commented Sep 30, 2024

var sum = 0
state.numbers.forEach {
    sum += $0
}
let sum = state.numbers.reduce(0, +)

@medef00
Copy link

medef00 commented Sep 30, 2024

179 if -> guard

@medef00
Copy link

medef00 commented Sep 30, 2024

CalcState -> CalculationContext / CalculatorData

@medef00
Copy link

medef00 commented Sep 30, 2024

Spacer().frame(height: 10) так лучше не делать, посмотри в сторону Grid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment