Swift - "navigationController?.pushViewController" doesn't work - swift

I have 3 ViewControllers like below.
A: HomeController,
B: NavController1,
C: NavController2
I set a custom navigation bar in B and C and I use "navigationController?.pushViewController" for the segues From A to B to C.
These are the codes.
Custom navigation bar:
class CustomNavBar: UINavigationBar {
let navBarView: UIView = {
let view = UIView()
view.backgroundColor = .black
// Logo
let logo = UIImageView(image: <image-file>)
view.addSubview(logo)
logo.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logo.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
return view
}()
public func setupNavBar() {
if let window = UIApplication.shared.keyWindow {
window.addSubview(navBarView)
navBarView.topAnchor.constraint(equalTo: window.topAnchor, constant: 0).isActive = true
navBarView.leftAnchor.constraint(equalTo: window.leftAnchor, constant: 0).isActive = true
navBarView.rightAnchor.constraint(equalTo: window.rightAnchor, constant: 0).isActive = true
}
}
}
And in the ViewControllers B and C I put these codes.
class NavController1: UIViewController {
let navBar = CustomNavBar()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
navBar.setupNavBar()
}
override func viewWillDisappear(_ animated: Bool) {
loginHeader.headerBg.isHidden = true
loginHeader.backButton.isHidden = true
}
}
And for the segues, I used the code below with a UIButton action.
self.navigationController?.pushViewController(NavController1, animated: true)
It works with the segue from A to B but it doesn't work with B to C.
Can someone point out what the problem is? Thank you!

UINavigationController is derived from UIViewController, which means it has a navigationController property. Thing is, this will be nil on the actual UINavigationController because it doesn't have a navigation controller, it is the navigation controller. Because you are using optional chaining, the message is never getting sent because navigationController is nil. Try sending the message to self instead (given that self is of type UINavigationController, and the current one).
I.E. self.pushViewController(NavController1, animated: true)

Related

Can't tap the button if I add UIHostingController as a child (its view opaque is set to false)

View Hierarchy
ViewController
HostingViewController
Discussion
At viewDidLoad's ViewController, it will add the UIHostingController and set the isOpaque property to false because user can tap the button that is in ViewController.
Unfortunately, I won't able to tap the button
let ipView = UIHostingController(rootView: InputView())
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
addChild(ipView)
view.addSubview(ipView.view)
// It's can't tap the button in ViewController
// even I set the isOpaque = false
ipView.view.backgroundColor = .clear
ipView.view.isOpaque = false
ipView.view.translatesAutoresizingMaskIntoConstraints = false
ipView.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
ipView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
ipView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
ipView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
The button should be triggered the tapped event, but it's not if I add UIHostingController
#IBAction func buttonDidTap(_ sender: UIButton) {
sender.setTitle("Tapped", for: .normal)
}

Swift: Make the background image of UITableViewController embedded in Navigation Controller fill the entire screen

I managed to create translucent and rounded UITableViewCells in a UITableViewController that is embedded inside a Navigation Controller with this line of code in viewDidLoad():
tableView.backgroundView = UIImageView(image: UIImage(named: "nightTokyo"))
But I want the background image to fill the entire phone screen. I changed the code (and only this line of code) to:
navigationController?.view = UIImageView(image: UIImage(named: "nightTokyo"))
Now the background image fills up the entire phone screen, but my table and even the iPhone's time and battery indicator icons are missing.
What I want is for the background image to fill the entire screen, but the tableView, its cells, the iPhone time, battery level icon, etc. to remain displayed.
navigationController?.setNavigationBarHidden(true, animated: true)
Here is what I did which worked for me using Swift 5, XCode 12.
Step 1 (Optional) - Create a custom UINavigationController class
class CustomNavigationController: UINavigationController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationBar.isTranslucent = true
}
Replace your UINavigationController with this UINavigationController subclass. I mark this as optional as this is based on preference, if you do not set this, your navigation bar will be opaque and you cannot see what's beneath it.
Setting the navigationBar.isTranslucent = true allows you to see the background beneath it which is what I like. A subclass is also optional but you might need to make other updates to your nav bar so I always like to make this a subclass.
Step 2 - Set up your background view constraints
class CustomViewController: UIViewController {
// your background view
let bgImageView: UIImageView = {
let bgImageView = UIImageView()
bgImageView.image = UIImage(named: "gradient_background")
bgImageView.contentMode = .scaleAspectFill
return bgImageView
}()
// Get the height of the nav bar and the status bar so you
// know how far up your background needs to go
var topBarHeight: CGFloat {
var top = self.navigationController?.navigationBar.frame.height ?? 0.0
if #available(iOS 13.0, *) {
top += UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
} else {
top += UIApplication.shared.statusBarFrame.height
}
return top
}
var isLayoutConfigured = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
title = "Site Visit"
// you only want to do this once
if !isLayoutConfigured() {
isLayoutConfigured = true
configBackground()
}
}
private func configBackground() {
view.addSubview(bgImageView)
configureBackgroundConstraints()
}
// Set up your constraints, main one here is the top constraint
private func configureBackgroundConstraints() {
bgImageView.translatesAutoresizingMaskIntoConstraints = false
bgImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor,
constant: -topBarHeight).isActive = true
bgImageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 0).isActive = true
bgImageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: 0).isActive = true
bgImageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: 0).isActive = true
view.layoutIfNeeded()
}
Before setting constraints:
After setting above constraints:

Cant change colour of searchBar and it jumps to top of view when selected

I'm trying to attach a search bar to the top of my tableView and change its attributes (eg. colour, placeholder). However, I can't figure out how. I've tried embedding the tableView in another view but that didn't help. Any ideas?
func setupSearch(){
search.delegate = self
search.automaticallyShowsCancelButton = false
search.searchBar.tintColor = UIColor.red
search.searchBar.barTintColor = UIColor.red
search.obscuresBackgroundDuringPresentation = false
search.hidesNavigationBarDuringPresentation = false
search.searchBar.placeholder = "Type something here to search"
navigationItem.searchController = search
tableView.tableHeaderView = search.searchBar
}
This function is called in the viewDidLoad() and the tableView is added but not with the right colour or placeholder and jumps to the top of the screen when selected.
Any help would be appreciated.
This is the updated code for setupSearch (everything is working fine except the bar jumps to the top when selected):
func setupSearch(){
search.delegate = self
search.automaticallyShowsCancelButton = false
search.searchBar.barTintColor = UIColor.red
search.obscuresBackgroundDuringPresentation = false
search.hidesNavigationBarDuringPresentation = false
tableView.tableHeaderView = search.searchBar
}
I declare the search bar at the start using:
let search = UISearchController(searchResultsController: nil)
Any ideas on how to stop the bar jumping to the top?
Just add search bar as tableview's headerview not with navigation's item searchcontroller(not add search bar with both(tableview and navigation) as in your code). You can try with updated code below:
func setupSearch(){
search.delegate = self
search.automaticallyShowsCancelButton = false
search.searchBar.barTintColor = UIColor.red
search.obscuresBackgroundDuringPresentation = false
search.hidesNavigationBarDuringPresentation = false
search.searchBar.placeholder = "Type something here to search"
tableView.tableHeaderView = search.searchBar
self.definesPresentationContext = true
}
override func viewDidLoad() {
super.viewDidLoad()
setupSearch()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) // be notified when the keyboard changes your table View frame
}
#objc func keyboardWillShow(notification: NSNotification) {
tableView.frame.origin.y = 0 // reset the table view to its original coordinates
}
func setupSearch(){
// for iOS 12 and lower, you can change the placeholder like this :
let textFieldSearchBar = searchBar.value(forKey: "searchField") as? UITextField
textFieldSearchBar?.textColor = .red
let searchBarLabel = textFieldSearchBar!.value(forKey: "placeholderLabel") as? UILabel
textFieldSearchBarLabel?.textColor = .red
}

TableView Content behind TabBar

I've searched through S/OF and can't find a fix for the TableView being behind my TabBar.
I set up my TableView like this;
func setUpTableView() {
messagesTableView.frame = view.frame
messagesTableView.backgroundColor = .white
view.addSubview(messagesTableView)
messagesTableView.tableFooterView = UIView()
messagesTableView.delegate = self
messagesTableView.dataSource = self
messagesTableView.register(UITableViewCell.self, forCellReuseIdentifier: messagesCellIdentifier)
edgesForExtendedLayout = []
extendedLayoutIncludesOpaqueBars = false
messagesTableView.contentInsetAdjustmentBehavior = .never
}
Then I setUpTableView() in viewDidLoad().
According to all sources
edgesForExtendedLayout = []
extendedLayoutIncludesOpaqueBars = false
messagesTableView.contentInsetAdjustmentBehavior = .never
Should satisfy the content insets and not allow the TableView to scroll behind the TabBar.
Please note my TabBars' translucency is set to false inside TabBarController.
tabBar.isTranslucent = false
As always any help appreciated.
Adding Illustration
When scrolling to the bottom is not showing the complete TableView content as the TabBar covers the last few indexes.
Did you try this in viewDidAppear ?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
}
Edit
Remove
messagesTableView.frame = view.frame
and add autoLayout to your messagesTableView
messagesTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
messagesTableView.topAnchor.constraint(equalTo: topAnchor),
messagesTableView.leftAnchor.constraint(equalTo: leftAnchor),
messagesTableView.bottomAnchor.constraint(equalTo: bottomAnchor),
messagesTableView.rightAnchor.constraint(equalTo: rightAnchor)
])

Swift make label overlap status bar

I have this label in Swift
let top_error_rep: UILabel = {
let lb = UILabel()
lb.text="Password should be at least 6 charachters"
lb.textColor = UIColor(r: 230, g: 230, b: 230)
lb.backgroundColor = .red
return lb;
}()
I show it only when user input is less than 6 characters. Now when I show it status bar overlaps the input, how can I prevent that?
This is how it looks like
Set label's position.
After you add the label to subViews of your view. You need to set it's position by using constraint like this:
top_error_rep.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
top_error_rep.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
top_error_rep.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
top_error_rep.heightAnchor.constraint(equalToConstant: 20).isActive = true
or you can use different way. Google for Auto layout programmatically swift
Make it only show when user input is less than 6 characters
How do I check when a UITextField changes?
This may helps you
EDIT
add this to your ViewController to hide the status bar
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//Add below line.....
UIApplication.shared.isStatusBarHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//It will show the status bar again after dismiss
UIApplication.shared.isStatusBarHidden = false
}
override var prefersStatusBarHidden: Bool {
return true
}