Last active
December 5, 2024 21:44
-
-
Save sergiocampama/f8268e9dd36a4ee15606fa2a1846cfdb to your computer and use it in GitHub Desktop.
Task.startOnMainActor Example
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 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