Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LargeTitles UIScrollView does not support multiple observers implementing _scrollViewWillEndDraggingWithVelocity:targetContentOffset

I have implemented large titles in my app with the following code:

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()
    })
}

I am able to successfully toggle between views on a tabbar but when I push a view ontop of the tabbar controller and then pop it off using this code:

_ = self.navigationController?.popViewController(animated: true)

I get this crash when I toggle between views on the tabbar again: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'ERROR: UIScrollView does not support multiple observers implementing _scrollViewWillEndDraggingWithVelocity:targetContentOffset:'

like image 641
user1079052 Avatar asked Dec 18 '17 15:12

user1079052


5 Answers

This is not a solution, but a potential thing that you need to investigate in your code. I got this same error message (UIScrollView does not support multiple observers implementing _scrollViewWillEndDraggingWithVelocity:targetContentOffset) and I noticed I was doing something incorrectly. I got this error message in a SwiftUI app using NavigationView.

The mistake I had made was that ParentView had a Navigation View at the root. Using a NavigationLink I was moving to ChildView, which also had a NavigationView as the root. Here's what it looked like in code:

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")
    }
}

Initially this is what ChildView looked like:

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

Notice how I was trying to push a view which itself was embedded in a NavigationView. Removing it as shown in the first snippet, took care of the error messages. You can try looking into that, maybe you are doing the same mistake just in UIKit instead of SwiftUI.

like image 130
Rajat Avatar answered Mar 02 '23 00:03

Rajat


The problem happened when the tableview was still scrolling when I went to another view. I fixed the problem by setting a bool in the scrollViewDidScroll that disables any scrolling when the segue is started.

    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
}
like image 33
user1079052 Avatar answered Mar 01 '23 22:03

user1079052


I found the solution. You have to set the first navigation controller to not use large titles.

The point is that now UIScrollView has only one observer (navigationController) implementing _scrollViewWillEndDraggingWithVelocity.

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

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

}
like image 45
Hugo Vanderlei Avatar answered Mar 02 '23 00:03

Hugo Vanderlei


I've the same problem and I fixed it by removing this line from AppDelegate:

UINavigationBar.appearance().prefersLargeTitles = true

and handle prefersLargeTitles inside viewDidLoad in certain UIViewController

like image 41
kamwysoc Avatar answered Mar 02 '23 00:03

kamwysoc


I think all of above answers don't really solve the issue and are overcomplicated. I recommend enabling/disabling large titles in each of your UIViewController's subclasses, so they don't use large titles at the same time. Good place to do it is in the viewWillAppear and viewWillDisappear methods

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
}
like image 37
Adam Avatar answered Mar 01 '23 22:03

Adam