LargeTitles UIScrollView 不支持多个观察者实现 _scrollViewWillEndDraggingWithVelocity:targetContentOffset。

14

我在我的应用程序中使用以下代码实现了大标题:

if #available(iOS 11.0, *) {
            navigationController?.navigationBar.prefersLargeTitles = true
            navigationItem.largeTitleDisplayMode = .always
        } else {
            // Fallback on earlier versions
        }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y <= 0 {
        if #available(iOS 11.0, *) {
            self.navigationItem.largeTitleDisplayMode = .always
        } else {
            // Fallback on earlier versions
        }
    } else {
        if #available(iOS 11.0, *) {
            self.navigationItem.largeTitleDisplayMode = .never
        } else {
            // Fallback on earlier versions
        }
    }
    self.navigationController?.navigationBar.setNeedsLayout()
    self.view.setNeedsLayout()
    UIView.animate(withDuration: 0.01, animations: {
        self.navigationController?.navigationBar.layoutIfNeeded()
        self.view.layoutIfNeeded()
    })
}
我能够成功地在选项卡栏之间切换视图,但是当我将一个视图推到选项卡栏控制器的顶部,然后使用以下代码弹出它时:
_ = self.navigationController?.popViewController(animated: true)

当我再次在选项卡上切换视图时,会出现以下错误: 终止应用程序,原因是未捕获的异常 'NSInternalInconsistencyException',错误原因:UIScrollView不支持多个实现_scrollViewWillEndDraggingWithVelocity:targetContentOffset:观察者'


我遇到了相同的问题 :( - Matthew Davis
当我去到另一个视图时,表格视图仍在滚动时出现了问题。我通过在scrollViewDidScroll中设置一个布尔值来解决问题,该布尔值在启动segue时禁用任何滚动。 - user1079052
5个回答

8
这不是一个解决方案,而是你需要在代码中调查的潜在问题。我得到了同样的错误信息(UIScrollView不支持实现“_scrollViewWillEndDraggingWithVelocity:targetContentOffset”方法的多个观察者),并且我注意到我做错了一些事情。 我在使用NavigationView的SwiftUI应用程序中遇到了这个错误消息。 我的错误是,在ParentView中根视图有一个NavigationView。使用NavigationLink我正在移动到ChildView,它也有一个NavigationView作为根视图。以下是代码中的外观:
import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ParentView()
        }
    }
}

struct ParentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: ChildView()) {
                    Text("Parent view")
                }
            }
            .navigationTitle("Parent")
        }
    }
}

struct ChildView: View {
    var body: some View {
        List {
            ForEach(0 ..< 5) { _ in
                Text("Child view")
            }
        }
        .navigationTitle("Child")
    }
}

最初ChildView的外观如下:

struct ChildView: View {
    var body: some View {
        NavigationView {
            List {
                ForEach(0 ..< 5) { _ in
                    Text("Second screen")
                }
            }
            .navigationTitle("Second")
        }
    }
}

请注意,我试图推送一个自身嵌入在NavigationView中的视图。如第一个示例中所示删除它可以解决错误消息。你可以尝试查看一下,也许你正在做同样的错误,只是在UIKit而不是SwiftUI中。

是的,我犯了这个错误,因为我对此还很陌生。 - sgelves
这应该是被接受的答案。尝试使用导航链接(NavigationLink)来引导到另一个NavigationView是错误使用NavigationView的方式。这与在UIKit中尝试将NavigationController推入另一个NavigationController几乎相同。当涉及到SwiftUI时,协调器/路由层是一个有趣的话题。也许将NavigationView嵌入到场景视图的主体内不应该发生,或者至少应该依赖于某些@Environment变量。 - dvp.petrov

2

我找到了解决方案。您需要将第一个导航控制器设置为不使用大标题。

关键是现在UIScrollView只有一个观察者(navigationController)实现_scrollViewWillEndDraggingWithVelocity。

if (@available(iOS 11.0, *)) {

    self.navigationController.navigationBar.prefersLargeTitles = FALSE;
    self.navigationController.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;

}

1
当我切换到另一个视图时,如果表格视图仍在滚动,则会出现问题。我通过在scrollViewDidScroll中设置一个bool值来解决这个问题,该值在开始segue时禁用任何滚动。
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if viewIsVisible {
            if scrollView.contentOffset.y <= 0 {
                if #available(iOS 11.0, *) {
                    self.navigationItem.largeTitleDisplayMode = .always
                } else {
                    // Fallback on earlier versions
                }
            } else {
                if #available(iOS 11.0, *) {
                    self.navigationItem.largeTitleDisplayMode = .never
                } else {
                    // Fallback on earlier versions
                }
            }
            self.navigationController?.navigationBar.setNeedsLayout()
            self.view.setNeedsLayout()
            UIView.animate(withDuration: 0.01, animations: {
                self.navigationController?.navigationBar.layoutIfNeeded()
                self.view.layoutIfNeeded()
            })
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        self.viewIsVisible = false
}

1

我认为以上所有答案都没有真正解决问题,并且过于复杂。我建议在每个UIViewController的子类中启用/禁用大标题,以便它们不同时使用大标题。在viewWillAppearviewWillDisappear方法中执行此操作是一个好地方。

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.largeTitleDisplayMode = .always
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.navigationBar.prefersLargeTitles = true
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.navigationBar.prefersLargeTitles = false
}

1

我有同样的问题,通过从AppDelegate中删除此行代码来解决它:

UINavigationBar.appearance().prefersLargeTitles = true

在某些UIViewController中,在viewDidLoad内处理prefersLargeTitles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接