Navigation controller with searchBar jump - ios

I have a navigation controller in my main View where it includes a searchBar. Then when it goes to second View (without searchBar) there is a little jump on the screen, and same thing happens when I go back to first View.
Here is my Navigation controller code for first viewController:
func configureNavBar() {
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.barTintColor = .mainPink()
navigationController?.navigationBar.barStyle = .black
searchBar = UISearchBar()
searchBar.delegate = self
searchBar.tintColor = .white
navigationItem.titleView = searchBar
searchBar.showsCancelButton = true
}
Second:
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.barTintColor = .mainPink()
navigationController?.navigationBar.barStyle = .black
Is there any way to eliminate this 'jump' ?

This question actually contains two different issues:
1- In case of "Large Navigation Bar" suddenly collapsed when navigating to another View Controller:
The TableView scrolling is the main reason, Try the following:
self.tableView.contentInsetAdjustmentBehavior = .never
You can set it from "Size Inspector" in storyboard also.
It adjusts its scroll position corresponding to the SafeArea.
2- The case of "Default Navigation Bar" that contains SearchBar in its Navigation Item's title view:
The main reason is that the search bar is added with height "56" by default
Regarding a black line that appears under the navigation bar of the pushed view controller, So you can fix it with the following:
// Inside ViewDidLoad of the Pushed View Controller
self.extendedLayoutIncludesOpaqueBars = true
OR
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout()
navigationController?.view.layoutIfNeeded()
}
For preventing the NavigationBar to be extended at all when putting the SearchBar like this:
You need to create a custom view with a fixed height frame and add this search bar inside it, check the following:
class SearchBarViewHolder: UIView {
let searchBar: UISearchBar
init(customSearchBar: UISearchBar) {
searchBar = customSearchBar
super.init(frame: CGRect.zero)
addSubview(searchBar)
}
override convenience init(frame: CGRect) {
self.init(customSearchBar: UISearchBar())
self.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
searchBar.frame = bounds
}
}
// Adding Search bar
let searchBarViewHolder = SearchBarViewHolder(customSearchBar: searchBar)
searchBarViewHolder.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44)
navigationItem.titleView = searchBarViewHolder

Related

How to adjust sublayer on UINavigationController?

In my Swift project, I added a sublayer to UINavigationController.
But after adding this GAGradientLayer, I can't see the navigation title text or back button.
The weird thing is that in the view hierarchy, the CAGradientLayer(which was added as sublayer) is behind the title and button.
I tried to reload navigationController layer with LayoutIfNeeded, setNeedsLayout or setNeedsDisplay but nothing worked.
And I also just tried to change the navigation title but it doesn't work.
(Actually The text of navigation title is loaded on the view controller behind, so I don't want to change this on this VC.)
So, How can I show my navigation title text and button with CAGradientlayer above?
Here's the screenshot
Here's the codes needed
import UIKit
import SnapKit
class BulletinBoardViewController: UIViewController {
// ...
var backgroundGradientLayer: CAGradientLayer?
let bulletinBoardView = BulletinBoardView()
// MARK: - Lifecycles
override func viewDidLoad() {
super.viewDidLoad()
setBulletinBoardView()
setCells()
}
override func viewWillAppear(_ animated: Bool) {
setupBackgroundLayer()
}
override func viewWillDisappear(_ animated: Bool) {
self.backgroundGradientLayer?.removeFromSuperlayer()
}
// MARK: - Helpers
func setupBackgroundLayer() {
DispatchQueue.main.async {
if let backgroundGradientLayer = self.backgroundGradientLayer {
backgroundGradientLayer.frame = CGRect(x: 0, y: -59, width: 500, height: 103)
self.navigationController?.navigationBar.layer.addSublayer(backgroundGradientLayer)
}
}
}
func setBulletinBoardView() {
self.view.addSubview(bulletinBoardView)
bulletinBoardView.snp.makeConstraints { make in
make.right.left.top.equalTo(self.view.safeAreaLayoutGuide)
make.bottom.equalTo(self.view)
}
}
// ...
}
The origin navigation controller setting is below
class MainPageViewController: UIViewController {
// ...
func setupNav() {
navigationController?.navigationBar.tintColor = .black
navigationItem.rightBarButtonItem = listButton
navigationItem.leftBarButtonItem = settingButton
let backBarButtonItem = UIBarButtonItem(title: "",
style: .plain,
target: self,
action: nil)
self.navigationItem.backBarButtonItem = backBarButtonItem
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .systemGray3
appearance.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: AppFontName.bold, size: 20)!]
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance =
navigationController?.navigationBar.standardAppearance
}
// ...
}

Swift prefersLargeTitles collapsed when WKWebview loaded

I create a NavigationController with a large title NavigationBar with this code.
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let appearance = UIBarButtonItem.appearance()
appearance.setBackButtonTitlePositionAdjustment(UIOffset.init(horizontal: 0.0, vertical: -60), for: .default)
self.navigationBar.barTintColor = UIColor.white
self.navigationBar.prefersLargeTitles = true
self.navigationBar.isTranslucent = true
self.navigationBar.tintColor = UIColor.black
}
}
Everything works fine, but when my WKWebView is loaded, somehow the large title bar collapsed automatically without any ScrollViewDelegate code. Please take a look at my gif here to clarify
Here is my WKWebView code.
class resultViewController: UIViewController
{
private var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
webView = WKWebView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height))
webView.navigationDelegate = self
webView.scrollView.delegate = self
self.view.addSubview(webView)
let url = URL(string: "https://google.com")
webView.load(URLRequest(url: url!))
}
}
I expect to keep the large title bar visible at first screen, and only collapse when scrolling up event triggered.
Any idea?
The reason of this issue is your navigationBar height is changing when you webView starts loading the page. so using viewLayoutMarginsDidChange() we can change navigationBar height.
viewLayoutMarginsDidChange() Called to notify the view controller that the layout margins of its root view changed. This method will get called every time when navigationBar height changes.
Refer This Answer https://stackoverflow.com/a/57430695/7301439

Hide Navigation Controller Search Bar & opened Large Title programmatically

I have a tableView. I set the all settings about searchController ( Search Bar in Large Navigation Bar ) - ( open / close when scroll tableview ). I implemented rightBarButtonItem which name is 'Close' . I want to hide/close tableView and Search Bar with programmatically. I can hide tableView but not SearchBar.
When I do isHidden for SearchBar , The Large Navigation Bar doesnt shrink to normal size.
Pic 1. Opened search bar with scroll down.
Pic 2. Not Hidden Large Navigation Bar with programmatically ( searchar.isHidden not implemented here )
Thanks in advance.
I tried this before but not run
tableView.setContentOffset(.zero, animated: false)
navigationController?.navigationBar.prefersLargeTitles = false
I tried to find a proper way to hide search bar, but I didn't find. But I found a workaround to hide your search bar which is change content offset your table view.
You may try this function to hide your table view and search bar.
func hide() {
tableView.isHidden = true
let point = tableView.contentOffset
let searchBarFrame = self.navigationItem.searchController?.searchBar.frame
let newPoint = CGPoint(x: point.x, y: point.y + searchBarFrame!.height)
tableView.setContentOffset(newPoint, animated: true)
}
Just try this:
navigationItem.searchController = nil
This is all my test code:
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var leftBarButtonItem: UIBarButtonItem!
var isHidden = false
var searchController: UISearchController {
let search = UISearchController(searchResultsController: nil)
search.searchBar.placeholder = "hello world"
search.obscuresBackgroundDuringPresentation = false
return search
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Test"
tableView.delegate = self
tableView.dataSource = self
showSearchController()
}
#IBAction func isHiddenAction(_ sender: UIBarButtonItem) {
isHidden = !isHidden
self.tableView.isHidden = isHidden
if isHidden {
leftBarButtonItem.title = "Show"
hiddenSearchController()
} else {
leftBarButtonItem.title = "Hidden"
showSearchController()
}
}
func hiddenSearchController() {
navigationItem.searchController = nil
}
func showSearchController() {
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = true
definesPresentationContext = true
}

tableView.setContentOffset not working on UITableView embedded in UIViewcontroller

I had this code in a UITableViewController and it worked perfectly.
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
tableView.setContentOffset(point, animated: true
}
Now I'm refactoring my code to fit more of an MVC style architecture. What I did is create a UITableView in the View class:
class View: UIView {
lazy var tableView: UITableView = {
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
return table
}()
func configureView() {
// tableView
addSubview(tableView)
tableView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
tableView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
tableView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
}
}
and then use the View class in my ViewController:
class ViewController: UIViewController {
var newView: View! { return self.view as! View }
override func loadView() {
view = View(frame: UIScreen.main.bounds)
newView.configureView()
}
override func viewDidLoad() {
super.viewDidLoad()
setupSearchBar()
}
func setupSearchBar() {
let searchBar: UISearchBar = searchController.searchBar
newView.tableView.tableHeaderView = searchBar
let point = CGPoint(x: 0, y: searchBar.frame.size.height)
newView.tableView.setContentOffset(point, animated: true)
}
The tableView shows up no problem and everything else is fine. The only thing that's not working is the setContentOffset is being called, but it's not offsetting the content. I want the searchbar to be hidden by default when the user first opens this viewController (similar to iMessage), but after I moved the code from a UITableViewController to separate files (UIView + UIViewController) like in this example, the searchbar always shows by default.
I'm not sure why it's not working. Any help would be greatly appreciated.
It's probably a timing problem relative to layout. Instead of calling setUpSearchBar in viewDidLoad, do it later, in viewDidLayoutSubviews, when initial layout has actually taken place. This method can be called many times, so use a flag to prevent it from being called more than once:
var didSetUp = false
override func viewDidLayoutSubviews() {
if !didSetUp {
didSetUp = true
setUpSearchBar()
}
}
Also: Your animated value is wrong:
newView.tableView.setContentOffset(point, animated: true)
You mean false. You don't want this movement to be visible. The table view should just appear with the search bar out of sight.

Get search bar in navigation bar in Swift

So I've tried everything trying to get a search bar into the navigation bar in Swift. But sadly I haven't gotten it working, just yet...
For those of you who don't know what I'm talking about, I'm trying to do something like this
Note the search bar in the navigation bar. So here's what I'm currently using
self.searchDisplayController?.displaysSearchBarInNavigationBar = true
I popped that in my viewDidLoad, and then when I load up the app I'm presented with, just an empty navigation bar.... :( Any ideas?
Try this
let leftNavBarButton = UIBarButtonItem(customView:Yoursearchbar)
self.navigationItem.leftBarButtonItem = leftNavBarButton
Update
You keep a lazy UISearchBar property
lazy var searchBar:UISearchBar = UISearchBar(frame: CGRectMake(0, 0, 200, 20))
In viewDidLoad
searchBar.placeholder = "Your placeholder"
var leftNavBarButton = UIBarButtonItem(customView:searchBar)
self.navigationItem.leftBarButtonItem = leftNavBarButton
If you want to use storyboard
just drag your searchbar as a outlet,then replace the lazy property with your outlet searchbar
// create the search bar programatically since you won't be
// able to drag one onto the navigation bar
searchBar = UISearchBar()
searchBar.sizeToFit()
// the UIViewController comes with a navigationItem property
// this will automatically be initialized for you if when the
// view controller is added to a navigation controller's stack
// you just need to set the titleView to be the search bar
navigationItem.titleView = searchBar
Swift 5, XCode 11, Storyboard way so you can easily add all the search bar attributes through the storyboard and you have less code in your view controller class.
1.) Add your search bar view as external view in viewcontroller.
2.) Connect searchBarView to you viewcontroller.
3.) Add your searchBarView to your navigationBar title item.
navigationItem.titleView = searchBarView
Result:
In your view controller:
lazy var searchBar = UISearchBar(frame: CGRectZero)
override func viewDidLoad() {
super.viewDidLoad()
searchBar.placeholder = "Search"
navigationItem.titleView = searchBar
}
Doing it this way, by setting the navigationItem.titleView, the search bar is automatically centered across the iPhone and iPad devices. Note: only tested with v8.4 and v9.0
for SWIFT 3
lazy var searchBar = UISearchBar(frame: CGRect.zero)
In 2019, you should use UISearchController.
override func viewDidLoad() {
super.viewDidLoad()
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self.viewModel
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search artists"
self.navigationItem.searchController = searchController
self.definesPresentationContext = true
}
And some class should conform to UISearchResultsUpdating. I usually add this as extension to my ViewModel.
extension ArtistSearchViewModel: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
print("Searching with: " + (searchController.searchBar.text ?? ""))
let searchText = (searchController.searchBar.text ?? "")
self.currentSearchText = searchText
search()
}
}
This will spawn something like this:
For iOS 11 and above
navigationItem.searchController = searchController
For iOS 10 and below
navigationItem.titleView = searchController.searchBar;
or you can assign it as leftBarButtonItem as described in this answer
For Swift 5 or letter
also, you can use this code. Fully Programmatically
import UIKit
class SearchTableViewController: UITableViewController {
private lazy var searchController: UISearchController = {
let sc = UISearchController(searchResultsController: nil)
sc.searchResultsUpdater = self
sc.delegate = self
sc.obscuresBackgroundDuringPresentation = false
sc.searchBar.placeholder = "Enter A Compiny Name Or Symbole"
sc.searchBar.autocapitalizationType = .allCharacters
return sc
}()
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
private func setupNavigationBar() {
navigationItem.searchController = searchController
}
}
// MARK: - UISearchResult Updating and UISearchControllerDelegate Extension
extension SearchTableViewController: UISearchResultsUpdating, UISearchControllerDelegate {
func updateSearchResults(for searchController: UISearchController) {
}
}
let searchBar = UISearchBar()
searchBar.sizeToFit()
searchBar.placeholder = ""
self.navigationController?.navigationBar.topItem?.titleView = searchBar
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.endEditing(true)
searchBar.text = nil
print("## search btn clicked : \(searchBar.text ?? "")")
}
Setting SearchBar as titleView, changes height of navigationBar to 56. To fix this, you can embed searchBar in view and set that as titleView.
var offset: CGFloat = 20
// If VC is pushed, back button should be visible
if navigationController?.navigationBar.backItem != nil {
offset = 40
}
let customFrame = CGRect(x: 0, y: 0, width: view.frame.size.width - offset, height: 44.0)
let searchBarContainer = UIView(frame: customFrame)
searchBar = UISearchBar(frame: customFrame)
searchBarContainer.addSubview(searchBar)
navigationItem.titleView = searchBarContainer

Resources