Fix reader interaction and web resource handling
This commit is contained in:
parent
22e7e44220
commit
99bdc98895
@ -156,6 +156,7 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
|
|||||||
}
|
}
|
||||||
Self.recordInMemoryResponse()
|
Self.recordInMemoryResponse()
|
||||||
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "finished")
|
RDEPUBWebViewDebug.logSchemeTask("ResourceScheme", requestURL: requestURL, fileURL: fileURL, event: "finished")
|
||||||
|
return
|
||||||
} catch {
|
} catch {
|
||||||
guard self.isTaskActive(taskID) else {
|
guard self.isTaskActive(taskID) else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -207,14 +208,8 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
let chunkSize = 65_536
|
let chunkSize = 65_536
|
||||||
var streamingCompleted = false
|
|
||||||
defer {
|
defer {
|
||||||
fileHandle.closeFile()
|
fileHandle.closeFile()
|
||||||
if !streamingCompleted {
|
|
||||||
// Task was cancelled during streaming; didFailWithError already sent above
|
|
||||||
// or will be sent by the guard check below. No need to send again.
|
|
||||||
}
|
|
||||||
self.clearTask(taskID)
|
|
||||||
}
|
}
|
||||||
while true {
|
while true {
|
||||||
guard self.isTaskActive(taskID) else {
|
guard self.isTaskActive(taskID) else {
|
||||||
@ -222,6 +217,7 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
|
urlSchemeTask.didFailWithError(NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
|
||||||
}
|
}
|
||||||
|
self.clearTask(taskID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let data = fileHandle.readData(ofLength: chunkSize)
|
let data = fileHandle.readData(ofLength: chunkSize)
|
||||||
@ -230,7 +226,6 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
|
|||||||
urlSchemeTask.didReceive(data)
|
urlSchemeTask.didReceive(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamingCompleted = true
|
|
||||||
self.dispatchTaskSuccessCallback(
|
self.dispatchTaskSuccessCallback(
|
||||||
taskID: taskID,
|
taskID: taskID,
|
||||||
urlSchemeTask: urlSchemeTask
|
urlSchemeTask: urlSchemeTask
|
||||||
@ -265,6 +260,7 @@ public final class RDEPUBResourceURLSchemeHandler: NSObject, WKURLSchemeHandler
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
callback()
|
callback()
|
||||||
|
self.clearTask(taskID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,10 @@ extension RDEPUBReaderController: RDEPUBWebContentViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard readerView.pageContentView(pageNum: readerView.currentPage) === contentView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let currentPage = activePages[readerView.currentPage]
|
let currentPage = activePages[readerView.currentPage]
|
||||||
guard readingSession?.pageContains(spineIndex: spineIndex, in: currentPage) == true else {
|
guard readingSession?.pageContains(spineIndex: spineIndex, in: currentPage) == true else {
|
||||||
return
|
return
|
||||||
@ -89,6 +93,10 @@ extension RDEPUBReaderController: RDEPUBTextContentViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func textContentView(_ contentView: RDEPUBTextContentView, didRequestReaderTapAt point: CGPoint) {
|
func textContentView(_ contentView: RDEPUBTextContentView, didRequestReaderTapAt point: CGPoint) {
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderController.textContentTap",
|
||||||
|
"delegate received reader tap from contentView=\(RDReaderTapDebug.describe(contentView)) point=\(RDReaderTapDebug.describe(point)) currentPage=\(readerView.currentPage)"
|
||||||
|
)
|
||||||
readerView.handleContentTap(at: point, in: contentView)
|
readerView.handleContentTap(at: point, in: contentView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -219,9 +219,16 @@ final class RDEPUBTextContentInteractionCoordinator: NSObject {
|
|||||||
private func updateSelectionInteractionState(_ state: SelectionInteractionState) {
|
private func updateSelectionInteractionState(_ state: SelectionInteractionState) {
|
||||||
let previousTapSuppressed = interactionState != .idle
|
let previousTapSuppressed = interactionState != .idle
|
||||||
let previousPagingSuppressed = shouldSuppressPagingInteraction(for: interactionState)
|
let previousPagingSuppressed = shouldSuppressPagingInteraction(for: interactionState)
|
||||||
|
let previousState = interactionState
|
||||||
interactionState = state
|
interactionState = state
|
||||||
let currentTapSuppressed = interactionState != .idle
|
let currentTapSuppressed = interactionState != .idle
|
||||||
let currentPagingSuppressed = shouldSuppressPagingInteraction(for: interactionState)
|
let currentPagingSuppressed = shouldSuppressPagingInteraction(for: interactionState)
|
||||||
|
if previousState != state {
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"TextContentInteraction.state",
|
||||||
|
"transition \(previousState) -> \(state) tapSuppressed=\(currentTapSuppressed) pagingSuppressed=\(currentPagingSuppressed)"
|
||||||
|
)
|
||||||
|
}
|
||||||
if previousTapSuppressed != currentTapSuppressed {
|
if previousTapSuppressed != currentTapSuppressed {
|
||||||
dependencies.selectionTapSuppressionDidChange(currentTapSuppressed)
|
dependencies.selectionTapSuppressionDidChange(currentTapSuppressed)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -727,6 +727,10 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
|| interactionCoordinator.interactionState != .idle
|
|| interactionCoordinator.interactionState != .idle
|
||||||
guard isUserInteractionEnabled != shouldEnableInteraction else { return }
|
guard isUserInteractionEnabled != shouldEnableInteraction else { return }
|
||||||
isUserInteractionEnabled = shouldEnableInteraction
|
isUserInteractionEnabled = shouldEnableInteraction
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"TextContentView.interactionAvailability",
|
||||||
|
"updated isUserInteractionEnabled=\(shouldEnableInteraction) currentPage=\(currentPage?.absolutePageIndex ?? -1) loading=\(loadingSpinner.isAnimating) selecting=\(selectionController.isSelecting) hasSelection=\(selectionController.hasActiveSelection) state=\(interactionCoordinator.interactionState)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateStaticGestureAvailability() {
|
private func updateStaticGestureAvailability() {
|
||||||
@ -737,24 +741,36 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
guard longPressChanged || tapChanged else { return }
|
guard longPressChanged || tapChanged else { return }
|
||||||
longPressGestureRecognizer.isEnabled = shouldEnableLongPress
|
longPressGestureRecognizer.isEnabled = shouldEnableLongPress
|
||||||
tapGestureRecognizer.isEnabled = shouldEnableTap
|
tapGestureRecognizer.isEnabled = shouldEnableTap
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"TextContentView.gestureAvailability",
|
||||||
|
"updated longPressEnabled=\(shouldEnableLongPress) tapEnabled=\(shouldEnableTap) hasInteractiveTextContent=\(hasInteractiveTextContent) loading=\(loadingSpinner.isAnimating) currentPage=\(currentPage?.absolutePageIndex ?? -1)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
|
||||||
let point = gesture.location(in: overlayView)
|
let point = gesture.location(in: overlayView)
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"TextContentView.handleTap",
|
||||||
|
"received point=\(RDReaderTapDebug.describe(point)) currentPage=\(currentPage?.absolutePageIndex ?? -1) selectionState=\(interactionCoordinator.interactionState) hasSelection=\(currentSelection != nil) loading=\(loadingSpinner.isAnimating)"
|
||||||
|
)
|
||||||
if let renderView = coreTextRenderView {
|
if let renderView = coreTextRenderView {
|
||||||
let renderPoint = gesture.location(in: renderView)
|
let renderPoint = gesture.location(in: renderView)
|
||||||
if renderView.selectionHandle(at: renderPoint) != nil {
|
if renderView.selectionHandle(at: renderPoint) != nil {
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "ignored because tap hit selection handle")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if currentSelection != nil {
|
if currentSelection != nil {
|
||||||
if renderView.selectionContains(renderPoint) {
|
if renderView.selectionContains(renderPoint) {
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "selection exists and tap stayed inside selection; showing selection menu")
|
||||||
showSelectionMenuIfNeeded()
|
showSelectionMenuIfNeeded()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "selection exists and tap moved outside selection; clearing selection")
|
||||||
clearSelection()
|
clearSelection()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if currentSelection != nil {
|
} else if currentSelection != nil {
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "selection exists without renderView; clearing selection")
|
||||||
clearSelection()
|
clearSelection()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -766,6 +782,7 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
// Footnote attachments carry their note text in alt/accessibility metadata.
|
// Footnote attachments carry their note text in alt/accessibility metadata.
|
||||||
// Prefer that semantic text over image preview even if attachment kind metadata is incomplete.
|
// Prefer that semantic text over image preview even if attachment kind metadata is incomplete.
|
||||||
if let footnoteText = attachmentText(at: point), kind == .footnote || !footnoteText.isEmpty {
|
if let footnoteText = attachmentText(at: point), kind == .footnote || !footnoteText.isEmpty {
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "resolved footnote attachment tap")
|
||||||
delegate?.textContentView(
|
delegate?.textContentView(
|
||||||
self,
|
self,
|
||||||
didActivateAttachmentText: footnoteText,
|
didActivateAttachmentText: footnoteText,
|
||||||
@ -777,12 +794,14 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
// Regular image attachments: present image viewer
|
// Regular image attachments: present image viewer
|
||||||
if let image = imageFromPage(attachment: attachment, page: page) {
|
if let image = imageFromPage(attachment: attachment, page: page) {
|
||||||
let altText = attachmentText(at: point)
|
let altText = attachmentText(at: point)
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "resolved image attachment tap altTextPresent=\(altText?.isEmpty == false)")
|
||||||
delegate?.textContentView(self, didActivateImage: image, sourceRect: sourceRect, altText: altText)
|
delegate?.textContentView(self, didActivateImage: image, sourceRect: sourceRect, altText: altText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let attachmentText = attachmentText(at: point),
|
if let attachmentText = attachmentText(at: point),
|
||||||
let sourceRect = attachmentSourceRect(at: point, fallbackPoint: point) {
|
let sourceRect = attachmentSourceRect(at: point, fallbackPoint: point) {
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "resolved fallback attachment text tap")
|
||||||
delegate?.textContentView(
|
delegate?.textContentView(
|
||||||
self,
|
self,
|
||||||
didActivateAttachmentText: attachmentText,
|
didActivateAttachmentText: attachmentText,
|
||||||
@ -793,9 +812,11 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
}
|
}
|
||||||
guard let highlight = highlight(at: point),
|
guard let highlight = highlight(at: point),
|
||||||
let sourceRect = highlightSourceRect(for: highlight, fallbackPoint: point) else {
|
let sourceRect = highlightSourceRect(for: highlight, fallbackPoint: point) else {
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "forwarding plain reader tap to delegate")
|
||||||
delegate?.textContentView(self, didRequestReaderTapAt: convert(point, from: overlayView))
|
delegate?.textContentView(self, didRequestReaderTapAt: convert(point, from: overlayView))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
RDReaderTapDebug.log("TextContentView.handleTap", "resolved highlight tap highlightId=\(highlight.id)")
|
||||||
delegate?.textContentView(self, didRequestHighlightActions: highlight, sourceRect: sourceRect)
|
delegate?.textContentView(self, didRequestHighlightActions: highlight, sourceRect: sourceRect)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,26 +956,36 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
func shouldSuppressReaderTap(at point: CGPoint) -> Bool {
|
func shouldSuppressReaderTap(at point: CGPoint) -> Bool {
|
||||||
#if canImport(DTCoreText)
|
#if canImport(DTCoreText)
|
||||||
if interactionCoordinator.interactionState == .selectionPending {
|
if interactionCoordinator.interactionState == .selectionPending {
|
||||||
|
RDReaderTapDebug.log("TextContentView.shouldSuppressReaderTap", "return true because interactionState is selectionPending")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let renderView = coreTextRenderView else {
|
guard let renderView = coreTextRenderView else {
|
||||||
|
RDReaderTapDebug.log("TextContentView.shouldSuppressReaderTap", "return false because renderView is nil")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let renderPoint = convert(point, to: renderView)
|
let renderPoint = convert(point, to: renderView)
|
||||||
if renderView.selectionHandle(at: renderPoint) != nil {
|
if renderView.selectionHandle(at: renderPoint) != nil {
|
||||||
|
RDReaderTapDebug.log("TextContentView.shouldSuppressReaderTap", "return true because tap hit selection handle")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if selectionController.hasActiveSelection, renderView.selectionContains(renderPoint) {
|
if selectionController.hasActiveSelection, renderView.selectionContains(renderPoint) {
|
||||||
|
RDReaderTapDebug.log("TextContentView.shouldSuppressReaderTap", "return true because tap is inside active selection")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch interactionCoordinator.interactionState {
|
switch interactionCoordinator.interactionState {
|
||||||
case .idle:
|
case .idle:
|
||||||
|
RDReaderTapDebug.log("TextContentView.shouldSuppressReaderTap", "return false because interactionState is idle")
|
||||||
return false
|
return false
|
||||||
case .selectionPending, .selecting, .selectionActive, .adjustingHandle:
|
case .selectionPending, .selecting, .selectionActive, .adjustingHandle:
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"TextContentView.shouldSuppressReaderTap",
|
||||||
|
"return true because interactionState=\(interactionCoordinator.interactionState)"
|
||||||
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
RDReaderTapDebug.log("TextContentView.shouldSuppressReaderTap", "return false because DTCoreText is unavailable")
|
||||||
return false
|
return false
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -1003,16 +1034,23 @@ final class RDEPUBTextContentView: UIView, UIGestureRecognizerDelegate, RDReader
|
|||||||
) -> Bool {
|
) -> Bool {
|
||||||
if gestureRecognizer === tapGestureRecognizer {
|
if gestureRecognizer === tapGestureRecognizer {
|
||||||
guard let renderView = coreTextRenderView else {
|
guard let renderView = coreTextRenderView else {
|
||||||
|
RDReaderTapDebug.log("TextContentView.gestureShouldReceive", "return true because renderView is nil")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let point = touch.location(in: renderView)
|
let point = touch.location(in: renderView)
|
||||||
if renderView.selectionHandle(at: point) != nil {
|
if renderView.selectionHandle(at: point) != nil {
|
||||||
|
RDReaderTapDebug.log("TextContentView.gestureShouldReceive", "return false because tap hit selection handle")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return interactionCoordinator.gestureRecognizer(gestureRecognizer, shouldReceive: touch)
|
let result = interactionCoordinator.gestureRecognizer(gestureRecognizer, shouldReceive: touch)
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"TextContentView.gestureShouldReceive",
|
||||||
|
"gesture=\(type(of: gestureRecognizer)) touchView=\(RDReaderTapDebug.describe(touch.view)) result=\(result)"
|
||||||
|
)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(
|
func gestureRecognizer(
|
||||||
|
|||||||
@ -6,6 +6,10 @@ extension RDReaderView {
|
|||||||
func tapCenter() {
|
func tapCenter() {
|
||||||
refreshToolViewsFromProviderIfNeeded()
|
refreshToolViewsFromProviderIfNeeded()
|
||||||
isShowToolView = !isShowToolView
|
isShowToolView = !isShowToolView
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.tapCenter",
|
||||||
|
"toggle toolView visible=\(isShowToolView) top=\(RDReaderTapDebug.describe(topToolView)) bottom=\(RDReaderTapDebug.describe(bottomToolView))"
|
||||||
|
)
|
||||||
if isShowToolView {
|
if isShowToolView {
|
||||||
if let topToolView = topToolView {
|
if let topToolView = topToolView {
|
||||||
installToolViewIfNeeded(topToolView, position: .top)
|
installToolViewIfNeeded(topToolView, position: .top)
|
||||||
@ -27,6 +31,7 @@ extension RDReaderView {
|
|||||||
|
|
||||||
collectionView.isUserInteractionEnabled = false
|
collectionView.isUserInteractionEnabled = false
|
||||||
pageViewController.view.isUserInteractionEnabled = false
|
pageViewController.view.isUserInteractionEnabled = false
|
||||||
|
RDReaderTapDebug.log("ReaderView.tapCenter", "disabled collectionView/pageViewController interaction while toolView is visible")
|
||||||
} else {
|
} else {
|
||||||
if let topToolView = topToolView {
|
if let topToolView = topToolView {
|
||||||
UIView.animate(withDuration: toolViewAnimationDuration, animations: {
|
UIView.animate(withDuration: toolViewAnimationDuration, animations: {
|
||||||
@ -46,6 +51,7 @@ extension RDReaderView {
|
|||||||
|
|
||||||
collectionView.isUserInteractionEnabled = true
|
collectionView.isUserInteractionEnabled = true
|
||||||
pageViewController.view.isUserInteractionEnabled = true
|
pageViewController.view.isUserInteractionEnabled = true
|
||||||
|
RDReaderTapDebug.log("ReaderView.tapCenter", "re-enabled collectionView/pageViewController interaction after hiding toolView")
|
||||||
}
|
}
|
||||||
onToolViewVisibilityChanged?(isShowToolView)
|
onToolViewVisibilityChanged?(isShowToolView)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,53 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
enum RDReaderTapDebug {
|
||||||
|
|
||||||
|
private static let enabledArguments: Set<String> = [
|
||||||
|
"--demo-tap-debug",
|
||||||
|
"--demo-reader-interaction-debug"
|
||||||
|
]
|
||||||
|
|
||||||
|
static var isEnabled: Bool {
|
||||||
|
#if DEBUG
|
||||||
|
return true
|
||||||
|
#else
|
||||||
|
let arguments = ProcessInfo.processInfo.arguments
|
||||||
|
if arguments.contains(where: { enabledArguments.contains($0) }) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let environment = ProcessInfo.processInfo.environment
|
||||||
|
return environment["RDREADER_TAP_DEBUG"] == "1"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static func log(_ scope: String, _ message: String) {
|
||||||
|
guard isEnabled else { return }
|
||||||
|
let threadRole = Thread.isMainThread ? "main" : "bg"
|
||||||
|
let queueLabel = String(validatingUTF8: __dispatch_queue_get_label(nil)) ?? "unknown"
|
||||||
|
print("[RDReaderTap][\(scope)][\(threadRole)][queue=\(queueLabel)] \(message)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func describe(_ point: CGPoint) -> String {
|
||||||
|
"(\(format(point.x)), \(format(point.y)))"
|
||||||
|
}
|
||||||
|
|
||||||
|
static func describe(_ rect: CGRect) -> String {
|
||||||
|
"(x:\(format(rect.origin.x)), y:\(format(rect.origin.y)), w:\(format(rect.size.width)), h:\(format(rect.size.height)))"
|
||||||
|
}
|
||||||
|
|
||||||
|
static func describe(_ view: UIView?) -> String {
|
||||||
|
guard let view else { return "nil" }
|
||||||
|
let identifier = view.accessibilityIdentifier ?? "nil"
|
||||||
|
return "\(type(of: view))(addr=\(Unmanaged.passUnretained(view).toOpaque()), id=\(identifier))"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func format(_ value: CGFloat) -> String {
|
||||||
|
String(format: "%.1f", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class RDReaderView: UIView {
|
public class RDReaderView: UIView {
|
||||||
|
|
||||||
enum TapEvent {
|
enum TapEvent {
|
||||||
@ -524,10 +571,16 @@ public class RDReaderView: UIView {
|
|||||||
@objc private func tapAction(tap: UITapGestureRecognizer) {
|
@objc private func tapAction(tap: UITapGestureRecognizer) {
|
||||||
let point = tap.location(in: tap.view)
|
let point = tap.location(in: tap.view)
|
||||||
let hitView = hitTest(point, with: nil)
|
let hitView = hitTest(point, with: nil)
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.tapAction",
|
||||||
|
"received point=\(RDReaderTapDebug.describe(point)) hitView=\(RDReaderTapDebug.describe(hitView)) display=\(currentDisplayType) currentPage=\(currentPage) toolVisible=\(isShowToolView) transitioning=\(isPageCurlTransitioning)"
|
||||||
|
)
|
||||||
if containsTextContentView(in: hitView) {
|
if containsTextContentView(in: hitView) {
|
||||||
|
RDReaderTapDebug.log("ReaderView.tapAction", "ignored because hitView is inside text content view")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if shouldSuppressChromeToggle(for: hitView, point: point) {
|
if shouldSuppressChromeToggle(for: hitView, point: point) {
|
||||||
|
RDReaderTapDebug.log("ReaderView.tapAction", "ignored because chrome toggle is suppressed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
handleResolvedTap(at: point, hitView: hitView, in: tap.view)
|
handleResolvedTap(at: point, hitView: hitView, in: tap.view)
|
||||||
@ -535,13 +588,24 @@ public class RDReaderView: UIView {
|
|||||||
|
|
||||||
private func shouldSuppressChromeToggle(for hitView: UIView?, point: CGPoint) -> Bool {
|
private func shouldSuppressChromeToggle(for hitView: UIView?, point: CGPoint) -> Bool {
|
||||||
if selectionTapSuppressedContentViews.allObjects.isEmpty == false {
|
if selectionTapSuppressedContentViews.allObjects.isEmpty == false {
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.suppressChromeToggle",
|
||||||
|
"suppressed by active selectionTapSuppressedContentViews count=\(selectionTapSuppressedContentViews.allObjects.count)"
|
||||||
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
var currentView = hitView
|
var currentView = hitView
|
||||||
while let view = currentView {
|
while let view = currentView {
|
||||||
if let textContentView = view as? RDEPUBTextContentView {
|
if let textContentView = view as? RDEPUBTextContentView {
|
||||||
let localPoint = convert(point, to: textContentView)
|
let localPoint = convert(point, to: textContentView)
|
||||||
return textContentView.shouldSuppressReaderTap(at: localPoint)
|
let shouldSuppress = textContentView.shouldSuppressReaderTap(at: localPoint)
|
||||||
|
if shouldSuppress {
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.suppressChromeToggle",
|
||||||
|
"suppressed by text content view=\(RDReaderTapDebug.describe(textContentView)) localPoint=\(RDReaderTapDebug.describe(localPoint))"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return shouldSuppress
|
||||||
}
|
}
|
||||||
currentView = view.superview
|
currentView = view.superview
|
||||||
}
|
}
|
||||||
@ -566,6 +630,10 @@ public class RDReaderView: UIView {
|
|||||||
} else {
|
} else {
|
||||||
selectionTapSuppressedContentViews.remove(contentView)
|
selectionTapSuppressedContentViews.remove(contentView)
|
||||||
}
|
}
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.selectionTapSuppression",
|
||||||
|
"contentView=\(RDReaderTapDebug.describe(contentView)) suppressed=\(isSuppressed) activeCount=\(selectionTapSuppressedContentViews.allObjects.count)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateSelectionPagingSuppression(for contentView: UIView, isSuppressed: Bool) {
|
func updateSelectionPagingSuppression(for contentView: UIView, isSuppressed: Bool) {
|
||||||
@ -576,18 +644,32 @@ public class RDReaderView: UIView {
|
|||||||
} else {
|
} else {
|
||||||
selectionPagingSuppressedContentViews.remove(contentView)
|
selectionPagingSuppressedContentViews.remove(contentView)
|
||||||
}
|
}
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.selectionPagingSuppression",
|
||||||
|
"contentView=\(RDReaderTapDebug.describe(contentView)) suppressed=\(isSuppressed) activeCount=\(selectionPagingSuppressedContentViews.allObjects.count)"
|
||||||
|
)
|
||||||
updatePagingInteractionSuppression()
|
updatePagingInteractionSuppression()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleContentTap(at point: CGPoint, in sourceView: UIView) {
|
func handleContentTap(at point: CGPoint, in sourceView: UIView) {
|
||||||
let localPoint = sourceView.convert(point, to: self)
|
let localPoint = sourceView.convert(point, to: self)
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.handleContentTap",
|
||||||
|
"forwarded from sourceView=\(RDReaderTapDebug.describe(sourceView)) sourcePoint=\(RDReaderTapDebug.describe(point)) localPoint=\(RDReaderTapDebug.describe(localPoint))"
|
||||||
|
)
|
||||||
handleResolvedTap(at: localPoint, hitView: nil, in: self)
|
handleResolvedTap(at: localPoint, hitView: nil, in: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleResolvedTap(at point: CGPoint, hitView: UIView?, in tapView: UIView?) {
|
private func handleResolvedTap(at point: CGPoint, hitView: UIView?, in tapView: UIView?) {
|
||||||
if isShowToolView {
|
if isShowToolView {
|
||||||
if let top = topToolView, isHitView(hitView, inside: top, point: point) { return }
|
if let top = topToolView, isHitView(hitView, inside: top, point: point) {
|
||||||
if let bottom = bottomToolView, isHitView(hitView, inside: bottom, point: point) { return }
|
RDReaderTapDebug.log("ReaderView.handleResolvedTap", "ignored because point hits top tool view")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let bottom = bottomToolView, isHitView(hitView, inside: bottom, point: point) {
|
||||||
|
RDReaderTapDebug.log("ReaderView.handleResolvedTap", "ignored because point hits bottom tool view")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
guard let viewFrame = tapView?.frame else { return }
|
guard let viewFrame = tapView?.frame else { return }
|
||||||
tapEvent = tapRegionHandler.resolveTapEvent(
|
tapEvent = tapRegionHandler.resolveTapEvent(
|
||||||
@ -595,6 +677,10 @@ public class RDReaderView: UIView {
|
|||||||
viewFrame: viewFrame,
|
viewFrame: viewFrame,
|
||||||
isToolViewVisible: isShowToolView
|
isToolViewVisible: isShowToolView
|
||||||
)
|
)
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.handleResolvedTap",
|
||||||
|
"resolved tapEvent=\(tapEvent) point=\(RDReaderTapDebug.describe(point)) viewFrame=\(RDReaderTapDebug.describe(viewFrame))"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func containsTextContentView(in hitView: UIView?) -> Bool {
|
private func containsTextContentView(in hitView: UIView?) -> Bool {
|
||||||
@ -612,6 +698,10 @@ public class RDReaderView: UIView {
|
|||||||
let shouldSuppress = activePagingSuppressionContentViews().isEmpty == false
|
let shouldSuppress = activePagingSuppressionContentViews().isEmpty == false
|
||||||
guard shouldSuppress != isPagingInteractionSuppressed else { return }
|
guard shouldSuppress != isPagingInteractionSuppressed else { return }
|
||||||
isPagingInteractionSuppressed = shouldSuppress
|
isPagingInteractionSuppressed = shouldSuppress
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.pagingSuppression",
|
||||||
|
"updated shouldSuppress=\(shouldSuppress) activeContentViews=\(activePagingSuppressionContentViews().count)"
|
||||||
|
)
|
||||||
setPagingInteractionEnabled(!shouldSuppress)
|
setPagingInteractionEnabled(!shouldSuppress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,22 +861,37 @@ extension RDReaderView: UIGestureRecognizerDelegate {
|
|||||||
guard gestureRecognizer === tapGestureRecognizer else { return true }
|
guard gestureRecognizer === tapGestureRecognizer else { return true }
|
||||||
|
|
||||||
if selectionTapSuppressedContentViews.allObjects.isEmpty == false {
|
if selectionTapSuppressedContentViews.allObjects.isEmpty == false {
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.gestureShouldReceive",
|
||||||
|
"return false because selectionTapSuppressedContentViews count=\(selectionTapSuppressedContentViews.allObjects.count)"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let point = touch.location(in: self)
|
let point = touch.location(in: self)
|
||||||
if containsTextContentView(in: touch.view) {
|
if containsTextContentView(in: touch.view) {
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.gestureShouldReceive",
|
||||||
|
"return false because touch.view is inside text content view point=\(RDReaderTapDebug.describe(point)) touchView=\(RDReaderTapDebug.describe(touch.view))"
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let topToolView, isHitView(touch.view, inside: topToolView, point: point) {
|
if let topToolView, isHitView(touch.view, inside: topToolView, point: point) {
|
||||||
|
RDReaderTapDebug.log("ReaderView.gestureShouldReceive", "return false because touch hit topToolView")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let bottomToolView, isHitView(touch.view, inside: bottomToolView, point: point) {
|
if let bottomToolView, isHitView(touch.view, inside: bottomToolView, point: point) {
|
||||||
|
RDReaderTapDebug.log("ReaderView.gestureShouldReceive", "return false because touch hit bottomToolView")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let searchBarView, isHitView(touch.view, inside: searchBarView, point: point) {
|
if let searchBarView, isHitView(touch.view, inside: searchBarView, point: point) {
|
||||||
|
RDReaderTapDebug.log("ReaderView.gestureShouldReceive", "return false because touch hit searchBarView")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
RDReaderTapDebug.log(
|
||||||
|
"ReaderView.gestureShouldReceive",
|
||||||
|
"return true point=\(RDReaderTapDebug.describe(point)) touchView=\(RDReaderTapDebug.describe(touch.view))"
|
||||||
|
)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user