Skip to content

Instantly share code, notes, and snippets.

@Jxrgxn
Last active March 8, 2023 17:37
Show Gist options
  • Save Jxrgxn/c7f2739aebb350a68d2b6cd8ae2e6dbf to your computer and use it in GitHub Desktop.
Save Jxrgxn/c7f2739aebb350a68d2b6cd8ae2e6dbf to your computer and use it in GitHub Desktop.
protocol RandomNumberGeneratorViewControllerProtocol: AnyObject {
// A protocol for creating view controllers that can show an array of numbers
func showNumbers(_ numbers: [Int])
// A protocol for creating view controllers that can show an error message
func showError(_ error: Error)
}
class RandomNumberGeneratorViewController: UIViewController, RandomNumberGeneratorViewControllerProtocol {
private let presenter: RandomNumberGeneratorPresenterProtocol
private let debouncer: DebouncerProtocol
// Create a collection view to display the random numbers
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.register(CustomCell.self, forCellWithReuseIdentifier: "CustomCell")
cv.delegate = self
return cv
}()
private var dataSource: UICollectionViewDiffableDataSource<Section, Int>!
init(presenter: RandomNumberGeneratorPresenterProtocol, debouncer: DebouncerProtocol) {
self.presenter = presenter
self.debouncer = debouncer
super.init(nibName: nil, bundle: nil)
presenter.view = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
presenter.generateUniqueRandomNumbers(count: 5, inRange: 1...100)
}
private func configureCollectionView() {
// Add the collection view to the view hierarchy
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// Create a data source for the collection view
dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) { collectionView, indexPath, number in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.configure(with: number)
return cell
}
}
func showNumbers(_ numbers: [Int]) {
// Update the data source with the new array of numbers
var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
snapshot.appendSections([.main])
snapshot.appendItems(numbers)
dataSource.apply(snapshot, animatingDifferences: true)
}
func showError(_ error: Error) {
// This function can be implemented to show an error message to the user
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension RandomNumberGeneratorViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemSize = (collectionView.bounds.width - 20) / 2
return CGSize(width: itemSize, height: itemSize)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
}
}
// MARK: - Constants
extension RandomNumberGeneratorViewController {
enum Section {
case main
}
}
// MARK: - Debouncer
protocol DebouncerProtocol {
// A protocol for creating debouncers that can handle a closure with no parameters and no return value
func call(action: @escaping () -> Void)
}
class Debouncer: DebouncerProtocol {
private let delay: TimeInterval
private var timer: Timer?
init(delay: TimeInterval) {
self.delay = delay
}
func call(action: @escaping () -> Void) {
// Invalidate the timer to prevent multiple calls to the closure
timer?.invalidate()
// Create a new timer to call the closure after a delay
timer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in
self?.debounce(action)
}
}
private func debounce(_ action: @escaping () -> Void) {
// Add some logic here to debounce the action
// This can include delaying or throttling the action, or performing additional checks before calling the action
// In this case, we're just immediately calling the action
action()
}
}
protocol RandomNumberGeneratorPresenterProtocol: AnyObject {
// A protocol for creating presenters that can generate unique random numbers within a given range
var view: RandomNumberGeneratorViewControllerProtocol? { get set }
func generateUniqueRandomNumbers(count: Int, inRange range: ClosedRange<Int>)
}
class RandomNumberGeneratorPresenter: RandomNumberGeneratorPresenterProtocol {
weak var view: RandomNumberGeneratorViewControllerProtocol?
private let debouncer: DebouncerProtocol
init(debouncer: DebouncerProtocol) {
self.debouncer = debouncer
}
func generateUniqueRandomNumbers(count: Int, inRange range: ClosedRange<Int>) {
debouncer.call { [weak self] in
var numbers = Set<Int>()
while numbers.count < count {
numbers.insert(Int.random(in: range))
}
let uniqueNumbers = Array(numbers)
self?.view?.showNumbers(uniqueNumbers)
}
}
}
import SwiftUI
import Combine
protocol RandomNumberGeneratorViewControllerProtocol: AnyObject {
func showError(_ error: Error)
}
struct RandomNumberGeneratorView: View {
@StateObject private var presenter = RandomNumberGeneratorPresenter()
@State private var numbers = [Int]()
var body: some View {
NavigationView {
if !numbers.isEmpty {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 5) {
ForEach(numbers, id: \.self) { number in
CustomCell(number: number)
}
}
} else {
Text("Generating numbers...")
}
.navigationBarTitle("Random Numbers")
.onAppear {
presenter.generateUniqueRandomNumbers(count: 5, inRange: 1...100)
}
.alert(isPresented: $presenter.isErrorShown) {
Alert(title: Text("Error"), message: Text(presenter.errorMessage ?? "Unknown error"), dismissButton: .default(Text("OK")))
}
}
.onReceive(presenter.$numbers) { numbers in
self.numbers = numbers
}
}
private func showError(_ error: Error) {
presenter.showError(error)
}
}
struct CustomCell: View {
let number: Int
var body: some View {
Text("\(number)")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.padding(.horizontal, 10)
}
}
class RandomNumberGeneratorPresenter: ObservableObject {
@Published var isErrorShown = false
@Published var errorMessage: String?
@Published var numbers = [Int]()
private var cancellable: AnyCancellable?
init() {
cancellable = $numbers
.debounce(for: 0.5, scheduler: DispatchQueue.main)
.sink { [weak self] numbers in
self?.showNumbers(numbers)
}
}
func generateUniqueRandomNumbers(count: Int, inRange range: ClosedRange<Int>) {
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
do {
let numbers = Set<Int>(0..<count).map { _ in Int.random(in: range) }
self?.numbers = Array(numbers)
} catch {
DispatchQueue.main.async {
self?.showError(error)
}
}
}
}
func showError(_ error: Error) {
errorMessage = error.localizedDescription
isErrorShown = true
}
private func showNumbers(_ numbers: [Int]) {
self.numbers = numbers
}
}
struct RandomNumberGeneratorView_Previews: PreviewProvider {
static var previews: some View {
RandomNumberGeneratorView()
}
}
This code defines a simple app that generates a set of unique random numbers and displays them in a grid of cells. The user interface is built using SwiftUI, and the app uses Combine to handle asynchronous operations.
Here's a breakdown of the code:
protocol RandomNumberGeneratorViewControllerProtocol: AnyObject defines a protocol that the view controller can conform to. It includes a method for displaying error messages.
struct RandomNumberGeneratorView: View defines the main view of the app. It includes a @StateObject property for the presenter, a @State property for the numbers, and a body computed property that defines the user interface. The view includes a NavigationView that wraps a LazyVGrid that displays the random numbers in a grid of cells. The NavigationView also includes a title, and an onAppear modifier that generates the random numbers. If an error occurs during the generation of the random numbers, an alert is presented. The onReceive modifier updates the numbers property when the presenter.numbers property changes.
struct CustomCell: View defines a custom view that displays a number in a blue rounded rectangle.
class RandomNumberGeneratorPresenter: ObservableObject defines a presenter class that generates the random numbers and handles errors. It includes @Published properties for the error state, the error message, and the generated numbers. It also includes a private var that holds a subscription to the debounced numbers publisher. The init method creates the subscription to the debounced numbers publisher. The generateUniqueRandomNumbers method generates the random numbers asynchronously, and updates the numbers property when done. If an error occurs during the generation of the random numbers, it updates the error state and the error message. The showError method updates the error state and the error message. The showNumbers method updates the numbers property.
struct RandomNumberGeneratorView_Previews: PreviewProvider defines a preview for the RandomNumberGeneratorView.
Overall, this code provides a simple example of how to use SwiftUI and Combine to build a simple app that generates random numbers asynchronously and displays them in a grid of cells. And its way fewer lines than the UIKit version.
@Jxrgxn
Copy link
Author

Jxrgxn commented Mar 8, 2023

Re: UIKIT Code: This code defines a random number generator feature in an iOS app. It consists of three main components: the view controller, the presenter, and the debouncer.

The view controller conforms to a protocol named RandomNumberGeneratorViewControllerProtocol which defines two functions: showNumbers for displaying an array of integers and showError for displaying an error message. The view controller also sets up a UICollectionView to display the generated numbers and implements the necessary methods of the UICollectionViewDelegateFlowLayout.

The presenter conforms to a protocol named RandomNumberGeneratorPresenterProtocol which defines a function generateUniqueRandomNumbers for generating unique random numbers within a given range. The presenter also has a weak reference to a RandomNumberGeneratorViewControllerProtocol instance and uses a Debouncer to debounce the calls to the generateUniqueRandomNumbers function.

The Debouncer is a class that conforms to a protocol named DebouncerProtocol which defines a function call that can handle a closure with no parameters and no return value. The Debouncer class can be used to throttle or delay the execution of a closure. In this code, it immediately calls the closure after debouncing.

Overall, this code demonstrates a simple implementation of the Model-View-Presenter (MVP) architecture pattern, and uses dependency injection to decouple the components of the feature.

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