Currently, my view hierarchy consists of a UIViewController (NOT UITableViewController), a UITableView nested in the view controller and a UIVisualEffectView (set to Extra Light) in front of the UITableView, aligned to the bottom of a UINavigationBar. The effect I want to achieve is somewhat similar to that of the App Store's segmented view.
However, I noticed a weird blur edge occurring at the boundary between the navigation bar and the UIVisualEffectView that makes the view look inconsistent, as pictured below (highlighted by the red circle):
Optimally, I would prefer that the UIVisualEffectView blends perfectly with the UINavigationBar's blur.
Thanks.
Try to use a UIToolBar instead of a UIVisualEffectView as the background of the segment. The navigation bar has translucent background rather than blur effect. UIToolBar has the same translucent background as navigation bar, so it would look seamless at the edge.
Looking to your picture it seems your issue is not attributable to UINavigationBar but to a view where you have added UISegmentedControl.
I don't know your structure but it could be the tableHeaderView (self.tableView.tableHeaderView) so a reasonable way to solve this problem is to change the header color:
Code example:
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
var headerView: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.backgroundColor = UIColor.clearColor()
return header
}
Related
I added the UINavigationBar with a large title in it. After that, I added a UITableView to this UIViewController. I gave the large title content mode to .always. But when I scroll the UITableView it hides and showed in the top of theUINavigationBar`
navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.largeTitleDisplayMode = .always
If you want to keep your large title while scrolling your UITableView, then add a UIView on top of your UITableView, like:
Problem
A navigation bar animates as it hides on scroll (hidesBarsOnSwipe). If there is a table on the page with a section header, the section header will stick to the top of the view and follow the user as they scroll. When using these two together, there is a gap between the section header and the navigation bar when the navigation bar is hiding, showing the background table cells. I would expect the section header to follow the navigation bar as it hides without a gap.
Using a section header is not strictly necessary for my issue. I'd simply like a bar that sticks to the top of the screen and follows the user as they scroll.
I've tried alternative approaches to the example posted below, which uses a UITableViewController. Adjusting the insets causes the section header to slip under the status bar. Using a UIViewController containing a static UIView (for the section header) and UITableView causes the same issue.
What would be an appropriate solution for this issue?
Example
The status bar's background is gray and the section header's background is red to better highlight the gap between the navigation bar and the section header. As the table is slowly scrolled down, the yellow table cells peeking through the gap become apparent. After the navigation bar is hidden, the section header resets on the status bar as expected.
View controller used for the above animation:
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Color status bar background.
let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as! UIView
if statusBar.responds(to: #selector(setter: UIView.backgroundColor)) {
statusBar.backgroundColor = .lightGray
}
UIApplication.shared.statusBarStyle = .default
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .red
return view
}
}
Update 1
Removing the background color from the status bar does not resolve the issue I am seeing. The GIF below shows the behavior with the status bar in its original, transparent state.
TLYShyNavBar has a GIF of the behavior I’d like to implement (added below). This project is not maintained and does not work without additional manipulation but has a good example of what I would expect the behavior to look like.
Update 2
I’m understanding that this behavior is not an error/issue but as Apple prefers it. Coloring the background of the View is an option (see answer below). There are two ways to go about that for this situation:
Color the background to match the navigation bar and remove the navigation bar’s shadow to make the view seamless.
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.isTranslucent = false
Color the background to match the red bar.
Unfortunately, this method masks the gap instead of removing it. It makes one of the bars appear to distort in shape during their animation until they've completed their transition in both cases.
The problem is that a table view does not work well fullscreen when there is a status bar, because the cells come up behind the status bar but the section headers do not. So don't do that.
To use a table view controller to work without a navigation bar but with the status bar showing, configure the table view controller as an embedded (custom child) view controller so that you are in charge of the table view's position. Pin the table view top to the safe area top.
That gives the configuration you are after:
The status bar is clear, and the parent view controller's view is visible behind it. The table view itself, along with its section headers and cells, stops at the bottom of the status bar; the cells do not come up behind the status bar. Thus we don't get the mismatch seen in your illustrations.
Here's a gif showing that the result is coherent:
I've colored the nav bar and the main view differently so you can distinguish them, but imagine they were exactly the same color — then this would be totally coherent.
Typically, translucent UINavigationBars have a light gray color above a white background.
However, many navigation bars throughout iOS 11 have a white color.
For example, navigation bars in the Files app are white AND translucent which are noticeably different from setting barTintColor to white.
How do I achieve this kind of effect on a UINavigationBar?
Set the barTintColor of the navigation bar to white.
After that, subclass UINavigationBar and set the shadow image to an empty UIImage.
class CustomNavBar: UINavigationBar {
override func awakeFromNib() {
super.awakeFromNib()
shadowImage = UIImage()
}
}
Finally, set the class of the navigation bar to the custom navigation bar class you just created.
Result
I was trying to build an app with the same style as the Files app, but ended up with the same problem.
It seems that when barTintColor is non-nil, UINavigationBar will disable the translucency effect, and there doesn't seem to be a public way to force it back on.
I think there is a reasonable way to get this effect though: you could replace the background view of the navigation bar with your own UIVisualEffectView.
It's possible to suppress both the background view, and the separator view of a navigation bar by providing them with dummy UIImage objects.
navigationBar.setBackgroundImage(UIImage(), for: .default)
navigationBar.shadowImage = UIImage()
You would need to create your own UINavigationBar subclass, suppress these two views, and then add your own UIVisualEffectView subview and position it to always sit at the bottom of the view stack.
It's not an amazing solution, but it should hopefully create the desired effect with minimal internal hacking of the navigation bar class.
So I've spent a huge amount of time researching this, and I've found the answer, but beware: it's very, very hacky.
So, the view that's responsible for creating this gray glow on the navigation bar is something called _UIVisualEffectBackdropView. More specifically, this view's layer has four filters attached (all of them are private CAFilters), and first of them, named luminanceCurveMap, creates this gray color for the navigation bar. So my solution is to
Find _UIVisualEffectBackdropView in the view hierarchy of UINavigationBar
Remove luminanceCurveMap filter of of its layer.
Here's the function I created to find _UIVisualEffectBackdropView in the hierarchy:
extension UIView {
fileprivate static func findRecursively(typeName: String, in view: UIView) -> UIView? {
let typeOf = type(of: view)
if String(describing: typeOf) == typeName {
return view
} else {
for subview in view.subviews {
if let found = UIView.findRecursively(typeName: typeName, in: subview) {
return found
}
}
return nil
}
}
}
And then override viewDidAppear in your custom UINavigationController subclass (only viewDidAppear, viewWillAppear for example didn't work):
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let backdrop = UIView.findRecursively(typeName: "_UIVisualEffectBackdropView", in: navigationBar) {
let luminanceCurveMapIndex = backdrop.layer.filters?.firstIndex { filter in
if let caFilter = filter as? NSObject, let name = caFilter.value(forKey: "name") as? String {
return name == "luminanceCurveMap"
} else {
return false
}
}
if let index = luminanceCurveMapIndex {
backdrop.layer.filters?.remove(at: index)
}
}
}
I know it's a lot, but that's the I came out with. It preserves all native translucency behavior while giving me the look I needed.
I am trying to hide the tab bar during the push to UserProfileViewController from a viewcontroller that has tabBar underneath.
code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let toVC = UIStoryboard(name:"UserProfile", bundle:nil).instantiateViewController(withIdentifier: "userProfileVC") as? ProfileViewController else { return }
...
toVC.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(toVC, animated: true)
}
so the code works fine and it does hide the bottom bar on push. However, right after the push, it gives me a slight delay and shows a blank white space underneath because of the hidden tab bar for like a second or two. I want to get rid of the bottom white space on push. Please help.
I have already tried:
ticking Hide Bottom Bar On Push in storyboard
ticking Under Opaque Bars On Push in storyboard
Layouts & Extend Edges options ticked in storyboard:
Adjust Scroll View Insets
Hides Bottom Bar On Push
Under Top Bars
Under Bottom Bars
Under Opaque Bars
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let memeShowViewController = self.storyboard!.instantiateViewControllerWithIdentifier("ShowMeme") as! MemeShowViewController
memeShowViewController.memeImage = appDelegate.memes[indexPath.row].memeImage
self.navigationController!.presentViewController(memeShowViewController, animated: true, completion: nil)
}
So I have a tableview that has a nav controller embedded in it and when I present the memeShowViewController modally I was wondering why the view of the memeShowViewController has a black background color when I didn't change it to be black.
In storyboard:
In simulator
Right now I'm just adding a navigation bar to the segue-ed view controller, I was wondering if there is a better way to do as the only functionality I need from the navigation bar is for the done button to segue back to the tableView.
The background window has a black background so if your app doesn't cover any part of the window (or alpha = 0) you'll see that black background.