mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Warning for too small clickable element
commit_hash:de018d18bbd539f7a35949cbf0a3a18942a17d56
This commit is contained in:
@@ -11803,6 +11803,9 @@
|
||||
"client/ios/DivKit/Animators/ProgressInterpolator.swift":"divkit/public/client/ios/DivKit/Animators/ProgressInterpolator.swift",
|
||||
"client/ios/DivKit/Animators/ValueInterpolator.swift":"divkit/public/client/ios/DivKit/Animators/ValueInterpolator.swift",
|
||||
"client/ios/DivKit/Debug/Block+DebugInfo.swift":"divkit/public/client/ios/DivKit/Debug/Block+DebugInfo.swift",
|
||||
"client/ios/DivKit/Debug/DebugBlock+UIViewRenderableBlock.swift":"divkit/public/client/ios/DivKit/Debug/DebugBlock+UIViewRenderableBlock.swift",
|
||||
"client/ios/DivKit/Debug/DebugBlock.swift":"divkit/public/client/ios/DivKit/Debug/DebugBlock.swift",
|
||||
"client/ios/DivKit/Debug/DebugErrorCollector.swift":"divkit/public/client/ios/DivKit/Debug/DebugErrorCollector.swift",
|
||||
"client/ios/DivKit/Debug/DebugParams.swift":"divkit/public/client/ios/DivKit/Debug/DebugParams.swift",
|
||||
"client/ios/DivKit/Debug/ErrorListView.swift":"divkit/public/client/ios/DivKit/Debug/ErrorListView.swift",
|
||||
"client/ios/DivKit/DivAccessibilityElementsStorage.swift":"divkit/public/client/ios/DivKit/DivAccessibilityElementsStorage.swift",
|
||||
|
||||
@@ -7,77 +7,23 @@ import VGSL
|
||||
extension Block {
|
||||
func addingDebugInfo(context: DivBlockModelingContext) -> Block {
|
||||
let debugParams = context.debugParams
|
||||
guard debugParams.isDebugInfoEnabled else {
|
||||
guard debugParams.isDebugInfoEnabled,
|
||||
let debugErrorCollector = context.debugErrorCollector else {
|
||||
return self
|
||||
}
|
||||
|
||||
let errors = context.errorsStorage.errors
|
||||
let errorsCount = errors.count
|
||||
guard errorsCount > 0 else {
|
||||
return self
|
||||
}
|
||||
|
||||
let counterText = errorsCount > maxCount
|
||||
? "\(maxCount)+"
|
||||
: "\(errorsCount)"
|
||||
|
||||
let typo = Typo(font: context.fontProvider.font(size: 14))
|
||||
.with(color: .white)
|
||||
|
||||
let action = UserInterfaceAction(
|
||||
payload: .url(DebugInfoBlock.showOverlayURL),
|
||||
path: context.parentPath + "div_errors_indicator",
|
||||
accessibilityElement: nil
|
||||
)
|
||||
|
||||
let counter = TextBlock(
|
||||
widthTrait: .intrinsic,
|
||||
text: counterText.with(typo: typo)
|
||||
)
|
||||
|
||||
let indicator = counter
|
||||
.addingVerticalGaps(errorsButtonCounterGaps)
|
||||
.addingHorizontalGaps(calculateCounterHorizontalGaps(counter: counter))
|
||||
.addingDecorations(
|
||||
boundary: .clipCorner(radius: 10),
|
||||
backgroundColor: .red
|
||||
)
|
||||
.addingEdgeInsets(debugParams.errorCounterInsets)
|
||||
.addingEdgeGaps(2)
|
||||
.addingAccessibilityID(withTraits: (
|
||||
"divLayoutErrorCounter",
|
||||
.button
|
||||
))
|
||||
.addingDecorations(action: action)
|
||||
|
||||
let debugInfoBlock = DebugInfoBlock(
|
||||
child: indicator,
|
||||
showDebugInfo: {
|
||||
#if os(iOS)
|
||||
debugParams.showDebugInfo(ErrorListView(errors: errors.map(\.prettyMessage)))
|
||||
#else
|
||||
return
|
||||
#endif
|
||||
}
|
||||
let debugBlock = DebugBlock(
|
||||
errorCollector: debugErrorCollector,
|
||||
showDebugInfo: debugParams.showDebugInfo
|
||||
)
|
||||
|
||||
let block = LayeredBlock(
|
||||
widthTrait: calculatedWidthTrait,
|
||||
heightTrait: calculatedHeightTrait,
|
||||
children: [self, debugInfoBlock]
|
||||
verticalChildrenAlignment: .center,
|
||||
children: [self, debugBlock]
|
||||
)
|
||||
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateCounterHorizontalGaps(counter: Block) -> CGFloat {
|
||||
let additionalGap = (counter.intrinsicSize.height - counter.intrinsicSize.width) / 2
|
||||
return max(
|
||||
errorsButtonCounterGaps,
|
||||
errorsButtonCounterGaps + additionalGap
|
||||
)
|
||||
}
|
||||
|
||||
private let errorsButtonCounterGaps: CGFloat = 4
|
||||
private let maxCount = 9999
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import UIKit
|
||||
|
||||
import LayoutKit
|
||||
import VGSL
|
||||
|
||||
extension DebugBlock: UIViewRenderable {
|
||||
static func makeBlockView() -> any LayoutKit.BlockView {
|
||||
DebugBlockView()
|
||||
}
|
||||
|
||||
func canConfigureBlockView(_ view: any LayoutKit.BlockView) -> Bool {
|
||||
view is DebugBlockView
|
||||
}
|
||||
|
||||
func configureBlockView(
|
||||
_ view: BlockView,
|
||||
observer _: ElementStateObserver?,
|
||||
overscrollDelegate _: ScrollDelegate?,
|
||||
renderingDelegate _: RenderingDelegate?
|
||||
) {
|
||||
(view as? DebugBlockView)?.configure(
|
||||
errorCollector: errorCollector,
|
||||
showDebugInfo: showDebugInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private final class DebugBlockView: BlockView, VisibleBoundsTrackingContainer {
|
||||
let effectiveBackgroundColor: UIColor? = nil
|
||||
let visibleBoundsTrackingSubviews: [VisibleBoundsTrackingView] = []
|
||||
|
||||
private var showDebugInfo: ((ViewType) -> Void)?
|
||||
private var errorCollector: DebugErrorCollector?
|
||||
private let disposePool = AutodisposePool()
|
||||
|
||||
private let errorsLabel: UILabel = {
|
||||
let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(squareDimension: 50.0)))
|
||||
label.text = "0"
|
||||
label.numberOfLines = 1
|
||||
label.font = .systemFont(ofSize: 14)
|
||||
label.textColor = .white
|
||||
label.textAlignment = .center
|
||||
label.backgroundColor = .red
|
||||
label.isHidden = true
|
||||
return label
|
||||
}()
|
||||
|
||||
init() {
|
||||
super.init(frame: .zero)
|
||||
addSubview(errorsLabel)
|
||||
accessibilityIdentifier = "divLayoutErrorCounter"
|
||||
accessibilityTraits = .button
|
||||
clipsToBounds = true
|
||||
addGestureRecognizer(UITapGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(handleTapGesture(_:))
|
||||
))
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func configure(
|
||||
errorCollector: DebugErrorCollector,
|
||||
showDebugInfo: @escaping (ViewType) -> Void
|
||||
) {
|
||||
self.showDebugInfo = showDebugInfo
|
||||
if self.errorCollector !== errorCollector {
|
||||
self.errorCollector = errorCollector
|
||||
errorCollector.observableErrorCount.currentAndNewValues.addObserver { [weak self] _ in
|
||||
self?.updateCountLabel()
|
||||
}.dispose(in: disposePool)
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
errorsLabel.frame = bounds
|
||||
layer.cornerRadius = bounds.size.height / 2
|
||||
}
|
||||
|
||||
private func updateCountLabel() {
|
||||
let errorsCount = errorCollector?.totalErrorCount ?? 0
|
||||
errorsLabel.isHidden = errorsCount == 0
|
||||
errorsLabel.text = "\(min(maxCount, errorsCount))"
|
||||
}
|
||||
|
||||
@objc func handleTapGesture(_: UITapGestureRecognizer) {
|
||||
guard let showDebugInfo, let errorCollector, errorCollector.totalErrorCount > 0 else { return }
|
||||
showDebugInfo(ErrorListView(errors: errorCollector.errorList))
|
||||
}
|
||||
}
|
||||
|
||||
private let showOverlayURL = URL(string: "debugInfo://show")!
|
||||
private let maxCount = 9999
|
||||
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
|
||||
import LayoutKit
|
||||
import VGSL
|
||||
|
||||
final class DebugBlock: BlockWithTraits, LayoutCachingDefaultImpl {
|
||||
let widthTrait = LayoutTrait.fixed(buttonSize)
|
||||
let heightTrait = LayoutTrait.fixed(buttonSize)
|
||||
|
||||
var intrinsicContentWidth: CGFloat {
|
||||
widthTrait.fixedValue ?? 0.0
|
||||
}
|
||||
|
||||
func intrinsicContentHeight(forWidth _: CGFloat) -> CGFloat {
|
||||
heightTrait.fixedValue ?? 0.0
|
||||
}
|
||||
|
||||
let errorCollector: DebugErrorCollector
|
||||
let showDebugInfo: (ViewType) -> Void
|
||||
|
||||
init(
|
||||
errorCollector: DebugErrorCollector,
|
||||
showDebugInfo: @escaping (ViewType) -> Void
|
||||
) {
|
||||
self.errorCollector = errorCollector
|
||||
self.showDebugInfo = showDebugInfo
|
||||
}
|
||||
|
||||
func equals(_ other: any LayoutKit.Block) -> Bool {
|
||||
if self === other { return true }
|
||||
guard let other = other as? DebugBlock else { return false }
|
||||
return errorCollector === other.errorCollector
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"DebugBlock errors: \(errorCollector.debugDescription))"
|
||||
}
|
||||
|
||||
func getImageHolders() -> [any VGSLUI.ImageHolder] {
|
||||
[]
|
||||
}
|
||||
|
||||
func updated(withStates _: LayoutKit.BlocksState) throws -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
private let buttonSize = 50.0
|
||||
@@ -0,0 +1,50 @@
|
||||
import VGSLFundamentals
|
||||
|
||||
final class DebugErrorCollector: DivReporter {
|
||||
private let wrappedDivReporter: DivReporter
|
||||
|
||||
var errorStorage: DivErrorsStorage
|
||||
|
||||
private(set) var layoutErrors = [DivError]()
|
||||
let observableErrorCount = ObservableProperty<Int>(initialValue: 0)
|
||||
|
||||
init(
|
||||
wrappedDivReporter: DivReporter,
|
||||
errorStorage: DivErrorsStorage
|
||||
) {
|
||||
self.wrappedDivReporter = wrappedDivReporter
|
||||
self.errorStorage = errorStorage
|
||||
}
|
||||
|
||||
func reportError(cardId: DivCardID, error: DivError) {
|
||||
wrappedDivReporter.reportError(cardId: cardId, error: error)
|
||||
guard !hasError(error) else { return }
|
||||
layoutErrors.append(error)
|
||||
observableErrorCount.value += 1
|
||||
}
|
||||
|
||||
func reportAction(cardId: DivCardID, info: DivActionInfo) {
|
||||
wrappedDivReporter.reportAction(cardId: cardId, info: info)
|
||||
}
|
||||
|
||||
var totalErrorCount: Int {
|
||||
errorStorage.errors.count + layoutErrors.count
|
||||
}
|
||||
|
||||
var errorList: [String] {
|
||||
errorStorage.errors.map(\.prettyMessage) + layoutErrors.map(\.prettyMessage)
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
"Errors: \(errorList)"
|
||||
}
|
||||
|
||||
private func hasError(_ error: DivError) -> Bool {
|
||||
layoutErrors.contains {
|
||||
$0.kind == error.kind
|
||||
&& $0.message == error.message
|
||||
&& $0.path == error.path
|
||||
&& $0.level == error.level
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ public struct DivBlockModelingContext {
|
||||
let playerFactory: PlayerFactory?
|
||||
private(set) weak var parentScrollView: ScrollView?
|
||||
public private(set) var errorsStorage: DivErrorsStorage
|
||||
let debugErrorCollector: DebugErrorCollector?
|
||||
private let persistentValuesStorage: DivPersistentValuesStorage
|
||||
let tooltipViewFactory: DivTooltipViewFactory?
|
||||
let functionsStorage: DivFunctionsStorage?
|
||||
@@ -102,6 +103,7 @@ public struct DivBlockModelingContext {
|
||||
scheduler: scheduler,
|
||||
parentScrollView: parentScrollView,
|
||||
errorsStorage: errorsStorage,
|
||||
debugErrorCollector: nil,
|
||||
layoutDirection: layoutDirection,
|
||||
variableTracker: variableTracker,
|
||||
persistentValuesStorage: persistentValuesStorage,
|
||||
@@ -136,6 +138,7 @@ public struct DivBlockModelingContext {
|
||||
scheduler: Scheduling?,
|
||||
parentScrollView: ScrollView?,
|
||||
errorsStorage: DivErrorsStorage?,
|
||||
debugErrorCollector: DebugErrorCollector?,
|
||||
layoutDirection: UserInterfaceLayoutDirection,
|
||||
variableTracker: DivVariableTracker?,
|
||||
persistentValuesStorage: DivPersistentValuesStorage?,
|
||||
@@ -165,6 +168,7 @@ public struct DivBlockModelingContext {
|
||||
self.scheduler = scheduler ?? TimerScheduler()
|
||||
self.parentScrollView = parentScrollView
|
||||
let errorsStorage = errorsStorage ?? DivErrorsStorage(errors: [])
|
||||
self.debugErrorCollector = debugErrorCollector
|
||||
self.errorsStorage = errorsStorage
|
||||
self.layoutDirection = layoutDirection
|
||||
self.variableTracker = variableTracker
|
||||
|
||||
@@ -13,6 +13,7 @@ public protocol DivError: CustomStringConvertible {
|
||||
public enum DivErrorKind: String {
|
||||
case deserialization
|
||||
case blockModeling = "divModeling"
|
||||
case layout
|
||||
case expression
|
||||
case unknown
|
||||
}
|
||||
@@ -105,6 +106,32 @@ struct DivBlockModelingWarning: DivError {
|
||||
}
|
||||
}
|
||||
|
||||
struct DivLayoutError: DivError {
|
||||
public let kind = DivErrorKind.layout
|
||||
public let level = DivErrorLevel.error
|
||||
public let message: String
|
||||
public let path: UIElementPath
|
||||
|
||||
init(_ message: String, path: UIElementPath) {
|
||||
self.message = message
|
||||
self.path = path
|
||||
DivKitLogger.error(description)
|
||||
}
|
||||
}
|
||||
|
||||
struct DivLayoutWarning: DivError {
|
||||
public let kind = DivErrorKind.layout
|
||||
public let level = DivErrorLevel.warning
|
||||
public let message: String
|
||||
public let path: UIElementPath
|
||||
|
||||
init(_ message: String, path: UIElementPath) {
|
||||
self.message = message
|
||||
self.path = path
|
||||
DivKitLogger.warning(description)
|
||||
}
|
||||
}
|
||||
|
||||
struct DivExpressionError: Error, DivError {
|
||||
public let kind = DivErrorKind.expression
|
||||
public let message: String
|
||||
|
||||
@@ -50,6 +50,7 @@ public final class DivKitComponents {
|
||||
private let variableTracker = DivVariableTracker()
|
||||
private let idToPath = IdToPath()
|
||||
private let animatorController = DivAnimatorController()
|
||||
private var debugErrorCollectors = [DivCardID: DebugErrorCollector]()
|
||||
|
||||
/// You can create an instance of `DivKitComponents` with various optional parameters that allow
|
||||
/// you to customize the behavior and functionality of `DivKit` to suit your specific needs.
|
||||
@@ -231,6 +232,7 @@ public final class DivKitComponents {
|
||||
tooltipManager.reset()
|
||||
idToPath.reset()
|
||||
animatorController.reset()
|
||||
debugErrorCollectors = [:]
|
||||
}
|
||||
|
||||
public func reset(cardId: DivCardID) {
|
||||
@@ -244,6 +246,7 @@ public final class DivKitComponents {
|
||||
timerStorage.reset(cardId: cardId)
|
||||
idToPath.reset(cardId: cardId)
|
||||
animatorController.reset(cardId: cardId)
|
||||
debugErrorCollectors[cardId] = nil
|
||||
}
|
||||
|
||||
/// When using DivView, use DivData.resolve to avoid adding variables twice.
|
||||
@@ -313,6 +316,7 @@ public final class DivKitComponents {
|
||||
? DivStateManager()
|
||||
: stateManagement.getStateManagerForCard(cardId: cardId)
|
||||
|
||||
let errorsStorage = DivErrorsStorage(errors: [])
|
||||
return DivBlockModelingContext(
|
||||
viewId: viewId,
|
||||
cardLogId: nil,
|
||||
@@ -337,7 +341,12 @@ public final class DivKitComponents {
|
||||
debugParams: debugParams,
|
||||
scheduler: nil,
|
||||
parentScrollView: parentScrollView,
|
||||
errorsStorage: nil,
|
||||
errorsStorage: errorsStorage,
|
||||
debugErrorCollector: debugErrorCollector(
|
||||
for: cardId,
|
||||
debugParams: debugParams,
|
||||
errorsStorage: errorsStorage
|
||||
),
|
||||
layoutDirection: layoutDirection,
|
||||
variableTracker: variableTracker,
|
||||
persistentValuesStorage: persistentValuesStorage,
|
||||
@@ -379,6 +388,28 @@ public final class DivKitComponents {
|
||||
updateAggregator.forceUpdate()
|
||||
}
|
||||
|
||||
@_spi(Internal)
|
||||
public func renderingDelegate(for cardId: DivCardID) -> RenderingDelegate {
|
||||
ErrorsReportingRenderingDelegate(
|
||||
wrappedRenderingDelegate: tooltipManager,
|
||||
cardId: cardId,
|
||||
divReporter: debugErrorCollectors[cardId]
|
||||
)
|
||||
}
|
||||
|
||||
private func debugErrorCollector(
|
||||
for cardId: DivCardID,
|
||||
debugParams: DebugParams,
|
||||
errorsStorage: DivErrorsStorage
|
||||
) -> DebugErrorCollector? {
|
||||
guard debugParams.isDebugInfoEnabled else { return nil }
|
||||
let collector = debugErrorCollectors.getOrCreate(cardId, factory: {
|
||||
DebugErrorCollector(wrappedDivReporter: reporter, errorStorage: errorsStorage)
|
||||
})
|
||||
collector.errorStorage = errorsStorage
|
||||
return collector
|
||||
}
|
||||
|
||||
private func onVariablesChanged(event: DivVariablesStorage.ChangeEvent) {
|
||||
switch event.kind {
|
||||
case let .global(variables):
|
||||
@@ -397,3 +428,47 @@ let defaultPlayerFactory: PlayerFactory? = DefaultPlayerFactory()
|
||||
#else
|
||||
let defaultPlayerFactory: PlayerFactory? = nil
|
||||
#endif
|
||||
|
||||
private final class ErrorsReportingRenderingDelegate: RenderingDelegate {
|
||||
private let wrappedRenderingDelegate: RenderingDelegate
|
||||
private let cardId: DivCardID
|
||||
private let divReporter: DivReporter?
|
||||
|
||||
init(
|
||||
wrappedRenderingDelegate: RenderingDelegate,
|
||||
cardId: DivCardID,
|
||||
divReporter: DivReporter?
|
||||
) {
|
||||
self.wrappedRenderingDelegate = wrappedRenderingDelegate
|
||||
self.cardId = cardId
|
||||
self.divReporter = divReporter
|
||||
}
|
||||
|
||||
func reportRenderingError(message: String, isWarning: Bool, path: UIElementPath) {
|
||||
wrappedRenderingDelegate.reportRenderingError(
|
||||
message: message,
|
||||
isWarning: isWarning,
|
||||
path: path
|
||||
)
|
||||
guard let divReporter else { return }
|
||||
let error: DivError = if isWarning {
|
||||
DivLayoutWarning(message, path: path)
|
||||
}
|
||||
else {
|
||||
DivLayoutError(message, path: path)
|
||||
}
|
||||
divReporter.reportError(cardId: cardId, error: error)
|
||||
}
|
||||
|
||||
func mapView(_ view: any LayoutKit.BlockView, to id: LayoutKit.BlockViewID) {
|
||||
wrappedRenderingDelegate.mapView(view, to: id)
|
||||
}
|
||||
|
||||
func tooltipAnchorViewAdded(anchorView: any LayoutKit.TooltipAnchorView) {
|
||||
wrappedRenderingDelegate.tooltipAnchorViewAdded(anchorView: anchorView)
|
||||
}
|
||||
|
||||
func tooltipAnchorViewRemoved(anchorView: any LayoutKit.TooltipAnchorView) {
|
||||
wrappedRenderingDelegate.tooltipAnchorViewRemoved(anchorView: anchorView)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ extension Block {
|
||||
actionAnimation: actionsHolder.actionAnimation
|
||||
.resolveActionAnimation(context.expressionResolver),
|
||||
doubleTapActions: doubletapActions,
|
||||
longTapActions: longtapActions
|
||||
longTapActions: longtapActions,
|
||||
path: context.parentPath
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,17 +180,23 @@ public final class DivView: VisibleBoundsTrackingView {
|
||||
}
|
||||
|
||||
private func update(block: Block) {
|
||||
let renderingDelegate: RenderingDelegate?
|
||||
if blockProvider?.id != nil, let divCardId = blockProvider?.cardId {
|
||||
renderingDelegate = divKitComponents.renderingDelegate(for: divCardId)
|
||||
} else {
|
||||
renderingDelegate = nil
|
||||
}
|
||||
if let blockView, block.canConfigureBlockView(blockView) {
|
||||
block.configureBlockView(
|
||||
blockView,
|
||||
observer: self,
|
||||
overscrollDelegate: nil,
|
||||
renderingDelegate: divKitComponents.tooltipManager
|
||||
renderingDelegate: renderingDelegate
|
||||
)
|
||||
} else {
|
||||
blockView = block.makeBlockView(
|
||||
observer: self,
|
||||
renderingDelegate: divKitComponents.tooltipManager
|
||||
renderingDelegate: renderingDelegate
|
||||
)
|
||||
}
|
||||
invalidateIntrinsicContentSize()
|
||||
|
||||
@@ -28,7 +28,8 @@ extension Block {
|
||||
tooltips: [BlockTooltip]? = nil,
|
||||
forceWrapping: Bool,
|
||||
accessibilityElement: AccessibilityElement? = nil,
|
||||
reuseId: String?
|
||||
reuseId: String?,
|
||||
path: UIElementPath?
|
||||
) -> Block {
|
||||
let hasAlpha = alpha != nil && alpha?.isApproximatelyEqualTo(1) != true
|
||||
let anythingToApplyExceptBoundary =
|
||||
@@ -72,7 +73,8 @@ extension Block {
|
||||
visibilityParams: visibilityParams ?? block.visibilityParams,
|
||||
tooltips: [tooltips, block.tooltips].compactMap { $0 }.flatMap { $0 },
|
||||
accessibilityElement: accessibilityElement,
|
||||
reuseId: reuseId
|
||||
reuseId: reuseId,
|
||||
path: path
|
||||
)
|
||||
}
|
||||
|
||||
@@ -97,7 +99,8 @@ extension Block {
|
||||
visibilityParams: visibilityParams,
|
||||
tooltips: tooltips ?? [],
|
||||
accessibilityElement: accessibilityElement,
|
||||
reuseId: reuseId
|
||||
reuseId: reuseId,
|
||||
path: path
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,7 +129,8 @@ extension Block {
|
||||
tooltips: [BlockTooltip]? = nil,
|
||||
forceWrapping: Bool = false,
|
||||
accessibilityElement: AccessibilityElement? = nil,
|
||||
reuseId: String? = nil
|
||||
reuseId: String? = nil,
|
||||
path: UIElementPath? = nil
|
||||
) -> Block {
|
||||
let decoratedBlock = applyDecoratingBlockProperties(
|
||||
boundary: boundary,
|
||||
@@ -144,7 +148,8 @@ extension Block {
|
||||
tooltips: tooltips,
|
||||
forceWrapping: forceWrapping,
|
||||
accessibilityElement: accessibilityElement,
|
||||
reuseId: reuseId
|
||||
reuseId: reuseId,
|
||||
path: path
|
||||
)
|
||||
return decoratedBlock.shaded(with: shadow)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ final class DecoratingBlock: WrapperBlock {
|
||||
let tooltips: [BlockTooltip]
|
||||
let accessibilityElement: AccessibilityElement?
|
||||
let reuseId: String
|
||||
let path: UIElementPath?
|
||||
|
||||
init(
|
||||
child: Block,
|
||||
@@ -44,7 +45,8 @@ final class DecoratingBlock: WrapperBlock {
|
||||
visibilityParams: VisibilityParams? = nil,
|
||||
tooltips: [BlockTooltip] = [],
|
||||
accessibilityElement: AccessibilityElement? = nil,
|
||||
reuseId: String? = nil
|
||||
reuseId: String? = nil,
|
||||
path: UIElementPath? = nil
|
||||
) {
|
||||
self.child = child
|
||||
self.backgroundColor = backgroundColor
|
||||
@@ -63,6 +65,7 @@ final class DecoratingBlock: WrapperBlock {
|
||||
self.tooltips = tooltips
|
||||
self.accessibilityElement = accessibilityElement
|
||||
self.reuseId = reuseId ?? DecoratingBlock.defaultReuseId
|
||||
self.path = path
|
||||
}
|
||||
|
||||
var intrinsicContentWidth: CGFloat {
|
||||
@@ -148,7 +151,8 @@ extension DecoratingBlock {
|
||||
visibilityParams: VisibilityParams? = nil,
|
||||
tooltips: [BlockTooltip]? = nil,
|
||||
accessibilityElement: AccessibilityElement? = nil,
|
||||
reuseId: String? = nil
|
||||
reuseId: String? = nil,
|
||||
path: UIElementPath? = nil
|
||||
) -> DecoratingBlock {
|
||||
DecoratingBlock(
|
||||
child: child ?? self.child,
|
||||
@@ -167,7 +171,8 @@ extension DecoratingBlock {
|
||||
visibilityParams: visibilityParams ?? self.visibilityParams,
|
||||
tooltips: tooltips ?? self.tooltips,
|
||||
accessibilityElement: accessibilityElement ?? self.accessibilityElement,
|
||||
reuseId: reuseId ?? self.reuseId
|
||||
reuseId: reuseId ?? self.reuseId,
|
||||
path: path ?? self.path
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -48,7 +48,8 @@ extension DecoratingBlock {
|
||||
visibilityParams: visibilityParams,
|
||||
tooltips: tooltips,
|
||||
accessibility: accessibilityElement,
|
||||
reuseId: reuseId
|
||||
reuseId: reuseId,
|
||||
path: path
|
||||
)
|
||||
view.configure(
|
||||
model: model,
|
||||
@@ -122,6 +123,7 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
|
||||
let tooltips: [BlockTooltip]
|
||||
let accessibility: AccessibilityElement?
|
||||
let reuseId: String?
|
||||
let path: UIElementPath?
|
||||
|
||||
var hasResponsiveUI: Bool {
|
||||
actions.hasPayload || longTapActions.hasPayload || doubleTapActions.hasPayload
|
||||
@@ -257,6 +259,18 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
|
||||
doubleTapRecognizer?.isEnabled = model.shouldHandleDoubleTap
|
||||
longPressRecognizer?.isEnabled = model.shouldHandleLongTap
|
||||
}
|
||||
|
||||
private func checkTouchableArea() {
|
||||
guard tapRecognizer != nil || doubleTapRecognizer != nil || longPressRecognizer != nil else { return }
|
||||
guard !bounds.size.isApproximatelyEqualTo(.zero) else { return }
|
||||
if bounds.width < 44 || bounds.height < 44 {
|
||||
renderingDelegate?.reportRenderingError(
|
||||
message: "Touchable view is too small: \(bounds.size), \(model.child)",
|
||||
isWarning: true,
|
||||
path: model.path ?? UIElementPath("")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
@@ -321,6 +335,8 @@ private final class DecoratingView: UIControl, BlockViewProtocol, VisibleBoundsT
|
||||
}
|
||||
|
||||
blurView?.frame = bounds
|
||||
|
||||
checkTouchableArea()
|
||||
|
||||
guard let view = childView else { return }
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ public protocol RenderingDelegate: AnyObject {
|
||||
func mapView(_ view: BlockView, to id: BlockViewID)
|
||||
func tooltipAnchorViewAdded(anchorView: TooltipAnchorView)
|
||||
func tooltipAnchorViewRemoved(anchorView: TooltipAnchorView)
|
||||
func reportRenderingError(message: String, isWarning: Bool, path: UIElementPath)
|
||||
}
|
||||
|
||||
public typealias BlockViewID = Tagged<BlockViewProtocol, String>
|
||||
@@ -18,6 +19,10 @@ public protocol DivViewMetaProviding: AnyObject {
|
||||
public protocol TooltipAnchorView: ViewType {
|
||||
var tooltips: [BlockTooltip] { get }
|
||||
}
|
||||
|
||||
extension RenderingDelegate {
|
||||
public func reportRenderingError(message: String, isWarning: Bool, path: UIElementPath) { }
|
||||
}
|
||||
#else
|
||||
public protocol RenderingDelegate {}
|
||||
public typealias BlockViewID = Tagged<AnyObject, String>
|
||||
|
||||
@@ -68,6 +68,10 @@ private final class SubviewStorage: RenderingDelegate {
|
||||
func tooltipAnchorViewRemoved(anchorView: TooltipAnchorView) {
|
||||
wrappedRenderingDelegate?.tooltipAnchorViewRemoved(anchorView: anchorView)
|
||||
}
|
||||
|
||||
func reportRenderingError(message: String, isWarning: Bool, path: UIElementPath) {
|
||||
wrappedRenderingDelegate?.reportRenderingError(message: message, isWarning: isWarning, path: path)
|
||||
}
|
||||
|
||||
func getView(_ id: BlockViewID) -> DetachableAnimationBlockView? {
|
||||
views.first { $0.id == id }?.view
|
||||
|
||||
Reference in New Issue
Block a user