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
Related
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
I have a UITextView on my view, and the keyboard can be dismissed by pushing a done-button on the keyboard's toolbar. This button is also performing a save on the text. The whole thing was working perfectly until the most recent version of my app. I can't figure out what happened. I was suspecting that the problem was setting backwards compatibility of my app to iOS version 11.0. But then i tried with higher development targets again, and the result is the same. I did not touch this view's code when building the recent updates, so I am not sure what causes the problem. Relevant parts of my code :
let textView: UITextView = {
let tw = UITextView()
tw.font = Font.kohinoorTeluguMedium_20
tw.returnKeyType = .default
tw.isScrollEnabled = true
tw.keyboardDismissMode = .interactive
tw.translatesAutoresizingMaskIntoConstraints = false
return tw
}()
fileprivate func setupKeyboardToolbar() {
let keyboardToolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
keyboardToolBar.translatesAutoresizingMaskIntoConstraints = false
keyboardToolBar.sizeToFit()
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDoneButton))
keyboardToolBar.items = [doneButton]
self.textView.inputAccessoryView = keyboardToolBar
textView.delegate = self
// self.textView.autoresizingMask = .flexibleHeight
keyboardToolBar.layoutIfNeeded()
keyboardToolBar.isHidden = false
}
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardToolbar()
tableView.register(DetailCell.self, forCellReuseIdentifier: cellId)
tableView.backgroundColor = AdaptiveColors.tableViewBackgroundColor
tableView.separatorColor = AdaptiveColors.tableViewBackgroundColor
setupDismissButton()
let editButton = UIBarButtonItem(title: LocalizedString.edit, style: .plain, target: self, action: #selector(handleEdit))
editButton.tintColor = .white
navigationItem.rightBarButtonItem = editButton
}
#objc func handleDoneButton() {
let context = CoreDataManager.shared.persistentContainer.viewContext
lesson?.notes = textView.text
do {
try context.save()
} catch let savErr {
print("Error saving context \(savErr)")
}
textView.resignFirstResponder()
}
func textViewDidChange(_ textView: UITextView) {
tableView.beginUpdates()
tableView.endUpdates()
}
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
return true
}
}
After reading relevant stackOverflow answers, I have tried calling layoutIfNeeded() and isHidden = false on keyboardToolbar, with no luck. Also gave a try to set self.textView.autoResizingMask = .flexibleHeight, with no luck. Also tried to set keyboardToolbar's delegate to self, and declaring self as UIToolbarDelegate.
Thanks anyone for helping!
Ok, I have fixed it, it was a stupid mistake I overlooked at some point.
I had some issues with auto layout, so I moved this textView object to my custom cell class, instead of the view controller. Then I was talking to that textView in cellForRow at indexPath method of my VC, but i forgot to setup the toolbar in the cell class. I still had the textView and toolbar setup in the view controller.
I have fixed this problem by moving the setup in the custom cell lass, then making a delegate pattern between the custom cell and the vc, to make note-saving possible.
I have a very simple web view project which just loads a website. There is a bug though, the website content is showing in the status bar if I scroll down.
I read that this is because the background color of the ViewController is set to be transparent, how can I change it to another color?
I tried it like this:
let color = UIColor.black;
self.view.backgroundColor = color
But nothing changes
Whole Code:
ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
#IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// 1 The webView loads the url using an URLRequest object.
let url = URL(string: "https://www.blizz-z.de/")!
webView.load(URLRequest(url: url))
// 2 A refresh item is added to the toolbar which will refresh the current webpage.
let refresh = UIBarButtonItem(
barButtonSystemItem: .refresh,
target: webView,
action: #selector(webView.reload)
)
toolbarItems = [refresh]
navigationController?.isToolbarHidden = true
navigationController?.isNavigationBarHidden = true
}
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
let color = UIColor.black;
self.view.backgroundColor = color
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
title = webView.title
}
}
I could just disable the status bar with the following code to fix the bug, but I try to keep it:
override var prefersStatusBarHidden: Bool {
return true
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let height = UIScreen.main.bounds.height
let width = UIScreen.main.bounds.width
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: width, height: height))
webView.load(URLRequest(url: (URL(string: "https://www.blizz-z.de/")!)))
view.addSubview(webView)
UIApplication.shared.statusBarView?.backgroundColor = #colorLiteral(red: 0.9529411765, green: 0.9529411765, blue: 0.9529411765, alpha: 1)
}
}
extension UIApplication {
var statusBarView: UIView? {
if responds(to: Selector("statusBar")) {
return value(forKey: "statusBar") as? UIView
}
return nil
}
}
Make the webView.top constraint relative to safeArea.top constraint, and not the superview.top constraint. This allows you to make your webView under the status bar.
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.
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