fix: slow rendering after searching and back to home

This commit is contained in:
Tienson Qin
2025-12-07 13:49:44 +08:00
parent 768b85a2cb
commit 6d167fe67b
4 changed files with 76 additions and 147 deletions

View File

@@ -31,6 +31,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
/// Temporary snapshot image for smooth pop transitions.
private var popSnapshotView: UIView?
// Each stack has its own native VC stack, just like paths.
private var stackViewControllerStacks: [String: [UIViewController]] = [:]
// ---------------------------------------------------------
// MARK: Helpers
// ---------------------------------------------------------
@@ -40,14 +43,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
return raw
}
private func debugLogStacks(_ label: String) {
#if DEBUG
print("🧭 [\(label)] activeStackId=\(activeStackId)")
print(" pathStack=\(pathStack)")
print(" stackPathStacks=\(stackPathStacks)")
#endif
}
/// Returns the current native path stack for a given logical stack id,
/// or initialises a sensible default if none exists yet.
private func paths(for stackId: String) -> [String] {
@@ -72,6 +67,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
}
}
private func setViewControllers(_ vcs: [UIViewController], for stackId: String) {
stackViewControllerStacks[stackId] = vcs
}
private func viewControllers(for stackId: String) -> [UIViewController] {
stackViewControllerStacks[stackId] ?? []
}
// ---------------------------------------------------------
// MARK: UIApplication lifecycle
// ---------------------------------------------------------
@@ -198,7 +201,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
SharedWebViewController.instance.clearPlaceholder()
SharedWebViewController.instance.attach(to: vc)
debugLogStacks("emptyNavStack")
}
private func pushIfNeeded(path: String, animated: Bool) {
@@ -216,7 +218,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
nav.pushViewController(vc, animated: animated)
debugLogStacks("pushIfNeeded")
}
private func replaceTop(path: String) {
@@ -236,7 +237,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
}
nav.setViewControllers(stack, animated: false)
debugLogStacks("replaceTop")
}
private func popIfNeeded(animated: Bool) {
@@ -246,8 +246,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
_ = pathStack.popLast()
setPaths(pathStack, for: activeStackId)
nav.popViewController(animated: animated)
debugLogStacks("popIfNeeded")
}
}
@@ -274,12 +272,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
vcs.count < pathStack.count
}
#if DEBUG
print("🧭 willShow — isPop=\(isPop)")
print(" toVC=\(toVC.targetPath) fromVC=\(String(describing: fromVC?.targetPath))")
debugLogStacks("willShow")
#endif
if isPop {
// -----------------------------
// POP update per-stack pathStack, then notify JS.
@@ -322,10 +314,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
// 🔑 DO NOT call webView.goBack().
// Tell JS explicitly that native popped.
self.ignoreRoutePopCount += 1
#if DEBUG
print("⬅️ Native POP completed, notifying JS via onNativePop(), ignoreRoutePopCount=\(self.ignoreRoutePopCount)")
debugLogStacks("after native-pop pathStack update")
#endif
if let bridge = SharedWebViewController.instance.bridgeController.bridge {
let js = "window.LogseqNative && window.LogseqNative.onNativePop && window.LogseqNative.onNativePop();"
@@ -395,9 +383,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
private func observeRouteChanges() {
NotificationCenter.default.addObserver(
forName: UILocalPlugin.routeChangeNotification,
object: nil,
queue: .main
forName: UILocalPlugin.routeChangeNotification,
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
guard let nav = self.navController else { return }
@@ -408,40 +396,57 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
let stackId = (notification.userInfo?["stack"] as? String) ?? "home"
let previousStackId = self.activeStackId
#if DEBUG
print("📡 routeDidChange from JS → native")
print(" stackId=\(stackId) navigationType=\(navigationType) path=\(path)")
debugLogStacks("before observeRouteChanges")
#endif
// 🚫 Fast-path: ignore duplicate replace for same stack/path
if stackId == self.activeStackId,
navigationType == "replace",
path == self.pathStack.last {
return
}
// Fast-path: cancel search home root.
// We do NOT rebuild nav stack and we do NOT reattach the WebView.
// JS will just navigate the existing shared WKWebView to "/".
if previousStackId == "search",
stackId == "home"{
// Just update bookkeeping so future home pushes/pop work correctly.
self.activeStackId = "home"
self.setPaths(["/"], for: "home")
self.setPaths(["/__stack__/search"], for: "search")
nav.setViewControllers([], animated: false)
self.setViewControllers([], for: "home")
// 👈 Do NOTHING to nav.viewControllers or SharedWebViewController here.
return
}
// ============================================
// 1 Stack switch: home capture goto ...
// 1 Stack switch: home search capture...
// ============================================
if stackId != self.activeStackId {
// Save current native stack paths; drop stale search stack when leaving it.
if previousStackId == "search", stackId != "search" {
self.setPaths(["/__stack__/search"], for: "search")
} else {
self.setPaths(self.pathStack, for: previousStackId)
}
self.setPaths(self.pathStack, for: previousStackId)
// Load (or create) new stack's paths
// Load saved paths for target stack
var newPaths = self.paths(for: stackId)
// Ensure the top of the stack matches the path sent by JS
if let last = newPaths.last, last != path {
if newPaths.isEmpty {
newPaths = [path]
} else {
newPaths[newPaths.count - 1] = path
}
// 🔑 Special rules for shaping the new stack
if stackId == "home", path == "/" {
// 👉 ALWAYS reset home to a single root VC.
newPaths = ["/"]
} else if newPaths.isEmpty {
// First time visiting this stack
newPaths = [path]
} else if let last = newPaths.last, last != path {
// Same history, but different top path align the top.
newPaths[newPaths.count - 1] = path
}
self.activeStackId = stackId
self.pathStack = newPaths
self.setPaths(newPaths, for: stackId)
// Rebuild the UINavigationController's stack from these paths
// Rebuild native stack for these paths
var vcs: [UIViewController] = []
for (idx, p) in newPaths.enumerated() {
let vc = NativePageViewController(path: p, push: idx > 0)
@@ -449,24 +454,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
}
nav.setViewControllers(vcs, animated: false)
self.setViewControllers(vcs, for: stackId)
if let lastVC = vcs.last as? NativePageViewController {
SharedWebViewController.instance.attach(to: lastVC)
SharedWebViewController.instance.clearPlaceholder()
// Defer & avoid redundant attach.
DispatchQueue.main.async {
if let bridge = SharedWebViewController.instance.bridgeController.bridge,
let webView = bridge.webView,
webView.isDescendant(of: lastVC.view) {
} else {
SharedWebViewController.instance.attach(to: lastVC)
}
SharedWebViewController.instance.clearPlaceholder()
}
}
#if DEBUG
print("🔀 STACK SWITCH to \(stackId)")
debugLogStacks("after stack switch")
#endif
// For stacks like "capture", default paths are ["__/stack__/capture"],
// so they get a single VC and no back button.
return
}
// ============================================
// 2 Navigation *within* the active stack
// 2 Navigation *within* active stack
// ============================================
switch navigationType {
case "reset":
@@ -478,10 +485,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
case "pop":
if self.ignoreRoutePopCount > 0 {
self.ignoreRoutePopCount -= 1
#if DEBUG
print("🙈 ignoring JS pop (ignoreRoutePopCount→\(self.ignoreRoutePopCount))")
debugLogStacks("after ignore JS pop")
#endif
return
}
if self.pathStack.count > 1 {
@@ -491,12 +494,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UINavigationControllerDel
default:
self.pushIfNeeded(path: path, animated: true)
}
#if DEBUG
debugLogStacks("after observeRouteChanges switch")
#endif
}
}
}
// ---------------------------------------------------------

View File

@@ -320,7 +320,6 @@ private struct SearchTabHost26: View {
wasSearching = false
selectedTab.wrappedValue = .content(0)
store.selectedId = firstId
LiquidTabsPlugin.shared?.notifyTabSelected(id: firstId)
}
}
}

View File

@@ -158,7 +158,7 @@
route-match (state/sub :route-match)]
[:main#app-container-wrapper.ls-fold-button-on-right
[:div#app-container {:class (when show-popup? "invisible")}
[:div#main-container.flex.flex-1.overflow-x-hidden.py-4
[:div#main-container.flex.flex-1.overflow-x-hidden
(app current-repo route-match)]]
(when show-popup?
[:div.ls-layer

View File

@@ -16,30 +16,9 @@
(defonce ^:private pending-navigation (atom nil))
(defonce ^:private hooks-installed? (atom false))
;; --- DEBUG toggle ---
(def ^:private debug-nav? true)
(defn- dbg [tag & args]
(when debug-nav?
(let [payload (cond
;; one map argument → use it directly
(and (= 1 (count args))
(map? (first args)))
(first args)
;; even number of args → treat as k/v pairs
(even? (count args))
(apply hash-map args)
;; odd / weird → just log the raw args
:else
{:args args})]
(log/info tag payload))))
;; Track whether the latest change came from a native back gesture / popstate.
(.addEventListener js/window "popstate" (fn [_]
(reset! navigation-source :pop)
(dbg :nav/popstate {:source :popstate})))
(reset! navigation-source :pop)))
(defn current-stack
[]
@@ -48,7 +27,6 @@
(defn set-current-stack!
[stack]
(when (some? stack)
(dbg :nav/set-current-stack {:from @active-stack :to stack})
(reset! active-stack stack)))
(defn- strip-fragment [href]
@@ -77,7 +55,6 @@
(defn- record-navigation-intent!
[{:keys [type stack]}]
(let [stack (or stack @active-stack primary-stack)]
(dbg :nav/record-intent {:type type :stack stack})
(reset! pending-navigation {:type type
:stack stack})))
@@ -91,7 +68,6 @@
([k params query]
(record-navigation-intent! {:type :push
:stack @active-stack})
(dbg :nav/push-state {:name k :params params :query query :stack @active-stack})
(orig-push-state k params query)))
(defonce orig-replace-state rfe/replace-state)
@@ -103,7 +79,6 @@
([k params query]
(record-navigation-intent! {:type :replace
:stack @active-stack})
(dbg :nav/replace-state {:name k :params params :query query :stack @active-stack})
(orig-replace-state k params query)))
(defn install-navigation-hooks!
@@ -111,7 +86,6 @@
Also tags navigation with the active stack so native can keep per-stack history."
[]
(when (compare-and-set! hooks-installed? false true)
(dbg :nav/hooks-installed {})
(set! rfe/push-state push-state)
(set! rfe/replace-state replace-state)))
@@ -132,14 +106,6 @@
[stack]
(-> @stack-history (get stack) :history last))
;; --- DEBUG: watch stack-history changes ---
(add-watch stack-history ::stack-history-debug
(fn [_ _ old new]
(when debug-nav?
(dbg :nav/stack-history
:old (into {} (for [[k v] old] [k (mapv :path (:history v))]))
:new (into {} (for [[k v] new] [k (mapv :path (:history v))]))))))
(defn- remember-route!
[stack nav-type route path route-match]
(when stack
@@ -160,12 +126,6 @@
(conj history entry))
history)))]
(when entry
(dbg :nav/remember-route
:stack stack
:nav-type nav-type
:path path
:route-to (or (get-in route [:to])
(get-in route-match [:data :name])))
(swap! stack-history update stack (fn [{:keys [history] :as st}]
{:history (update-history history)}))
(swap! initialised-stacks assoc stack true)))))
@@ -173,7 +133,6 @@
(defn reset-stack-history!
[stack]
(when stack
(dbg :nav/reset-stack-history {:stack stack})
(swap! stack-history assoc stack {:history [(stack-defaults stack)]})
(swap! initialised-stacks dissoc stack)))
@@ -193,12 +152,7 @@
(true? push) "push"
:else "push"))]
(reset! navigation-source nil)
(dbg :nav/next-navigation
:src src
:intent intent
:stack stack
:first? first?
:nav-type nav-type)
(when first?
(swap! initialised-stacks assoc stack true))
{:navigation-type nav-type
@@ -207,7 +161,6 @@
(defn- notify-route-payload!
[payload]
(dbg :nav/notify-native payload)
(-> (.routeDidChange mobile-util/ui-local (clj->js payload))
(p/catch (fn [err]
(log/warn :mobile-native-navigation/route-report-failed
@@ -222,12 +175,6 @@
:stack (or stack (current-stack))})
stack (or stack (current-stack))
path (or path (current-path))]
(dbg :nav/notify-route-change
:stack stack
:navigation-type navigation-type
:path path
:route-to (or (:to route)
(get-in route-match [:data :name])))
(set-current-stack! stack)
(remember-route! stack navigation-type route path route-match)
(when (and (mobile-util/native-ios?)
@@ -254,20 +201,16 @@
"Activate a stack and restore its last known route."
[stack]
(when stack
(let [stack (ensure-stack stack)]
(dbg :nav/switch-stack {:to stack
:current @active-stack
:stack-top (select-keys (stack-top stack) [:path])})
(let [stack (ensure-stack stack)
current @active-stack]
(set-current-stack! stack)
(when-let [{:keys [path route route-match]} (stack-top stack)]
(let [route-match (or route-match (:route-match (stack-defaults stack)))
path (or path (current-path))]
(dbg :nav/switch-stack-apply
:stack stack
:path path
:route-name (or (get-in route [:data :name])
(get-in route-match [:data :name])))
(route-handler/set-route-match! route-match)
(when (= current "search")
;; reset to :home
(orig-replace-state :home nil nil))
(notify-route-change!
{:route {:to (or (get-in route [:data :name])
(get-in route-match [:data :name]))
@@ -286,23 +229,14 @@
(let [stack (current-stack)
{:keys [history]} (get @stack-history stack)
history (vec history)]
(if (<= (count history) 1)
(dbg :nav/pop-stack-root {:stack stack
:history (mapv :path history)})
(when (> (count history) 1)
(let [new-history (subvec history 0 (dec (count history)))
{:keys [route route-match path]} (peek new-history)
{:keys [route-match]} (peek new-history)
route-match (or route-match (:route-match (stack-defaults stack)))
route-name (get-in route-match [:data :name])
path-params (get-in route-match [:parameters :path])
query-params (get-in route-match [:parameters :query])]
(dbg :nav/pop-stack
:stack stack
:old-history (mapv :path history)
:new-history (mapv :path new-history)
:target-path path
:route-name route-name)
(swap! stack-history assoc stack {:history new-history})
;; Pretend this came from a pop for next-navigation!
@@ -315,10 +249,6 @@
(defn ^:export install-native-bridge!
[]
(dbg :nav/install-native-bridge {})
(set! (.-LogseqNative js/window)
(clj->js
{:onNativePop (fn []
(dbg :nav/on-native-pop {:stack (current-stack)
:path (current-path)})
(pop-stack!))})))
{:onNativePop (fn [] (pop-stack!))})))