UINavigationBar overlaps UITableView when programmatically setting prompt - ios

I have a UINavigationController which contains a UITableViewController. This navigation controller pushes other UITableViewControllers around and eventually these table view controllers will have a prompt.
The problem is when I set this prompt programmatically it overlaps the content of the table view underneath it.
(A search bar is being hidden by the navigation bar)
I was looking around in SO and found this answer. I tried the suggestion there in two different ways in the affected view controller but nothing changed:
override func viewDidLoad() {
super.viewDidLoad()
self.edgesForExtendedLayout = .None;
self.extendedLayoutIncludesOpaqueBars = false;
self.navigationItem.title = NSLocalizedString("Add Anime or Manga", comment: "")
self.navigationItem.prompt = NSLocalizedString("Search media belonging to this series.", comment: "")
}
-
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = NSLocalizedString("Add Anime or Manga", comment: "")
self.navigationItem.prompt = NSLocalizedString("Search media belonging to this series.", comment: "")
self.edgesForExtendedLayout = .None;
self.extendedLayoutIncludesOpaqueBars = false;
}
A comment in that same answer linked to this Apple guide on preventing views from overlapping each other. The problem is UITableViewController doesn't appear to have top/bottom layout guides so I can't create a constraint (another SO answer says having said layouts in table view controllers is irrelevant).
As such I have exhausted all my options.

I have tried to reproduce your problem and it seems that when not all the viewControllers have a prompt the navigationBar is somehow not resizing properly.
It seems you need to somehow trigger the layouting for the UINavigationController. The only way I could make it work properly was by adding this in viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES];
[self.navigationController setNavigationBarHidden:NO];
}
Maybe this prompt is meant to be used consistently across the entire application (meaning having one for all viewControllers or none of them), that's why the UINavigationController does not layout it's subviews when it changes.
Hope this works for you too.

Select your TableViewController from document outline and change the value to translucent navigation bar of top bar in attributes inspector. Be sure that you will not select uitableview you should select your your table view controller(aka File's Owner) from document outline.

You have to set prompt only if view did appear, then it works:
override func viewDidAppear(_ animated: Bool) {
navigationItem.prompt = "your prompt here"
}

It's 2019 and this is still not fixed. Slow Clap. I refuse to be cowed by such things so I hammered iOS into submission with the dirtiest trick in the book. I fixed this by doing a disgusting -44 "y trick" on the UINavigation while placing the UITableView in top 44, I know it's stupid, but it works.. I am sure new fangled phones will ruin my genius.. but hey ho.. I have lazily left irrelevant code (because I am idle) but hopefully you can see what I did.
WITHOUT THE y: -44 Hack
WITH THE y: -44 Hack
let screenSize: CGRect = UIScreen.main.bounds
let navBar = UINavigationBar(frame: CGRect(x: 0, y: -44, width: screenSize.width, height: 44)) //<<--note minus 44
navBar.barTintColor = Globals.Color_BackgroundGrey()
navBar.isTranslucent = false
tableView.contentInset = UIEdgeInsets(top: 44, left: 0, bottom: 0, right: 0); //<--note plus 44
self.edgesForExtendedLayout = []
let navItem = UINavigationItem(title: "Boaty Mc Boatface")
let doneItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(done))
navItem.rightBarButtonItem = doneItem
navBar.setItems([navItem], animated: false)
view.addSubview(navBar)

Related

Subviews in presented ViewController gets wrong position after rotation

I'm experimenting with UISearchController, but I can't get it right. I present a clean UISearchController from my own UIViewController, and it looks great - but as soon as I rotate the device, it gets shifted a few points up or down.
To recreate this, just do these few steps:
Create a new Single View project
Delete Main.storyboard from the project files and remove its name from the project settings (Project -> General -> Target -> Main Interface)
In AppDelegate.swift:
application didFinishLaunchingWithOptions...{
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
return true
}
ViewController.swift:
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 20, y: 200, width: 100, height: 40))
button.setTitle("Search", for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(click), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func click(){
self.present(UISearchController(searchResultsController: nil), animated: true, completion: nil)
}
}
And that's it. This is what I'm seeing:
Presenting the search when the device is in portrait mode looks great in portrait - but if you rotate the device to landscape while presenting the searchbar, it will be wrongly positioned, a few pixels above the top of the screen.
Presenting the search when in landscape will yield the opposite. It looks great in landscape, but when rotating it to portrait the entire search controller view will be pushed down a few pixels.
It's not a matter of height size on the bar. The entire bar gets pushed up/down.
I tried investigating a bit further. I presented the search controller from landscape mode and rotated to portrait, and then debugged the view hierarchy:
To be honest, I'm not quite sure what I'm looking at. The top-most view is a UISearchBarBackground embedded within a _UISearchBarContainerView, which is within a _UISearchControllerView.
As you can see in the size inspector on the right side, the middle-view "container" has y: 5, which makes no sense. When debugging the correct state, y is 0. What's really interesting is that the top-most view has y: -44 in this corrupt situation, and it has the same when it's correct - but you can clearly see that there is some space leftover above it. There seem to be three different y-positions. I don't get it.
I've read some guides on how to implement a UISearchController, but literally every single example I find is people modally presenting a custom ViewController that contains a SearchController. This will result in the entire custom ViewController being animated up from below.
Since the UISearchController is a subclass of UIViewController, I wanted to test out presenting it directly, not as part of a regular UIViewController. This gives the cool effect that the searchBar animates in from above, and the keyboard from below.
But why doesn't this work?
Edit: I just found out that if I enable Hide status bar in the project settings, the UISearchController looks even more correct in landscape than the "correct state" from above, and even animates correctly to portrait. It's super weird, because the status bar doesn't change at all. It was never visible in landscape. Why does this happen? It seems so buggy. Look at these three states:
The first state is when showing search controller from portrait then rotating to landscape (doesn't matter if Hide status bar is enabled or not.
The second state is when showing search controller from landscape if Hide status bar is false
The third state is when showing search controller from landscape if Hide status bar is true.
As stated in the documentation:
Although a UISearchController object is a view controller, you should
never present it directly from your interface. If you want to present
the search results interface explicitly, wrap your search controller
in a UISearchContainerViewController object and present that object
instead.
Try to wrap your UISearchController inside a UISearchContainerViewController.
How about make a custom view contains search bar.
Make it on top of the layers.
And when rotate occurs update frame/constraints of it.
This is covered in guidelines for designing for iPhone X. Now Navigation bar contains search controller. You can instanciate the search controller and set it to navigationItem.searchController. Now it will handle the search controller while rotation.
var search = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = search
You can do this tric
class ViewController: UIViewController {
let sc = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let button = UIButton(frame: CGRect(x: 20, y: 200, width: 100, height: 40))
button.setTitle("Search", for: .normal)
button.backgroundColor = .black
button.addTarget(self, action: #selector(click), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func click(){
self.present(sc, animated: true, completion: nil)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
var mFrame = sc.view.frame
mFrame.origin.y = 5.0
sc.view.frame = mFrame
}

How to make search bar shrinking effect like default mailbox

How make animation like ios default mail. I need the same effect like search bar is hide at initial and when i drag table view download the it shows the search bar.
like in the screenshot
update to the question.- Right now i am using a searchBar above tableview in the view controller.
var resultSearchController = UISearchController()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
workActivityTableView.tableHeaderView = controller.searchBar
workActivityTableView.contentOffset = CGPoint(x: 0, y: controller.searchBar.frame.height)
navigationController?.extendedLayoutIncludesOpaqueBars = true
return controller
})()
Drag search bar in to your tableview in your storyboard as it should be the first subview of your tableview!
Tableview will consider it as a header.
Your view hierarchy will be look like,
And the default size of search bar will be 44.
Now in your viewDidload set content offset of your table view like,
_tblView.contentOffset = CGPointMake(0, 44);
or in swift you can say,
tblView.contentOffset = CGPoint(x: 0, y: 44)
and you're done!
bellows are screenshot of results,
Initially
After scroll
This will not work if prefersLargeTitles set to true then initially you will get search bar!
I'd suggest you add the search bar as a tableHeaderView to your tableView, and in your viewDidLoad, add the following lines:
tableView.tableHeaderView = your_searchbar
tableView.contentOffset = CGPoint(x: 0, y: your_searchar.frame.height)
navigationController?.extendedLayoutIncludesOpaqueBars = true
UPDATE:
This can be easily achieved by adding the search controller as a part of navigationItem. Something like this:
self.navigationItem.searchController = searchController
This is available from iOS 11.

Top layout guide moves underneath navigation bar after pushing search results

This one has kept be up a few days. I think the easiest way to illustrate my issue is with this 10 second gif.
Description
As you can see, when you go back after pushing to a search result, the segmented top bar disappears when you go back, but when I switch to the tab with "Tise" in the header and back, it resets and works properly.
As seen in the below screenshot, my issue is that the top layout guide gets offset underneath the navigation bar, which makes the segmented bar hide underneath it.
Code
My search controller is an extremely normal implementation.
/* Search controller */
let exploreSearchController = StandaloneExploreSearchResultController.initFromStoryboard(type: .top)
searchController = UISearchController(searchResultsController: exploreSearchController)
searchController?.searchResultsUpdater = exploreSearchController
searchController?.delegate = self
searchController?.searchBar.searchBarStyle = .minimal
searchController?.searchBar.isTranslucent = false
searchController?.searchBar.backgroundColor = .clear
searchController?.hidesNavigationBarDuringPresentation = false
searchController?.dimsBackgroundDuringPresentation = false
searchController?.searchBar.tintColor = #colorLiteral()
navigationItem.titleView = searchController?.searchBar
definesPresentationContext = true
and I push the result controllers in StandaloneExploreSearchResults with
presentingViewController?.navigationController?.pushViewController(viewController, animated: true)
For every new view controller, I update the navigation bar style, which I suspect might be triggering it. If I disable this function, I get different offset bugs, as seen in the following gif.
func updateNavigationBarStyle(viewController: UIViewController) {
let style = (viewController as? NavigationControllerStyling)?.preferredStyle() ?? .default
print(#file.components(separatedBy: "/").last!,":",#line,"-",#function, viewController.classForCoder)
//Showhide
UIApplication.shared.setStatusBar(hidden: style.statusBar.hidden, style: style.statusBar.style)
setNavigationBarHidden(style.hidden.hidden, animated: style.hidden.animated)
navigationBar.setCustomShadow(hidden: style.shadowHidden)
//Colors
navigationBar.tintColor = style.tintColor
navigationBar.barTintColor = style.barTintColor
navigationBar.backgroundColor = style.backgroundColor
navigationBar.setBackgroundImage(style.backgroundImage, for: .default)
navigationBar.titleTextAttributes = style.titleTextAttributes
navigationBar.isTranslucent = style.translucent
navigationBar.hairlineImageView?.isHidden = true //Always hide the native shadow
}
Question
Is there a way to either force the auto layout reload that switching tabs triggers (I've tried all the methods I could find with no success). or fix the bug somehow?
Well, that solved it. I guess setting the colors and/or images somehow fiddled with the opaque property.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
extendedLayoutIncludesOpaqueBars = true
}
Try this code in viewDidLoad -
self.edgesForExtendedLayout = []

iOS adjust UICollectionView Insets after hiding Navigation Bar

#IBAction func searchOn() {
self.searchController.searchResultsUpdater = self
self.searchController.hidesNavigationBarDuringPresentation = true
self.searchController.dimsBackgroundDuringPresentation = false
self.definesPresentationContext = true
self.present(searchController, animated: true, completion: nil)
}
What I want to do here is to hide navigation bar when presenting Search bar and place cells in correct position, because now they are going under search bar.
One of the solutions I was able to think of is just to animateWithDuration UIEdgeInsetsMake downwards same distance as they get moved up when navigation bar hides. But the problem is that I don't know the duration of the animation between switching navigation and search bars.
Can anyone help figure this out?
Or at least help me to get the duration of animation which switches between navigation and search bars.
write this code in viewDidLoad() method.
let flow: UICollectionViewFlowLayout = CollectionView.collectionViewLayout as! UICollectionViewFlowLayout
flow.sectionInset = UIEdgeInsetsMake(70, 0, 0, 0)
one more way is to try setting the contentOffset to required position.and change the y position to move up and down, u can give negative values also
for example,
//without animation
collectionview.contentOffset = CGPoint(x: 0, y: 100);
//with animation
collectionview.setContentOffset(CGPoint(x: 0, y: 100), animated: true)
and also u can take advantage of sectionInset also as Chirag Patel mentioned

Swift - toolbar follows tableview when scrolling

In my UITableViewController, my toolbar follows my tableview when I scroll it. My code looks like this:
override func viewDidLoad() {
let toolbar: UIToolbar = UIToolbar()
let checkButton = [UIBarButtonItem(title: "Done", style: .Done, target: self, action: "checkedPress")]
toolbar.frame = CGRectMake(0, self.view.frame.size.height - 46, self.view.frame.size.width, 48)
toolbar.sizeToFit()
toolbar.setItems(checkButton, animated: true)
toolbar.backgroundColor = UIColor.redColor()
self.view.addSubview(toolbar)
}
and it looks like this when I run the app:
I want the toolbar to stick to the bottom of the view, how is this achieved?
Any suggestions would be appreciated.
The problem is that since you're using a UITableViewController, with self.view.addSubview(toolbar), you've added your toolbar as a subview of your UITableViewController's view, i.e. a UITableView. As a subview of the UITableView, the toolbar will scroll along with the table.
The solution: Use a UIView containing a UITableView instead of using a UITableViewController if you'd like to customize your view controller. That way you can add elements to the view that aren't subviews of your tableview.
You can also embed your TableViewController in a Navigation Controller and show from the storyboard or programmatically a Toolbar. This one also standard sticks to the bottom and stays on top of the Views content and you have some functionality to hide it automatically on some conditions.
You don't have to use your Navigation controller always for navigating, sometimes its a convenient way for doing stuff Xcode makes hard to use without changing your already made Views.

Resources