I am sorry if this sounds very beginner, but after looking at Apple doc as well as several tutorials I still struggle to understand how Combine's Future works.
I have this very simple code which stores the current date on first button tap, and prints the interval on the second one:
import UIKit
class MyViewController: UIViewController {
private var startTime: Date = .now
private var completion: (TimeInterval) -> Void = { _ in }
private var isOn = false
@IBAction func tapped() {
if isOn {
completion(Date.now.timeIntervalSince(startTime))
} else {
startTime = .now
}
isOn.toggle()
}
init(_ completion: @escaping (TimeInterval) -> Void) {
super.init(nibName: "MyViewController", bundle: .main)
self.completion = completion
}
required init?(coder: NSCoder) {
fatalError()
}
}
In my AppDelegate:
window?.rootViewController = MyViewController {
print("Tapped with time interval: \($0)")
}
Now I would like to replace that completion handler with a Future
, but I'm confused about what to do. I guess I have to create a function like this in my view controller:
func afterSecondTap() -> Future<TimeInterval, Error> {
return Future { promise in
// what to do here?
}
}
And in the AppDelegate
something like this:
window?.rootViewController = MyViewController()
.afterSecondTap()
.sink(receiveCompletion: { completion in
}, receiveValue: { value in
})
.store(in: &subscriptions)
However this would not work because I get an error saying
Cannot assign value of type '()' to type 'UIViewController'
Thank you for helping me understand this
CodePudding user response:
There are two issues here.
First when you assign a rootViewController it expects UIViewController of some sort, as soon as you add .afterSecondTap()
and the rest it changes the type. So let's start like this
let controller = MyViewController()
window?.rootViewController = controller
Next let's see how we can publish time interval from the controller, I would think Future
is not what you need:
class MyViewController: UIViewController {
private var startTime: Date = .now
private var isOn = false
var timeInterval: AnyPublisher<TimeInterval, Never> {
_timeInterval
.compactMap { $0 }
.eraseToAnyPublisher()
}
private var _timeInterval = ConcurrentValueSubject<TimeInterval?, Never>(nil)
@IBAction func tapped() {
if isOn {
_timeInterval.send(Date.now.timeIntervalSince(startTime)
} else {
startTime = .now
}
isOn.toggle()
}
init() {
super.init(nibName: "MyViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError()
}
}
now you can do this:
let controller = MyViewController()
window?.rootViewController = controller
controller
.timeInterval
.sink(
receiveCompletion: { _ in },
receiveValue: { _ in })
.store(in: &subscriptions)
CodePudding user response:
When you store a Cancellable
using .store
, the value returned will no longer be UIViewController
, it is going to be Void
.
You shouldn't use Future
, instead use PassThroughSubject
like so:
class MyViewController: UIViewController {
private var startTime: Date = .now
private var isOn = false
var publisher = PassthroughSubject<TimeInterval, Never>()
@IBAction func tapped() {
if isOn {
publisher.send(Date.now.timeIntervalSince(startTime))
} else {
startTime = .now
}
isOn.toggle()
}
init() {
super.init(nibName: "MyViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError()
}
}
let viewController = MyViewController()
viewController
.publisher
.sink(receiveCompletion: { completion in
}, receiveValue: { value in
})
.store(in: &subscriptions)
.
window?.rootViewController = viewController