UINavigationBar & UIToolBar do not share the same appearance effect - swift

I am trying to replicate the visual effect that apple uses with several of its applications where the navigation bar and a toolbar are combined. Here is an example:
The peculiarity is that the two have and share a somewhat transparent background effect, say .configureWithDefaultBackground() (In the picture you can see how the navigation bar and the toolbar share the same background blur effect)
Trying to replicate the design, when the content of the collection has not yet started to scroll, the background color of the toolbar does not behave like the navigation bar and when it does start scrolling, the effect is shared. Here are some images
Trying to replicate the design, when the content of the collection has not yet started to scroll, the background color of the toolbar does not behave like the navigation bar and it has a "grey" background color while in the apple one, before you start to scroll, both are white (or .systemBackground)
When you start scrolling, the two share the same background effect and behave as expected.
Here's some code:
private func configureNavBar() {
let navigationBarAppearanceStandard = UINavigationBarAppearance()
navigationBarAppearanceStandard.configureWithDefaultBackground()
let navigationBarAppearanceScrollEdge = UINavigationBarAppearance()
navigationBarAppearanceScrollEdge.configureWithOpaqueBackground()
navigationController?.navigationBar.standardAppearance = navigationBarAppearanceStandard
navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearanceScrollEdge
navigationController?.navigationBar.standardAppearance.shadowColor = .clear
navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = .clear
}
func configureToolbar() {
let toolbarAppearance = UIToolbarAppearance()
toolbarAppearance.configureWithTransparentBackground() // .default & .opaque behave the same, gray color before scrolling
toolbar.scrollEdgeAppearance = toolbarAppearance
toolbar.compactAppearance = toolbarAppearance
toolbar.delegate = self
toolbar.translatesAutoresizingMaskIntoConstraints = false
}
private func layoutUI() {
view.addSubview(collectionView)
view.addSubview(toolbar)
NSLayoutConstraint.activate([
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor),
toolbar.heightAnchor.constraint(equalToConstant: 50),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
collectionView.contentInset.top = 50
collectionView.verticalScrollIndicatorInsets.top = 50
}
Tried self.toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .top, barMetrics: .default), but makes it transparent and not blurry.
Does anyone know why the toolbar is grayed out and not the same color as the navigation bar before you start scrolling the collectionView?

Related

Why does NSScrollView display a white track?

I am programming with macos, in swift. I have a few paragraphs of text (Lopem Ipsum - to test) inside a scroll view. Also a title, which is a NSTextView, positioned above.
When I present it in a popover, all looks fine.
However in another part of the project, I present a the same contents (using a copy of the same layout code - below), but this time in a modal window.
It has a ugly white track! That must be connected to the scroll view, since it only appears when I set the vertical scrollbar - hasVerticalScroller, which is necessary because I want vertical scrolling. As it should, the knob highlights on mouse-hover and it scolls properly.
It probably would be fine with a solid white background. But that's not what I want! Thus I have the setting scrollView.drawsBackground = false.
I've tried: scrollView.verticalScroller?.highlight(false); scrollView.verticalScroller?.wantsLayer = true; scrollView.verticalScroller?.layer?.backgroundColor = NSColor.clear.cgColor - no luck!
Even tried with only one paragraph (ie. no scroll), still the same.
I am using code similar to this:
class ViewController: NSViewController {
let scrollView = NSScrollView()
let textView = NSTextView()
override func viewDidLoad() {
super.viewDidLoad()
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
textView.autoresizingMask = .width
textView.isVerticallyResizable = true
textView.textContainer?.widthTracksTextView = true
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.documentView = textView
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
}
What can I do to have a clear (no background) track on the scrollbar?
scrollView.scrollerStyle = .overlay

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:

UIStackView Animation Issue

I have a subStackView inside a stackView and when I hide/show the contents of ONE subStackView, the animation goes all the way up over the other stack views: https://www.youtube.com/watch?v=vKXwX7OpkxU
This is how I create the subStackView. I tried with and without clipToBounds and with an without translatedAutoresizingMaskIntoConstraints. Also tried layoutIfNeeded in the animation part.
let subStackView = UIStackView(arrangedSubviews: [self.innerView[0], self.innerView[1])
subStackView.translatesAutoresizingMaskIntoConstraints = false
subStackView.axis = .vertical
subStackView.distribution = .fillEqually
subStackView.alignment = .fill
subStackView.spacing = 0
subStackView.clipsToBounds = true
This subStackView is then loaded into a mainStackView which results in the issue.
One way to fix your problem is to control more directly how the purple view is shown and hidden. What you're doing now (I assume) is setting isHidden property to true and then letting the stack view do whatever it wants. Instead, let's put the purple view inside a container view, and animate the container view's height down to zero. Then it can look like this:
The reason to use a container view instead of just animating the purple view's height directly is that you might (in general) have other constraints controlling the purple view's height, so also constraining its height to zero would fill up your console with unsatisfiable constraint errors.
So here's what I did for the demo. I made a “Hello, world!” label with a purple background. I constrained its height to 80. I put the label inside a container view (just a plain UIView). I constrained the top, leading, and trailing edges of the label to the container view, as normal. I also constrained the bottom edge of the label to the container view, but at priority 999* (which is less than the default, “required” priority of 1000). This means that the container view will try very hard to be the same size as the label, but if the container view is forced to change height, it will do so without affecting the label's height.
The container also has clipsToBounds set, so if the container becomes shorter than the label, the bottom part of the label is hidden.
To toggle the visibility of the label, I activate or deactivate a required-priority height constraint on the container view that sets its height to zero. Then I ask the window to lay out its children, inside an animation block.
In my demo, I also have the stack view's spacing set to 12. If I just leave the container view “visible” (not isHidden) with a height of zero, the stack view will put 12 points of space after the button, which can look incorrect. On iOS 11 and later, I fix this by setting a custom spacing of 0 after the button when I “hide” the container, and restore the default spacing when I “show” it.
On iOS version before iOS 11, I just go ahead and really hide the container (setting its isHidden to true) after the hiding animation completes. And I show the container (setting its isHidden to false) before running the showing animation. This results in a little bump as the spacing instantly disappears or reappears, but it's not too bad.
Handling the stack view spacing makes the code substantially bigger, so if you're not using spacing in your stack view, you can use simpler code.
Anyway, here's my code:
class TaskletViewController: UIViewController {
#IBAction func buttonWasTapped() {
if detailContainerHideConstraint == nil {
detailContainerHideConstraint = detailContainer.heightAnchor.constraint(equalToConstant: 0)
}
let wantHidden = !(detailContainerHideConstraint?.isActive ?? false)
if wantHidden {
UIView.animate(withDuration: 0.25, animations: {
if #available(iOS 11.0, *) {
self.stackView.setCustomSpacing(0, after: self.button)
}
self.detailContainerHideConstraint?.isActive = true
self.view.window?.layoutIfNeeded()
}, completion: { _ in
if #available(iOS 11.0, *) { } else {
self.detailContainer.isHidden = true
}
})
} else {
if #available(iOS 11.0, *) { } else {
detailContainer.isHidden = false
}
UIView.animate(withDuration: 0.25, animations: {
if #available(iOS 11.0, *) {
self.stackView.setCustomSpacing(self.stackView.spacing, after: self.button)
}
self.detailContainerHideConstraint?.isActive = false
self.view.window?.layoutIfNeeded()
})
}
}
override func loadView() {
stackView.axis = .vertical
stackView.spacing = 12
stackView.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = UIColor.green.withAlphaComponent(0.2)
button.setTitle("Tap to toggle", for: .normal)
button.addTarget(self, action: #selector(buttonWasTapped), for: .touchUpInside)
button.setContentHuggingPriority(.required, for: .vertical)
button.setContentCompressionResistancePriority(.required, for: .vertical)
stackView.addArrangedSubview(button)
detailLabel.translatesAutoresizingMaskIntoConstraints = false
detailLabel.text = "Hello, world!"
detailLabel.textAlignment = .center
detailLabel.backgroundColor = UIColor.purple.withAlphaComponent(0.2)
detailLabel.heightAnchor.constraint(equalToConstant: 80).isActive = true
detailContainer.translatesAutoresizingMaskIntoConstraints = false
detailContainer.clipsToBounds = true
detailContainer.addSubview(detailLabel)
let bottomConstraint = detailLabel.bottomAnchor.constraint(equalTo: detailContainer.bottomAnchor)
bottomConstraint.priority = .init(999)
NSLayoutConstraint.activate([
detailLabel.topAnchor.constraint(equalTo: detailContainer.topAnchor),
detailLabel.leadingAnchor.constraint(equalTo: detailContainer.leadingAnchor),
detailLabel.trailingAnchor.constraint(equalTo: detailContainer.trailingAnchor),
bottomConstraint
])
stackView.addArrangedSubview(detailContainer)
self.view = stackView
}
private let stackView = UIStackView()
private let button = UIButton(type: .roundedRect)
private let detailLabel = UILabel()
private let detailContainer = UIView()
private var detailContainerHideConstraint: NSLayoutConstraint?
}

reset the UIView when UISearchController is focused

I am facing an issue where I can see a black portion in my UIViewController where I have a UISearchController, UICollectionView and UISegmentControl because of my poor design.
I have added a UISearchController in navigationbar using following code:
func setupSearchBar(){
navigationItem.searchController = taskSearchController
taskSearchController.searchBar.tintColor = .white
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.lightGray]
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).attributedPlaceholder = NSAttributedString(string: "Search Text", attributes: [NSAttributedStringKey.foregroundColor: UIColor.lightGray])
if let textfield = taskSearchController.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundview = textfield.subviews.first {
backgroundview.backgroundColor = UIColor.init(white: 1, alpha: 1)
backgroundview.layer.cornerRadius = 10
backgroundview.clipsToBounds = true
}
}
taskSearchController.hidesNavigationBarDuringPresentation = true
//navigationItem.hidesSearchBarWhenScrolling = false
//taskSearchController.searchBar.scopeButtonTitles = ["ASSIGNED TASK","CREATED TASK"]
}
after adding UISearchController I am adding a UISegmentControl and a UICollectionView using following code:
func setupView(){
//self.view.addSubview(coverView)
self.view.addSubview(taskSegment)
taskSegment.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
taskSegment.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
taskSegment.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
taskSegment.heightAnchor.constraint(equalToConstant: 30).isActive = true
self.view.addSubview(collectionView)
collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: taskSegment.bottomAnchor, constant: 10).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 5).isActive = true
}
This my initial view which is fine.
This is how it looks when UISearchController is focused
Here you can see when navigationBar hides and UISearchController placed at top at that time my UISegment and UIcollectionView remains at same position so I want to move it according to change in UISearchController.
Can anyone tell me what should I do?
Thank You in advance.
It hides the navigation bar because hideNavigationBarDuringPresentation is set to true. This is intended behavior.
You either need to disable this so that the view constraints dont change during presentation or you need to amend your constraints so that the view would adjust to the height change of the navigation bar. You should be able to do this quite easily by setting a top constraint to the Safe area of the main view.

White line under navigation bar when scrolling or until pressing on the search bar

As you can see in the video, there is a white thin line under the navigation bar the moment i start scrolling.
It would only disappear when I would press on the searchBar (contained by my searchController, so the search bar is not added from the Storyboard). I tried a lot of different combinations in order to try to make it disappear but nothing worked.
Any help is appreciated! Thanks!
Video: https://www.youtube.com/watch?v=KcgZmBg1VS0
This is the code inside my viewDidLoad:
searchController.searchBar.delegate = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.placeholder = "Search for a recipe"
searchController.searchBar.barTintColor = navigationController?.navigationBar.barTintColor
searchController.searchBar.tintColor = UIColor.white
Try to set background color and change shadow of the navigationBar. It possibly connected with navigationBar.
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
self.navigationController?.navigationBar.shadowImage = UIImage()
Try to add this line:
searchController.searchBar.backgroundColor = navigationController?.navigationBar.barTintColor
In the file setupSearchBar()
After code fix:
You can remove that shadow below your navigation bar by the way. I have created extension for doing this:
extension UINavigationBar {
func shouldRemoveShadow(_ value: Bool) -> Void {
self.setValue(value, forKey: "hidesShadow")
}
}