Skip to content

Instantly share code, notes, and snippets.

@sergiocampama
Last active December 5, 2024 21:44
Show Gist options
  • Save sergiocampama/f8268e9dd36a4ee15606fa2a1846cfdb to your computer and use it in GitHub Desktop.
Save sergiocampama/f8268e9dd36a4ee15606fa2a1846cfdb to your computer and use it in GitHub Desktop.
Task.startOnMainActor Example
import UIKit
class KPView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
class KPButton: KPView {
var callback: (@Sendable @MainActor () async throws -> Void)?
var isEnabled = true
private(set) var isLoading = false
private var loadingTask: Task<Void, Never>?
private let loadingIndicator: UIActivityIndicatorView = {
let view = UIActivityIndicatorView(style: .medium)
view.hidesWhenStopped = true
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 8
backgroundColor = .red.withAlphaComponent(0.2)
addSubview(loadingIndicator)
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: 200, height: 40)
}
override func layoutSubviews() {
super.layoutSubviews()
loadingIndicator.sizeToFit()
loadingIndicator.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if
isEnabled,
!isLoading,
let callback,
touches.count == 1,
let touch = touches.first,
bounds.contains(touch.location(in: self))
{
isLoading = true
// Change this to force flickering of the loadingIndicator because of the
// startAnimating()/stopAnimating() calls in different RunLoop events.
let showFlicker = true
if showFlicker {
loadingTask = Task { [weak self] in
do {
try await callback()
self?.finishLoading()
} catch {
self?.finishLoading()
}
}
} else {
loadingTask = Task.startOnMainActor { [weak self] in
do {
try await callback()
self?.finishLoading()
} catch {
self?.finishLoading()
}
}
}
loadingTask = Task.startOnMainActor { [weak self] in
do {
try await callback()
self?.finishLoading()
} catch {
self?.finishLoading()
}
}
if isLoading {
loadingIndicator.startAnimating()
} else {
loadingTask = nil
}
}
}
private func finishLoading() {
isLoading = false
loadingIndicator.stopAnimating()
loadingTask = nil
}
}
class KPViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
class RootViewController: KPViewController {
private let button = KPButton()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(button)
// Does not ficker if using Task.startOnMainActor
button.callback = {
print("loading stuff")
}
// Uncomment to show a loading indicator on the "button".
// button.callback = {
// print("loading stuff")
// try await Task.sleep(for: .seconds(1))
// print("finished loading stuff")
// }
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.sizeToFit()
button.center = view.center
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment