Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,28 @@ class GutenbergView : FrameLayout {
fun undo() {
evaluateIfLoaded("editor.undo();")
}
/**
* Temporary Android native Find decision spike.
* This is not the final Find in Post API.
*/
fun debugFind(query: String) {
if (!isEditorLoaded) {
Log.e("GutenbergView", "Can't search until the editor has loaded")
return
}

handler.post {
webView.setFindListener { activeMatchOrdinal, numberOfMatches, isDoneCounting ->
Log.d(
"GutenbergFindSpike",
"query=$query activeMatchOrdinal=$activeMatchOrdinal " +
"numberOfMatches=$numberOfMatches isDoneCounting=$isDoneCounting"
)
}

webView.findAllAsync(query)
}
}

fun redo() {
evaluateIfLoaded("editor.redo();")
Expand Down
8 changes: 8 additions & 0 deletions ios/Demo-iOS/Sources/Views/EditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ struct EditorView: View {

private var moreMenu: some View {
Menu {
Button(action: {
viewModel.perform(.find)
}, label: {
Label("Find in Post", systemImage: "magnifyingglass")
})

Button(action: {
viewModel.isCodeEditorEnabled.toggle()
}, label: {
Expand Down Expand Up @@ -163,6 +169,7 @@ private struct _EditorView: UIViewControllerRepresentable {
switch $0 {
case .redo: viewController?.redo()
case .undo: viewController?.undo()
case .find: viewController?.presentFindNavigator()
case .triggerCrash:
viewController?.webView.evaluateJavaScript(triggerEditorCrashScript, completionHandler: nil)
}
Expand Down Expand Up @@ -402,6 +409,7 @@ private final class EditorViewModel {
enum Action {
case undo
case redo
case find
case triggerCrash
}

Expand Down
109 changes: 108 additions & 1 deletion ios/Sources/GutenbergKit/Sources/EditorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
config.applicationNameForUserAgent = "GutenbergKit/\(GutenbergKitVersion.version)"

self.webView = GBWebView(frame: .zero, configuration: config)
self.webView.isFindInteractionEnabled = true
self.webView.scrollView.keyboardDismissMode = .interactive

self.isWarmupMode = isWarmupMode
Expand Down Expand Up @@ -299,8 +300,9 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor)
webViewBottomToSafeArea
])
observeKeyboardVisibility()

// WebView starts hidden and fades in when editor navigation completes
webView.alpha = 0
Expand Down Expand Up @@ -340,6 +342,107 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
setupNavigationOverlay()
}

public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// The find navigator can be dismissed without any accompanying keyboard
// notification, so re-evaluate on layout as well.
updateWebViewBottomConstraint()
}

// MARK: - Web View Bottom Inset

/// Pins the web view above the software keyboard.
private lazy var webViewBottomToKeyboard = webView.bottomAnchor.constraint(
equalTo: view.keyboardLayoutGuide.topAnchor
)

/// Pins the web view to the bottom of the view, used whenever nothing is
/// docked above it.
///
/// `UIKeyboardLayoutGuide` folds the system find navigator into the region
/// it tracks. When the navigator and the keyboard are dismissed together,
/// the guide never receives a final update and stays short by the
/// navigator's height, stranding the web view above the bottom of the
/// screen. Swapping to the safe area whenever neither the keyboard nor the
/// navigator is present sidesteps the stale value without changing how the
/// keyboard itself is tracked.
private lazy var webViewBottomToSafeArea = webView.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor
)

private var isKeyboardVisible = false

private func observeKeyboardVisibility() {
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(keyboardWillShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
center.addObserver(
self,
selector: #selector(keyboardWillHide(_:)),
name: UIResponder.keyboardWillHideNotification,
object: nil
)
center.addObserver(
self,
selector: #selector(keyboardDidHide),
name: UIResponder.keyboardDidHideNotification,
object: nil
)
}

@objc private func keyboardWillShow() {
isKeyboardVisible = true
updateWebViewBottomConstraint()
}

@objc private func keyboardWillHide(_ notification: Notification) {
isKeyboardVisible = false
updateWebViewBottomConstraint()

// The find navigator still reports itself visible at this point, so the
// pass above cannot yet tell that nothing is left docked at the bottom.
// It flips one run loop later, while the keyboard is still animating
// away, so re-check then and borrow the keyboard's own duration: the web
// view grows alongside the keyboard instead of leaving a gap that snaps
// shut once the keyboard has gone.
let duration = notification.userInfo?[
UIResponder.keyboardAnimationDurationUserInfoKey
] as? TimeInterval ?? 0.25
DispatchQueue.main.async { [weak self] in
guard let self, self.updateWebViewBottomConstraint() else { return }
UIView.animate(withDuration: duration) {
self.view.layoutIfNeeded()
}
}
}

/// Final safety net for hosts where the check above still runs too early.
@objc private func keyboardDidHide() {
isKeyboardVisible = false
updateWebViewBottomConstraint()
}

/// - Returns: Whether the active constraint changed.
@discardableResult
private func updateWebViewBottomConstraint() -> Bool {
let isFindNavigatorVisible = webView.findInteraction?.isFindNavigatorVisible ?? false
let shouldTrackKeyboard = isKeyboardVisible || isFindNavigatorVisible
guard shouldTrackKeyboard != webViewBottomToKeyboard.isActive else { return false }

if shouldTrackKeyboard {
webViewBottomToSafeArea.isActive = false
webViewBottomToKeyboard.isActive = true
} else {
webViewBottomToKeyboard.isActive = false
webViewBottomToSafeArea.isActive = true
}
return true
}

public override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeNavigationOverlay()
Expand Down Expand Up @@ -568,6 +671,10 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
evaluate("editor.redo();")
}

public func presentFindNavigator() {
webView.findInteraction?.presentFindNavigator(showingReplace: false)
}

/// Dismisses the topmost modal dialog or menu in the editor
public func dismissTopModal() {
evaluate("editor.dismissTopModal();")
Expand Down