Last active
March 8, 2023 17:37
-
-
Save Jxrgxn/c7f2739aebb350a68d2b6cd8ae2e6dbf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.