You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently, the update process for elements to view in BlueprintView is asynchronous.
Specifically, when element is set, there is a call to setNeedsViewHierachyUpdate which sets a flag that an update must happen, then a call to setNeedsLayout where the update will happen on the next layout pass.
This has the side effect of potentially losing events if the previous closure bound to an element/view (like a text field, for instance) changes or is invalidated after being received.
UIKit will queue the events, so only send one per runloop pass, however there is a gap between the first being handled and the closure being updated (since it does not updated until the next layout pass has completed). Using Blueprint with Workflows can easily produce this with very fast input to text fields (eg: with a KIF test, but can be reproduced with a keyboard). Since the sink (event handler) in workflows is only valid for a single event in a single render pass, the behavior seen is a crash (or would be dropped events if it was not asserting) because of the gap in updates.
The naive "fix" for this would be to change BlueprintView's didSet on element to update the hierarchy, ie:
/// The root element that is displayed within the view.
public var element: Element? {
didSet {
setNeedsViewHierarchyUpdate()
+ // Immediately update the hierarchy when element is set, instead of waiting for the layout pass
+ updateViewHierarchyIfNeeded()
}
}
This is the naive fix, as blueprint should not support reentrant updates, so will likely need a bit of exploration to determine a "safe" way to make this update be synchronous.
And example view controller that reproduces what the behavior would be when used with Workflows: (a sink that invalidates after every update):
import UIKit
import BlueprintUI
import BlueprintUICommonControls
public final class SinkBackedBlueprintViewController: UIViewController {
private class Sink<Value> {
var valid = true
var onEvent: (Value) -> Void
init(onEvent: @escaping (Value) -> Void) {
self.onEvent = onEvent
}
func send(event: Value) {
if !valid {
fatalError("Old sink")
}
self.onEvent(event)
invalidate()
}
func invalidate() {
valid = false
}
}
private let blueprintView: BlueprintView
private var text: String = ""
private var sink: Sink<String>
public init() {
self.blueprintView = BlueprintView(frame: .zero)
self.sink = Sink(onEvent: { _ in })
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(blueprintView)
update(text: "")
}
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
blueprintView.frame = view.bounds
}
func update(text: String) {
self.text = text
generate()
}
func generate() {
var textField = TextField(text: text)
let sink = Sink<String>(onEvent: { [weak self] updated in
self?.update(text: updated)
})
textField.onChange = { [sink] updated in
sink.send(event: updated)
}
let label = AccessibilityElement(label: "email", value: nil, hint: nil, traits: [], wrapping: textField)
blueprintView.element = Column { col in
col.horizontalAlignment = .fill
col.minimumVerticalSpacing = 8.0
col.add(child: Box(backgroundColor: .green, cornerStyle: Box.CornerStyle.square, wrapping: nil))
col.add(
child: Box(
backgroundColor: .red,
cornerStyle: Box.CornerStyle.square,
wrapping: label))
col.add(child: Box(backgroundColor: .green, cornerStyle: Box.CornerStyle.square, wrapping: nil))
}
}
}
The text was updated successfully, but these errors were encountered:
Currently, the update process for elements to view in
BlueprintView
is asynchronous.Specifically, when
element
is set, there is a call tosetNeedsViewHierachyUpdate
which sets a flag that an update must happen, then a call tosetNeedsLayout
where the update will happen on the next layout pass.This has the side effect of potentially losing events if the previous closure bound to an element/view (like a text field, for instance) changes or is invalidated after being received.
UIKit will queue the events, so only send one per runloop pass, however there is a gap between the first being handled and the closure being updated (since it does not updated until the next layout pass has completed). Using Blueprint with Workflows can easily produce this with very fast input to text fields (eg: with a KIF test, but can be reproduced with a keyboard). Since the sink (event handler) in workflows is only valid for a single event in a single render pass, the behavior seen is a crash (or would be dropped events if it was not asserting) because of the gap in updates.
The naive "fix" for this would be to change
BlueprintView
's didSet onelement
to update the hierarchy, ie:This is the naive fix, as blueprint should not support reentrant updates, so will likely need a bit of exploration to determine a "safe" way to make this update be synchronous.
And example view controller that reproduces what the behavior would be when used with Workflows: (a sink that invalidates after every update):
The text was updated successfully, but these errors were encountered: